小黑子的SSM整合

news2024/11/15 23:19:12

SSM整合

  • 一、基于restful页面数据交互
    • 1.1 后台接口开发
    • 1.2 页面访问处理
  • 二、ssm整合
    • 2.1 流程分析
    • 2.2 整合配置
    • 2.3 功能模块开发
    • 2.4 接口测试
    • 2.5 表现层与前端数据传输协议定义
      • 2.5.1 协议实现
    • 2.6 异常处理器
      • 2.6.1 @RestControllerAdvice
      • 2.6.2 @ExceptionHandler
      • 2.6.3 项目异常处理
        • 2.6.3-I 异常分类
        • 2.6.3-II 异常解决方案
        • 2.6.3-III 具体实现
    • 2.7 前后台协议联调
      • 2.7.1 列表功能
    • 2.7.2 添加功能
      • 2.7.3 添加功能状态处理
      • 2.7.4 修改功能
      • 2.7.5 删除功能

一、基于restful页面数据交互

  • 入门案例
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

-快速开发
在这里插入图片描述
在这里插入图片描述

1.1 后台接口开发

准备:

  • pom文件的导包

    <dependencies>
    	<dependency>
     	 <groupId>org.springframework</groupId>
     	 <artifactId>spring-context</artifactId>
      	<version>5.3.7 </version>
    </dependency>
    
    
      <dependency>
      	<groupId>junit</groupId>
     	 <artifactId>junit</artifactId>
     	 <version>4.13.2</version>
     	 <scope>test</scope>
    	</dependency>
    
     <dependency>
      	<groupId>org.springframework</groupId>
      	<artifactId>spring-web</artifactId>
      	<version>5.3.7</version>
    	</dependency>
    
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.3.7</version>
    </dependency>
    
    	<!-- druid数据源-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.23</version>
    </dependency>
    
    	<!--        mysql-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.30</version>
    </dependency>
    
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.5</version>
    </dependency>
    
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope>
    </dependency>
    
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <!--注意版本,因为版本过低的原因在这里卡了很久!!!-->
      <version>3.0.1</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.25.RELEASE</version>
    </dependency>
    
    <dependency>
      <groupId>javax.annotation</groupId>
      <artifactId>jsr250-api</artifactId>
      <version>1.0</version>
    </dependency>
    
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.6</version>
    </dependency>
    
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.0.9.RELEASE</version>
      <scope>test</scope>
    </dependency>
    
    <!--    一定要加这个导包,不加就解析不了json格式-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.0</version>
    </dependency>
    </dependencies>
    
  • config包下的固定配置类

    //web容器的配置类
    public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0];
    }
    
     protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }
    
    	protected String[] getServletMappings() {
        return new String[]{"/"};
    }
    
    	//乱码处理
    	@Override
    	protected Filter[] getServletFilters(){
        	CharacterEncodingFilter filter = new CharacterEncodingFilter();
        	filter.setEncoding("UTF-8");
        	return new Filter[]{filter};
    	}
    }
    
    @Configuration
    @ComponentScan("com.itheima.controller")
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    
  • controller包

    @RestController
    @RequestMapping("/books")
    public class BookController {
    
    @PostMapping
    public String save(@RequestBody Book book){//接收json数据格式
        System.out.println("book save ==>"+book);
        return "{'module':'book save success'}";
    }
    
    @GetMapping
    public List<Book> getAll(){
      List<Book> bookList =  new ArrayList<Book>();
        Book book1 = new Book();
        book1.setType("计算机");
        book1.setName("SpringMVC入门");
        book1.setDescription("小试牛刀");
        bookList.add(book1);
    
        Book book2 = new Book();
        book2.setType("计算机");
        book2.setName("SpringMVC实战教程");
        book2.setDescription("一代宗师");
        bookList.add(book2);
    
        return bookList;
     }
    }
    
  • pojo实体类

    public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;
    
    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", type='" + type + '\'' +
                ", name='" + name + '\'' +
                ", description='" + description + '\'' +
                '}';
    }
    
    public Book() {
    }
    
    public Book(Integer id, String type, String name, String description) {
        this.id = id;
        this.type = type;
        this.name = name;
        this.description = description;
    }
    
    public Integer getId() {
        return id;
    }
    
    public void setId(Integer id) {
        this.id = id;
    }
    
    public String getType() {
        return type;
    }
    
    public void setType(String type) {
        this.type = type;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public String getDescription() {
        return description;
    }
    
    public void setDescription(String description) {
        this.description = description;
    }
    
    
    }
    

后台数据传输成功!
在这里插入图片描述
在这里插入图片描述

1.2 页面访问处理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 修改扫描包部分
@Configuration
@ComponentScan({"com.itheima.controller","com.itheima.config"})
@EnableWebMvc
public class SpringMvcConfig {
}
  • config包的SpringMvcSupport
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    //设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        //当访问/pags/????时候,走page目录下的内容
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    }
}

web的文件:
在这里插入图片描述
展示一下html页:

<!DOCTYPE html>

<html>
    <head>
        <!-- 页面meta -->
        <meta charset="utf-8">
        <title>SpringMVC案例</title>
        <!-- 引入样式 -->
        <link rel="stylesheet" href="../plugins/elementui/index.css">
        <link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min.css">
        <link rel="stylesheet" href="../css/style.css">
    </head>

    <body class="hold-transition">

        <div id="app">

            <div class="content-header">
                <h1>图书管理</h1>
            </div>

            <div class="app-container">
                <div class="box">
                    <div class="filter-container">
                        <el-input placeholder="图书名称" style="width: 200px;" class="filter-item"></el-input>
                        <el-button class="dalfBut">查询</el-button>
                        <el-button type="primary" class="butT" @click="openSave()">新建</el-button>
                    </div>

                    <el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row>
                        <el-table-column type="index" align="center" label="序号"></el-table-column>
                        <el-table-column prop="type" label="图书类别" align="center"></el-table-column>
                        <el-table-column prop="name" label="图书名称" align="center"></el-table-column>
                        <el-table-column prop="description" label="描述" align="center"></el-table-column>
                        <el-table-column label="操作" align="center">
                            <template slot-scope="scope">
                                <el-button type="primary" size="mini">编辑</el-button>
                                <el-button size="mini" type="danger">删除</el-button>
                            </template>
                        </el-table-column>
                    </el-table>

                    <div class="pagination-container">
                        <el-pagination
                            class="pagiantion"
                            @current-change="handleCurrentChange"
                            :current-page="pagination.currentPage"
                            :page-size="pagination.pageSize"
                            layout="total, prev, pager, next, jumper"
                            :total="pagination.total">
                        </el-pagination>
                    </div>

                    <!-- 新增标签弹层 -->
                    <div class="add-form">
                        <el-dialog title="新增图书" :visible.sync="dialogFormVisible">
                            <el-form ref="dataAddForm" :model="formData" :rules="rules" label-position="right" label-width="100px">
                                <el-row>
                                    <el-col :span="12">
                                        <el-form-item label="图书类别" prop="type">
                                            <el-input v-model="formData.type"/>
                                        </el-form-item>
                                    </el-col>
                                    <el-col :span="12">
                                        <el-form-item label="图书名称" prop="name">
                                            <el-input v-model="formData.name"/>
                                        </el-form-item>
                                    </el-col>
                                </el-row>
                                <el-row>
                                    <el-col :span="24">
                                        <el-form-item label="描述">
                                            <el-input v-model="formData.description" type="textarea"></el-input>
                                        </el-form-item>
                                    </el-col>
                                </el-row>
                            </el-form>
                            <div slot="footer" class="dialog-footer">
                                <el-button @click="dialogFormVisible = false">取消</el-button>
                                <el-button type="primary" @click="saveBook()">确定</el-button>
                            </div>
                        </el-dialog>
                    </div>

                </div>
            </div>
        </div>
    </body>

    <!-- 引入组件库 -->
    <script src="../js/vue.js"></script>
    <script src="../plugins/elementui/index.js"></script>
    <script type="text/javascript" src="../js/jquery.min.js"></script>
    <script src="../js/axios-0.18.0.js"></script>

    <script>
        var vue = new Vue({

            el: '#app',

            data:{
				dataList: [],//当前页要展示的分页列表数据
                formData: {},//表单数据
                dialogFormVisible: false,//增加表单是否可见
                dialogFormVisible4Edit:false,//编辑表单是否可见
                pagination: {},//分页模型数据,暂时弃用
            },

            //钩子函数,VUE对象初始化完成后自动执行
            created() {
                this.getAll();
            },

            methods: {
                // 重置表单
                resetForm() {
                    //清空输入框
                    this.formData = {};
                },

                // 弹出添加窗口
                openSave() {
                    this.dialogFormVisible = true;
                    this.resetForm();
                },

                //添加
                saveBook () {
                    axios.post("/books",this.formData).then((res)=>{

                    });
                },

                //主页列表查询
                getAll() {
                    axios.get("/books").then((res)=>{
                        this.dataList = res.data;
                    });
                },

            }
        })
    </script>
</html>

在这里插入图片描述
在这里插入图片描述

二、ssm整合

什么是ssm整合?

  • 微观:将学习的Spring SpringMVC Mybatis框架应用到项目中!
    • SpringMVC框架负责控制层
    • Spring框架负责整体和业务层的声明式事务管理
    • MyBatis框架负责数据库访问层
  • 宏观:Spring 接管一切(将框架核心组件交给Spring进行loC管理)。代码更加简洁。
    • SpringMVC管理表述层、SpringMVC相关组件
    • Spring 管理业务层、持久层、以及数据库相关(DataSource,MyBatis)的组件
    • 使用loC的方式管理一切所需组件
  • 实施:通过编写配置文件,实现 SpringloC容器接管—切组件。

思考:

第一问: ssm整合需要几个IoC容器?

两个容器
本质上说,整合就是将三层架构和框架核心API组件交给SpringloC容器管理!

一个容器可能就够了,但是我们常见的操作是创建两个loC容器(web容器和root容器),组件分类管理! 这种做法有以下好处和目的:

  1. 分离关注点:通过初始化两个容器,可以将各个层次的关注点进行分离。这种分离使得各个层次的组件能够更好地聚焦于各自的责任和功能。
  2. 解耦合:各个层次组件分离装配不同的loC容器,这样可以进行解耦。这种解耦合使得各个模块可以独立操作和测试,提高了代码的可维护性和可测试性。
  3. 灵活配置:通过使用两个容器,可以为每个容器提供各自的配置,以满足不同层次和组件的特定需求。每个配置文件也更加清晰和灵活。

总的来说,初始化两个容器在SSM整合中可以实现关注点分离、解耦合、灵活配置等好处。它们各自负责不同的层次和功能,并通过合适的集成方式协同工作,提供一个高效、可维护和可扩展的应用程序架构!

第二问:每个IoC容器对应哪些类型组件
在这里插入图片描述

容器名盛放组件
web容器web相关组件(controller,springmvc核心组件)
root容器业务和持久层相关组件(service,aop,tx,dataSource,mybatis,mapper等)

第三问:IoC容器之间关系和调用方向

  • 情况1:两个无关联之间的组件无法注入
    在这里插入图片描述

  • 情况2:子IoC容器可以单向的注入父亲、IoC容器的组件
    在这里插入图片描述

结论:web容器是root容器的子容器,父子容器关系

  • 父容器:root容器,放service、mapper、mybatis等
  • 子容器:web容器,放controller、web相关等

第四问: 具体多少配置类以及对应容器关系

配置类的数量是不固定的,但是至少要两个,为了方便编写,可以三层架构每层对应一个配置类,分别指定两个容器加载即可

在这里插入图片描述

建议的相关配置文件:

配置名对应内容对应容器
WebJavaConfigcontroller,springMvc相关web容器
ServiceJavaConfigservice,aop,tx相关root容器
MapperJavaConfigmapper,datasource,mybatis相关root容器

第五问:IoC初始化方式和配置关系

在web项目下,可以选择web.xml和配置类方式进行ioc配置,推荐配置类。
对于使用基于web的Spring配置的应用程序,建议如下的示例:
在这里插入图片描述
图解配置类和容器配置:
在这里插入图片描述

2.1 流程分析

  1. 创建工程

    • 创建一个Maven的web工程
    • pom.xml添加SSM需要的依赖jar包
    • 编写Web项目的入口配置类,实现AbstractAnnotationConfigDispatcherServletInitializer重写以下方法
      • getRootConfigClasses() :返回Spring的配置类 --> 需要SpringConfig配置类
      • getServletConfigClasses() :返回SpringMVC的配置类 --> 需要SpringMvcConfig配置类
      • getServletMappings() : 设置SpringMVC请求拦截路径规则
      • getServletFilters() :设置过滤器,解决POST请求中文乱码问题
  2. SSM整合(重点是各个配置的编写)

    • SpringConfig
      • 标识该类为配置类,使用@Configuration
      • 扫描Service所在的包,使用@ComponentScan
      • Service层要管理事务,使用@EnableTransactionManagement
      • 读取外部的properties配置文件,使用@PropertySource
      • 整合Mybatis需要引入Mybatis相关配置类,使用@Import
        • 第三方数据源配置类 JdbcConfig
        • 构建DataSource数据源,DruidDataSouroce,需要注入数据库连接四要素,使用 @Bean@Value
        • 构建平台事务管理器,DataSourceTransactionManager,使用@Bean
        • Mybatis配置类 MybatisConfig
        • 构建SqlSessionFactoryBean并设置别名扫描与数据源,使用@Bean
        • 构建MapperScannerConfigurer并设置DAO层的包扫描
    • SpringMvcConfig
      • 标识该类为配置类,使用@Configuratio
      • 扫描Controller所在的包,使用@ComponentScan
      • 开启SpringMVC注解支持,使用@EnableWebMvc
  3. 功能模块(与具体的业务模块有关)

    • 创建数据库表
    • 根据数据库表创建对应的模型类
    • 通过Dao层完成数据库表的增删改查(接口+自动代理)
    • 编写Service层(Service接口+实现类)
      • @Service
      • @Transactional
      • 整合Junit对业务层进行单元测试
        • @RunWith
        • @ContextConfiguration
        • @Test
    • 编写Controller
      • 接收请求 @RequestMapping@GetMapping@PostMapping@PutMapping@DeleteMapping
      • 接收数据 简单、POJO、嵌套POJO、集合、数组、JSON数据类型
        • @RequestParam
        • @PathVariable
        • @RequestBody
      • 转发业务层
        • @Autowired
      • 响应结果
        • @ResponseBody

2.2 整合配置

  • 步骤一:创建Maven的web项目

  • 步骤二:导入坐标

    <dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>
    
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>1.3.0</version>
    </dependency>
    
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.46</version>
    </dependency>
    
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.16</version>
    </dependency>
    
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
    
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.0</version>
    </dependency>
    </dependencies>
    
  • 步骤三:创建项目包结构

    • com.blog.config目录存放的是相关的配置类
    • com.blog.controller编写的是Controller类
    • com.blog.dao存放的是Dao接口,因为使用的是Mapper接口代理方式,所以没有实现类包
    • com.blog.service存的是Service接口,com.blog.service.impl存放的是Service实现类
    • resources:存入的是配置文件,如Jdbc.properties
    • webapp:目录可以存放静态资源
    • test/java:存放的是测试类
  • 步骤四:创建jdbc.properties

    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url = jdbc:mysql://localhost:3306/ssm_db?useSSL=false
    jdbc.username=root
    jdbc.password=root.
    
  • 步骤五:创建JdbcConfig配置类

    public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
    
    @Bean
    public PlatformTransactionManager platformTransactionManager(DataSource dataSource){
        DataSourceTransactionManager ds = new DataSourceTransactionManager();
        ds.setDataSource(dataSource);
        return ds;
    	}
    }
    
  • 步骤六:创建MyBatisConfig配置类

    public class MyBatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setTypeAliasesPackage("com.blog.domain");
        return factoryBean;
    }
    
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.blog.dao");
        return msc;
    	}
    }
    
  • 步骤七:创建SpringConfig配置类

    @Configuration
    @ComponentScan("com.blog.service")
    @PropertySource("jdbc.properties")
    @EnableTransactionManagement
    @Import({JdbcConfig.class, MyBatisConfig.class})
    public class SpringConfig {
    }
    
  • 步骤八:创建SpringMvcConfig配置类

    @Configuration
    @ComponentScan("com.blog.controller")
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    
  • 步骤九:创建ServletContainersInitConfig配置类

    public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }
    
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }
    
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
    
    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("utf-8");
        return new Filter[]{filter};
    	}
    }
    

2.3 功能模块开发

  • 步骤一:创建数据库及表
    在这里插入图片描述

  • 步骤二:编写pojo模型类

    public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;
    
    public Integer getId() {
        return id;
    }
    
    public void setId(Integer id) {
        this.id = id;
    }
    
    public String getType() {
        return type;
    }
    
    public void setType(String type) {
        this.type = type;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public String getDescription() {
        return description;
    }
    
    public void setDescription(String description) {
        this.description = description;
    }
    
    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", type='" + type + '\'' +
                ", name='" + name + '\'' +
                ", description='" + description + '\'' +
                '}';
    	}
    }
    
  • 步骤三:编写Dao接口

    public interface BookDao {
    @Insert("insert into tbl_book values (null, #{type}, #{name}, #{description})")
    void save(Book book);
    
    @Update("update tbl_book set type=#{type}, `name`=#{name}, `description`=#{description} where id=#{id}")
    void update(Book book);
    
    @Delete("delete from tbl_book where id=#{id}")
    void delete(Integer id);
    
    @Select("select * from tbl_book where id=#{id}")
    void getById(Integer id);
    
    @Select("select * from tbl_book")
    void getAll();
    }
    

idea在进行ssm整合的过程中,如果这个东西在整个系统中目前不存在(即:spring中没有配不可dao的bean)
在这里插入图片描述

在这里插入图片描述

  • 步骤四:编写Service接口及其实现类

    @Transactional
    public interface BookService {
    /**
     * 保存
     * @param book
     * @return
     */
    boolean save(Book book);
    
    /**
     * 修改
     * @param book
     * @return
     */
    boolean update(Book book);
    
    /**
     * 按id删除
     * @param id
     * @return
     */
    boolean delete(Integer id);
    
    /**
     * 按id查询
     * @param id
     * @return
     */
    Book getById(Integer id);
    
    /**
     * 查询所有
     * @return
     */
    List<Book> getAll();
    }
    
    @Service
    public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;
    
    
    public boolean save(Book book) {
        bookDao.save(book);
        return true;
    }
    
    
    public boolean update(Book book) {
        bookDao.update(book);
        return true;
    }
    
    public boolean delete(Integer id) {
        bookDao.delete(id);
        return true;
    }
    
    
    public Book getById(Integer id) {
        return bookDao.getById(id);
    }
    
    public List<Book> getAll() {
        return bookDao.getAll();
        }
    }
    
  • 步骤五:编写Controller类

    @RestController
    @RequestMapping("/books")
    public class BookController {
    @Autowired
    private BookService bookService;
    
    @PostMapping
    public boolean save(@RequestBody Book book) {
        return bookService.save(book);
    }
    
    @PutMapping
    public boolean update(@RequestBody Book book) {
        return bookService.update(book);
    }
    
    @DeleteMapping("/{id}")
    public boolean delete(@PathVariable Integer id) {
        return bookService.delete(id);
    }
    
    @GetMapping("/{id}")
    public Book getById(@PathVariable Integer id) {
        return bookService.getById(id);
    }
    
    @GetMapping
    public List<Book> getAll() {
        return bookService.getAll();
    	}
    }
    

2.4 接口测试

  • 步骤一:新建测试类

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = 	SpringConfig.class)
    public class BookServiceTest {
    }
    
  • 步骤二:注入Service

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfig.class)
    public class BookServiceTest {
    @Autowired
    	private BookService bookService;
    }
    
  • 步骤三:编写测试方法

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfig.class)
    public class BookServiceTest {
    
    @Autowired
    private BookService bookService;
    
    @Test
    public void testGetById() {
        Book book = bookService.getById(1);
        System.out.println(book);
    }
    
    @Test
    public void testGetAll() {
        for (Book book : bookService.getAll()) {
            System.out.println(book);
        }
     }
    }
    

运行报错:com.mysql.cj.jdbc.Driver

jdbc.driver = com.mysql.cj.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username = root
jdbc.password = root

在这里插入图片描述

修改如下:

jdbc.driver=com.mysql.jdbc.Driver

运行测试方法,可以查询到对应的数据
在这里插入图片描述

PostMan测试

  • 查下
    发送POST请求与数据,访问localhost:8080/books
    在这里插入图片描述

在这里插入图片描述

2.5 表现层与前端数据传输协议定义

SSM整合以及功能模块开发完成后,接下来我们在上述案例的基础上,分析一下有哪些问题需要我们解决。
首先第一个问题是:

  • 在Controller层增删改操作完成后,返回给前端的是boolean类型的数据
    true
  • 在Controller层查询单个,返回给前端的是对象

在这里插入图片描述

  • 目前我们就已经有三种数据类型返回给前端了,随着业务的增长,我们需要返回的数据类型就会越来越多。那么前端开发人员在解析数据的时候就比较凌乱了,所以对于前端来说,如果后端能返回一个统一的数据结果,前端在解析的时候就可以按照一种方式进行解析,开发就会变得更加简单

  • 所以现在我们需要解决的问题就是如何将返回的结果数据进行统一,具体如何来做,大体思路如下

    • 为了封装返回的结果数据:创建结果模型类,封装数据到data属性中

      • 可以设置data的数据类型为Object,这样data中就可以放任意的结果类型了,包括但不限于上面的boolean对象集合对象
    • 为了封装返回的数据是何种操作,以及是否操作成功:封装操作结果到code属性中

      • 例如增删改操作返回的都是true,那我们怎么分辨这个true到底是还是还是呢?我们就通过这个code来区分
    • 操作失败后,需要封装返回错误信息提示给用户:封装特殊消息到message(msg)属性中

      • 例如查询或删除的目标不存在,会返回null,那么此时我们需要提示查询/删除的目标不存在,请重试!

      在这里插入图片描述
      在这里插入图片描述

在这里插入图片描述

2.5.1 协议实现

对于结果封装,我们应该是在表现层进行处理,所以我们把结果类放在controller包下,当然你也可以放在domain包,这个都是可以的,具体如何实现结果封装,具体的步骤如下:

  • 步骤一:创建Result类

    public class Result {
    	//描述统一格式中的编码,用于区分操作,可以简化配置0或1表示成功失败
    private Integer code;
    //描述统一格式中的数据
    private Object data;
    //描述统一格式中的消息,可选属性
    private String msg;
    
    public Result() {
    }
    
    //构造器可以根据自己的需要来编写
    public Result(Integer code, Object data) {
        this.code = code;
        this.data = data;
    }
    
    public Result(Integer code, Object data, String msg) {
        this.code = code;
        this.data = data;
        this.msg = msg;
    }
    
    public Integer getCode() {
        return code;
    }
    
    public void setCode(Integer code) {
        this.code = code;
    }
    
    public Object getData() {
        return data;
    }
    
    public void setData(Object data) {
        this.data = data;
    }
    
    public String getMsg() {
        return msg;
    }
    
    public void setMsg(String msg) {
        this.msg = msg;
    }
    
    @Override
    public String toString() {
        return "Result{" +
                "code=" + code +
                ", data=" + data +
                ", msg='" + msg + '\'' +
                '}';
    	}
    }
    
  • 步骤二:定义返回码Code类

    public class Code {
    public static final Integer SAVE_OK = 20011;
    public static final Integer UPDATE_OK = 20021;
    public static final Integer DELETE_OK = 20031;
    public static final Integer GET_OK = 20041;
    
    public static final Integer SAVE_ERR = 20010;
    public static final Integer UPDATE_ERR = 20020;
    public static final Integer DELETE_ERR = 20030;
    public static final Integer GET_ERR = 20040;
    }
    

注意:code类中的常量设计也不是固定的,可以根据需要自行增减,例如将查询再进行细分为GET_OK,GET_ALL_OK,GET_PAGE_OK等。

  • 步骤三:修改Controller类的返回值

    @RestController
    @RequestMapping("/books")
    public class BookController {
    @Autowired
    private BookService bookService;
    
    @PostMapping
    public Result save(@RequestBody Book book) {
        boolean flag = bookService.save(book);
        return new Result(flag ? Code.SAVE_OK : Code.SAVE_ERR, flag);
    }
    
    @PutMapping
    public Result update(@RequestBody Book book) {
        boolean flag = bookService.update(book);
        return new Result(flag ? Code.UPDATE_OK : Code.UPDATE_ERR, flag);
    }
    
    @DeleteMapping("/{id}")
    public Result delete(@PathVariable Integer id) {
        boolean flag = bookService.delete(id);
        return new Result(flag ? Code.DELETE_OK : Code.DELETE_ERR, flag);
    }
    
    @GetMapping("/{id}")
    public Result getById(@PathVariable Integer id) {
        Book book = bookService.getById(id);
        Integer code = book == null ? Code.GET_ERR : Code.GET_OK;
        String msg = book == null ? "数据查询失败,请重试!" : "";
        return new Result(code, book, msg);
    }
    
    @GetMapping
    public Result getAll() {
        List<Book> bookList = bookService.getAll();
        Integer code = bookList == null ? Code.GET_ERR : Code.GET_OK;
        String msg = bookList == null ? "数据查询失败,请重试!" : "";
        return new Result(code, bookList, msg);
    	}
    }
    
  • 步骤四:启动服务测试
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

2.6 异常处理器

问题描述:
在学习这部分知识之前,先来演示一个效果,修改BookController的getById()方法,手写一个异常

@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
    //当id为1的时候,手动添加了一个错误信息
    if (id == 1){
        int a = 1 / 0;
    }
    Book book = bookService.getById(id);
    Integer code = book == null ? Code.GET_ERR : Code.GET_OK;
    String msg = book == null ? "数据查询失败,请重试!" : "";
    return new Result(code, book, msg);
}

重新启动服务器,使用PostMan发送请求,当传入的id为1时,会出现如下效果
在这里插入图片描述

前端接收到这个信息后,和我们之前约定的格式不一致,怎么解决呢?
在解决问题之前,我们先来看一下异常的种类,以及出现异常的原因:

  • 框架内部抛出的异常:因使用不合规导致
  • 数据层抛出的异常:因使用外部服务器故障导致(例如:服务器访问超时)
  • 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引越界异常等)
  • 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间转换导致异常)
  • 工具类抛出的异常:因工具类书写不严谨,健壮性不足导致(例如:必要时放的连接,长时间未释放等)

了解完上面这些出现异常的位置,我们发现,在我们开发的任何一个位置都可能会出现异常,而且这些异常是不能避免的,所以我们就需要对这些异常来进行处理。

思考

  1. 各个层级均出现异常,那么异常处理代码要写在哪一层?
    • 所有的异常均抛出到表现层进行处理
  2. 异常的种类很多,表现层如何将所有的异常都处理到呢?
    • 异常分类
  3. 表现层处理异常,每个方法中单独书写,代码书写两巨大,且意义不强,如何解决呢?
    • AOP

对于上面这些问题以及解决方案,SpringMVC已经为我们提供了一套了:

  • 异常处理器:

    • 集中的、统一的处理项目中出现的异常
    @RestControllerAdvice
    public class ProjectExceptionAdvice {
    @ExceptionHandler(Exception.class)
    public Result doException(Exception ex) {
        return new Result(666, null);
    	}
    }
    

2.6.1 @RestControllerAdvice

说明:此注解自带@ResponseBody注解与@Component注解,具备对应的功能

名称@RestControllerAdvice
类型类注解
位置Rest风格开发的控制器增强类定义上方
作用为Rest风格开发的控制器类做增强

2.6.2 @ExceptionHandler

说明:此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常

名称@ExceptionHandler
类型方法注解
位置专用于异常处理的控制器方法上方
作用设置指定异常的处理方案,功能等同于控制器方法,出现异常后终止原始控制器执行,并转入当前方法执行

异常处理器的使用

  • 步骤一:创建异常处理器类
    注意:要确保SpringMvcConfig能够扫描到异常处理器类

    @RestControllerAdvice
    public class ProjectExceptionAdvice {
    @ExceptionHandler(Exception.class)
    public void doException(Exception ex) {
        System.out.println("嘿嘿,逮到一个异常~");
    	}
    }
    
  • 步骤二:让程序抛出异常

    @GetMapping("/{id}")
    public Result getById(@PathVariable Integer id) {
    //当id为1的时候,手动添加了一个错误信息
    if (id == 1){
        int a = 1 / 0;
    }
    Book book = bookService.getById(id);
    Integer code = book == null ? Code.GET_ERR : Code.GET_OK;
    String msg = book == null ? "数据查询失败,请重试!" : "";
    return new Result(code, book, msg);
    }
    
  • 步骤三:使用PostMan发送GET请求访问localhost:8080/books/1
    控制台输出如下,说明异常已经被拦截,且执行了doException()方法
    但是现在没有返回数据给前端,为了统一返回结果,我们继续修改异常处理器类
    在这里插入图片描述

    @RestControllerAdvice
    public class ProjectExceptionAdvice {
    @ExceptionHandler(Exception.class)
    public Result doException(Exception ex) {
        System.out.println("嘿嘿,逮到一个异常~");
        return new Result(666, null, "嘿嘿,逮到一个异常~");
    	}
    }
    

在这里插入图片描述

2.6.3 项目异常处理

2.6.3-I 异常分类

因为异常的种类有很多,如果每一个异常都对应一个@ExceptionHandler,那得写多少个方法来处理各自的异常,所以我们在处理异常之前,需要对异常进行一个分类:

  • 业务异常(BusinessException)

    • 规范的用户行为产生的异常
      • 用户在页面输入内容的时候未按照指定格式进行数据填写,如在年龄框输入的是字符串
  • 不规范的用户行为操作产生的异常

    • 如用户手改URL,故意传递错误数据localhost:8080/books/略略略
      在这里插入图片描述
  • 系统异常(SystemException)

    • 项目运行过程中可预计,但无法避免的异常
    • 如服务器宕机
      在这里插入图片描述
  • 其他异常(Exception)

    • 编程人员未预期到的异常
      • 如:系统找不到指定文件
        在这里插入图片描述

将异常分类以后,针对不同类型的异常,要提供具体的解决方案

2.6.3-II 异常解决方案
  • 业务异常(BusinessException)
    • 发送对应消息传递给用户,提醒规范操作
      • 大家常见的就是提示用户名已存在或密码格式不正确等
  • 系统异常(SystemException)
    • 发送固定消息传递给用户,安抚用户
      • 系统繁忙,请稍后再试
      • 系统正在维护升级,请稍后再试
      • 系统出问题,请联系系统管理员等
    • 发送特定消息给运维人员,提醒维护
      • 可以发送短信、邮箱或者是公司内部通信软件
  • 记录日志
    • 发消息给运维和记录日志对用户来说是不可见的,属于后台程序
  • 其他异常(Exception)
    • 发送固定消息传递给用户,安抚用户
    • 发送特定消息给编程人员,提醒维护(纳入预期范围内)
      • 一般是程序没有考虑全,比如未做非空校验等
    • 记录日志
2.6.3-III 具体实现

思路:

  1. 先通过自定义异常,完成2. BusinessExceptionSystemException的定义
  2. 将其他异常包装成自定义异常类型
  3. 在异常处理器类中对不同的异常进行处理
  • 步骤一:自定义异常类

    public class SystemException extends RuntimeException {
    private Integer code;
    
    public Integer getCode() {
        return code;
    }
    
    public void setCode(Integer code) {
        this.code = code;
    }
    
    public SystemException() {
    }
    
    public SystemException(Integer code) {
        this.code = code;
    
    }
    
    public SystemException(Integer code, String message) {
        super(message);
        this.code = code;
    }
    
    public SystemException(Integer code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    	}
    }
    
    public class BusinessException extends RuntimeException{
    private Integer code;
    
    public Integer getCode() {
        return code;
    }
    
    public void setCode(Integer code) {
        this.code = code;
    }
    
    public BusinessException() {
    }
    
    public BusinessException(Integer code) {
        this.code = code;
    
    }
    
    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
    }
    
    public BusinessException(Integer code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    	}
    }
    

说明:
让自定义异常类继承RuntimeException的好处是,后期在抛出这两个异常的时候,就不用在try…catch…或throws了
自定义异常类中添加code属性的原因是为了更好的区分异常是来自哪个业务的

  • 步骤二:将其他异常包成自定义异常
    假如在BookServiceImplgetById方法抛异常了,该如何来包装呢?

具体的包装方式有:

方式一:try{}catch(){}在catch中重新throw我们自定义异常即可。 方式二:直接throw自定义异常即可

public Book getById(Integer id) {
    //模拟业务异常,包装成自定义异常
    if(id == 1){
        throw new BusinessException(Code.BUSINESS_ERR,"你别给我乱改URL噢");
    }
    //模拟系统异常,将可能出现的异常进行包装,转换成自定义异常
    try{
        int i = 1/0;
    }catch (Exception e){
        throw new SystemException(Code.SYSTEM_TIMEOUT_ERR,"服务器访问超时,请重试!",e);
    }
    return bookDao.getById(id);
}

上面为了使code看着更专业些,我们在Code类中再新增需要的属性

public class Code {
    public static final Integer SAVE_OK = 20011;
    public static final Integer UPDATE_OK = 20021;
    public static final Integer DELETE_OK = 20031;
    public static final Integer GET_OK = 20041;

    public static final Integer SAVE_ERR = 20010;
    public static final Integer UPDATE_ERR = 20020;
    public static final Integer DELETE_ERR = 20030;
    public static final Integer GET_ERR = 20040;

    public static final Integer SYSTEM_ERR = 50001;
    public static final Integer SYSTEM_TIMEOUT_ERR = 50002;
    public static final Integer SYSTEM_UNKNOW_ERR = 59999;

    public static final Integer PROJECT_VALIDATE_ERR = 60001;
    public static final Integer BUSINESS_ERR = 60002;
}
  • 步骤三:处理器类中处理自定义异常

    @RestControllerAdvice
    public class ProjectExceptionAdvice {
    @ExceptionHandler(SystemException.class)
    public Result doSystemException(SystemException ex) {
        return new Result(ex.getCode(), null, ex.getMessage());
    }
    
    @ExceptionHandler(BusinessException.class)
    public Result doBusinessException(BusinessException ex) {
        return new Result(ex.getCode(), null, ex.getMessage());
    }
    
    @ExceptionHandler(Exception.class)
    public Result doException(Exception ex) {
        return new Result(Code.SYSTEM_UNKNOW_ERR, null, "系统繁忙,请稍后再试!");
     }
    }
    
  • 步骤四:运行程序
    根据ID查询,如果传入的参数为1,会报BusinessException,错误信息应为你别给我乱改URL噢
    在这里插入图片描述
    在这里插入图片描述

运行一直显示系统繁忙的,把之前BookController中测试异常的int i = 1/0去掉。因为Exception是用来处理未安排的异常分支的。

那么对于异常我们就已经处理完成了,不管后台哪一层抛出异常,都会以我们与前端约定好的方式进行返回,前端只需要把信息获取到,根据返回的正确与否来展示不同的内容即可
在这里插入图片描述

2.7 前后台协议联调

环境准备

  • 导入提供好的前端页面,如果想自己写页面,也可以用element-ui,有空了考虑考虑

  • 由于添加了静态资源,SpringMVC会拦截,所以需要在对静态资源放行

    • 新建SpringMVCSupport类,继承WebMvcConfigurationSupport,并重写addResourceHandlers()方法
    @Configuration
    public class SpringMvcSupport extends WebMvcConfigurationSupport {
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    	}
    }
    
  • 同时也需要让SpringMvcConfig扫描到我们的配置类

    @Configuration
    @ComponentScan({"com.itheima.controller","com.itheima.config"})
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    

2.7.1 列表功能

需求:页面加载完后发送异步请求到后台获取列表数据进行展示

  1. 找到页面的钩子函数,created()
  2. created()方法中调用了this.getAll()方法
  3. 在getAll()方法中使用axios发送异步请求从后台获取数据
  4. 访问的路径为http://localhost/books
  5. 返回数据

那么修改getAll()方法

res.data表示获取的Result对象,而Result对象的data属性才是真正的数据
也就是将Rusult.data赋给了this.dataList

getAll() {
    axios.get("/books").then((res)=>{
        this.dataList = res.data.data;
    })
}

在钩子函数中直接调用getAll()即可

created() {
    this.getAll();
}

那么现在重启服务器,打开浏览器访问http://localhost:8080/pages/books.html,表格中可以正常显示数据了

2.7.2 添加功能

需求:完成图片的新增功能模块

  1. 找到页面上的新建按钮,按钮上绑定了@click="openSave()"方法
  2. 在method中找到openSave方法,方法中打开新增面板
  3. 新增面板中找到确定按钮,按钮上绑定了@click="saveBook()"方法
  4. 在method中找到saveBook方法
  5. 在方法中发送请求和数据,响应成功后将新增面板关闭并重新查询数据

openSave打开新增面板

openSave() {
    this.dialogFormVisible = true;
}

saveBook方法发送异步请求并携带数据

saveBook () {
    //发送ajax请求
    axios.post("/books",this.formData).then((res)=>{
        this.dialogFormVisible = false;
        this.getAll();
    });
}

2.7.3 添加功能状态处理

基础的新增功能已经完成,但是还有一些问题需要解决下:

需求:新增成功是关闭面板,重新查询数据,那么新增失败以后该如何处理?

  1. 在handlerAdd方法中根据后台返回的数据来进行不同的处理
  2. 如果后台返回的是成功,则提示成功信息,并关闭面板
  3. 如果后台返回的是失败,则提示错误信息
  • 修改前端页面

    saveBook() {
    axios.post("/books",this.formData).then((res)=>{
        //20011是成功的状态码,成功之后就关闭对话框,并显示添加成功
        if (res.data.code == 20011){
            this.dialogFormVisible = false;
            this.$message.success("添加成功")
        //20010是失败的状态码,失败后给用户提示信息
        }else if(res.data.code == 20010){
            this.$message.error("添加失败");
        //如果前两个都不满足,那就是SYSTEM_UNKNOW_ERR,未知异常了,显示未知异常的错误提示信息安抚用户情绪
        }else {
            this.$message.error(res.data.msg);
        }
    }).finally(()=>{
        this.getAll();
    	})
    }
    
  • 后台返回操作结果,将Dao层的增删改方法返回值从void改成int
    如果添加失败,int值为0,添加成功则int值为显示受影响的行数

    public interface BookDao {
    @Insert("insert into tbl_book values (null, #{type}, #{name}, #{description})")
    int save(Book book);
    
    @Update("update tbl_book set type=#{type}, `name`=#{name}, `description`=#{description} where id=#{id}")
    int update(Book book);
    
    @Delete("delete from tbl_book where id=#{id}")
    int delete(Integer id);
    
    @Select("select * from tbl_book where id=#{id}")
    Book getById(Integer id);
    
    @Select("select * from tbl_book")
    List<Book> getAll();
    }
    
  • 在BookServiceImpl中,增删改方法根据DAO的返回值来决定返回true/false
    如果受影响的行大于0,则添加成功,否则添加失败

    @Service
    public class BookServiceImpl implements BookService {
    
    @Autowired
    private BookDao bookDao;
    
    public boolean save(Book book) {
        return bookDao.save(book) > 0;
    }
    
    public boolean update(Book book) {
        return bookDao.update(book) > 0;
    }
    
    public boolean delete(Integer id) {
       return bookDao.delete(id) > 0;
    }
    
    public Book getById(Integer id) {
        return bookDao.getById(id);
    }
    
    public List<Book> getAll() {
        return bookDao.getAll();
    	}
    }
    

处理完新增后,会发现新增还存在一个问题,
新增成功后,再次点击新增按钮会发现之前的数据还存在,这个时候就需要在新增的时候将表单内容清空。vue:

// 重置表单
resetForm() {
    this.formData = {};
}

// 弹出添加窗口
openSave() {
    this.dialogFormVisible = true;
    //每次弹出表单的时候,都重置一下数据
    this.resetForm();
}

超过数据库设置的字段名限制,对此进行报错
在这里插入图片描述

2.7.4 修改功能

需求:完成图书信息的修改功能

  1. 找到页面中的编辑按钮,该按钮绑定了@click="openEdit(scope.row)"
  2. 在method的openEdit方法中发送异步请求根据ID查询图书信息
  3. 根据后台返回的结果,判断是否查询成功
    • 如果查询成功打开修改面板回显数据,如果失败提示错误信息
  4. 修改完成后找到修改面板的确定按钮,该按钮绑定了@click="handleEdit()"
  5. 在method的handleEdit方法中发送异步请求提交修改数据
  6. 根据后台返回的结果,判断是否修改成功
    • 如果成功提示错误信息,关闭修改面板,重新查询数据,如果失败提示错误信息

scope.row代表的是当前行的行数据,也就是说,scope.row就是选中行对应的json数据,如下:

{
    "id": 1,
    "type": "计算机理论",
    "name": "Spring实战 第五版",
    "description": "Spring入门经典教程,深入理解Spring原理技术内幕"
}
  • 修改openEdit()方法

    openEdit(row) {
    axios.get("/books/" + row.id).then((res) => {
        if (res.data.code == 20041) {
            this.formData = res.data.data;
            this.dialogFormVisible4Edit = true;
        } else {
            this.$message.error(res.data.msg);
        }
    });
    }
    
  • 修改handleUpdate方法

    //编辑
     handleEdit() {
      //发送ajax请求
     axios.put("/books",this.formData).then((res)=>{
      //如果操作成功,关闭弹层,显示数据
      if(res.data.code == 20021){//与后端设置的状态码有关
             this.dialogFormVisible4Edit = false;
               this.$message.success("修改成功");
       }else if(res.data.code == 20020){
                this.$message.error("修改失败");
          }else{
                 this.$message.error(res.data.msg);
             }
           }).finally(()=>{
                this.getAll();
            });
       },
    

2.7.5 删除功能

需求:完成页面的删除功能。

  1. 找到页面的删除按钮,按钮上绑定了@click="delete(scope.row)"
  2. method的delete方法弹出提示框
  3. 用户点击取消,提示操作已经被取消。
  4. 用户点击确定,发送异步请求并携带需要删除数据的主键ID
  5. 根据后台返回结果做不同的操作
    • 如果返回成功,提示成功信息,并重新查询数据
    • 如果​返回失败,提示错误信息,并重新查询数据
    • 修改delete方法
// 删除
                handleDelete(row) {
                    //1.弹出提示框
                    this.$confirm("此操作永久删除当前数据,是否继续?","提示",{
                        type:'info'
                    }).then(()=>{
                        //2.做删除业务
                        axios.delete("/books/"+row.id).then((res)=>{
                            if(res.data.code == 20031){
                                this.$message.success("删除成功");
                            }else{
                                this.$message.error("删除失败");
                            }
                        }).finally(()=>{
                            this.getAll();
                        });
                    }).catch(()=>{
                        //3.取消删除
                        this.$message.info("取消删除操作");
                    });
                }

至此增删改操作就都完成了,完整的前端代码如下:

<!DOCTYPE html>

<html>

    <head>

        <!-- 页面meta -->

        <meta charset="utf-8">

        <meta http-equiv="X-UA-Compatible" content="IE=edge">

        <title>SpringMVC案例</title>

        <meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">

        <!-- 引入样式 -->

        <link rel="stylesheet" href="../plugins/elementui/index.css">

        <link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min.css">

        <link rel="stylesheet" href="../css/style.css">

    </head>

    <body class="hold-transition">

        <div id="app">

            <div class="content-header">

                <h1>图书管理</h1>

            </div>

            <div class="app-container">

                <div class="box">

                    <div class="filter-container">

                        <el-input placeholder="图书名称" v-model="pagination.queryString" style="width: 200px;" class="filter-item"></el-input>

                        <el-button @click="getAll()" class="dalfBut">查询</el-button>

                        <el-button type="primary" class="butT" @click="handleCreate()">新建</el-button>

                    </div>

                    <el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row>

                        <el-table-column type="index" align="center" label="序号"></el-table-column>

                        <el-table-column prop="type" label="图书类别" align="center"></el-table-column>

                        <el-table-column prop="name" label="图书名称" align="center"></el-table-column>

                        <el-table-column prop="description" label="描述" align="center"></el-table-column>

                        <el-table-column label="操作" align="center">

                            <template slot-scope="scope">

                                <el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button>

                                <el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>

                            </template>

                        </el-table-column>

                    </el-table>

                    <!-- 新增标签弹层 -->

                    <div class="add-form">

                        <el-dialog title="新增图书" :visible.sync="dialogFormVisible">

                            <el-form ref="dataAddForm" :model="formData" :rules="rules" label-position="right" label-width="100px">

                                <el-row>

                                    <el-col :span="12">

                                        <el-form-item label="图书类别" prop="type">

                                            <el-input v-model="formData.type"/>

                                        </el-form-item>

                                    </el-col>

                                    <el-col :span="12">

                                        <el-form-item label="图书名称" prop="name">

                                            <el-input v-model="formData.name"/>

                                        </el-form-item>

                                    </el-col>

                                </el-row>


                                <el-row>

                                    <el-col :span="24">

                                        <el-form-item label="描述">

                                            <el-input v-model="formData.description" type="textarea"></el-input>

                                        </el-form-item>

                                    </el-col>

                                </el-row>

                            </el-form>

                            <div slot="footer" class="dialog-footer">

                                <el-button @click="dialogFormVisible = false">取消</el-button>

                                <el-button type="primary" @click="handleAdd()">确定</el-button>

                            </div>

                        </el-dialog>

                    </div>

                    <!-- 编辑标签弹层 -->

                    <div class="add-form">

                        <el-dialog title="编辑检查项" :visible.sync="dialogFormVisible4Edit">

                            <el-form ref="dataEditForm" :model="formData" :rules="rules" label-position="right" label-width="100px">

                                <el-row>

                                    <el-col :span="12">

                                        <el-form-item label="图书类别" prop="type">

                                            <el-input v-model="formData.type"/>

                                        </el-form-item>

                                    </el-col>

                                    <el-col :span="12">

                                        <el-form-item label="图书名称" prop="name">

                                            <el-input v-model="formData.name"/>

                                        </el-form-item>

                                    </el-col>

                                </el-row>

                                <el-row>

                                    <el-col :span="24">

                                        <el-form-item label="描述">

                                            <el-input v-model="formData.description" type="textarea"></el-input>

                                        </el-form-item>

                                    </el-col>

                                </el-row>

                            </el-form>

                            <div slot="footer" class="dialog-footer">

                                <el-button @click="dialogFormVisible4Edit = false">取消</el-button>

                                <el-button type="primary" @click="handleEdit()">确定</el-button>

                            </div>

                        </el-dialog>

                    </div>

                </div>

            </div>

        </div>

    </body>

    <!-- 引入组件库 -->

    <script src="../js/vue.js"></script>

    <script src="../plugins/elementui/index.js"></script>

    <script type="text/javascript" src="../js/jquery.min.js"></script>

    <script src="../js/axios-0.18.0.js"></script>

    <script>
        var vue = new Vue({

            el: '#app',
            data:{
                pagination: {},
				dataList: [],//当前页要展示的列表数据
                formData: {},//表单数据
                dialogFormVisible: false,//控制表单是否可见
                dialogFormVisible4Edit:false,//编辑表单是否可见
                rules: {//校验规则
                    type: [{ required: true, message: '图书类别为必填项', trigger: 'blur' }],
                    name: [{ required: true, message: '图书名称为必填项', trigger: 'blur' }]
                }
            },

            //钩子函数,VUE对象初始化完成后自动执行
            created() {
                this.getAll();
            },

            methods: {
                //列表
                getAll() {
                    //发送ajax请求
                    axios.get("/books").then((res)=>{
                        this.dataList = res.data.data;
                    });
                },

                //弹出添加窗口
                handleCreate() {
                    this.dialogFormVisible = true;
                    this.resetForm();
                },

                //重置表单
                resetForm() {
                    this.formData = {};
                },

                //添加
                handleAdd () {
                    //发送ajax请求
                    axios.post("/books",this.formData).then((res)=>{
                        // console.log(res.data);
                        //如果操作成功,关闭弹层,显示数据
                        if(res.data.code == 20011){
                            this.dialogFormVisible = false;
                            this.$message.success("添加成功");
                        }else if(res.data.code == 20010){
                            this.$message.error("添加失败");
                        }else{
                            this.$message.error(res.data.msg);
                        }
                    }).finally(()=>{
                        this.getAll();
                    });
                },

                //弹出编辑窗口
                handleUpdate(row) {
                    // console.log(row);   //row.id 查询条件
                    //查询数据,根据id查询
                    axios.get("/books/"+row.id).then((res)=>{
                        // console.log(res.data.data);
                        if(res.data.code == 20041){
                            //展示弹层,加载数据
                            this.formData = res.data.data;
                            this.dialogFormVisible4Edit = true;
                        }else{
                            this.$message.error(res.data.msg);
                        }
                    });
                },

                //编辑
                handleEdit() {
                    //发送ajax请求
                    axios.put("/books",this.formData).then((res)=>{
                        //如果操作成功,关闭弹层,显示数据
                        if(res.data.code == 20021){//与后端设置的状态码有关
                            this.dialogFormVisible4Edit = false;
                            this.$message.success("修改成功");
                        }else if(res.data.code == 20020){
                            this.$message.error("修改失败");
                        }else{
                            this.$message.error(res.data.msg);
                        }
                    }).finally(()=>{
                        this.getAll();
                    });
                },

                // 删除
                handleDelete(row) {
                    //1.弹出提示框
                    this.$confirm("此操作永久删除当前数据,是否继续?","提示",{
                        type:'info'
                    }).then(()=>{
                        //2.做删除业务
                        axios.delete("/books/"+row.id).then((res)=>{
                            if(res.data.code == 20031){
                                this.$message.success("删除成功");
                            }else{
                                this.$message.error("删除失败");
                            }
                        }).finally(()=>{
                            this.getAll();
                        });
                    }).catch(()=>{
                        //3.取消删除
                        this.$message.info("取消删除操作");
                    });
                }
            }
        })

    </script>

</html>

总结:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1231951.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DBeaver连接本地MySQL

原文&#xff1a; DBeaver21.3.0安装与连接本地MySQL_dbeaver创建本地数据库_傅大胖的博客-CSDN博客 其他&#xff1a; mysql 的驱动下载地址&#xff1a; Central Repository: mysql/mysql-connector-java ​​​​​​​

江湖再见,机器视觉兄弟们,我已经提离职了,聪明的机器视觉工程师,离职不亏本!

我闻江湖已叹息&#xff0c;又闻人间繁闹闹。同为布衣沦落人&#xff0c;相逢何必曾相识。 此生谁料事事休&#xff0c;道不尽人情冷暖&#xff0c;聚散离合总平常&#xff0c;不似勇气少年时。 我估计今年公司年底是发不出工资了&#xff0c;因为订单续不上。年终奖更是没有&…

Element Plus框架快速上手详解(一)

Element Plus框架快速上手详解 1、Element Plus1.1、安装 2、Button3、Link链接4、Layout布局5、Container布局容器6、Radio单选框6.1、单选框组6.2、事件 7、Checkbox多选框7.1、多选框组7.2、事件 8、Input输入框组件8.1、事件8.2、方法 9、Select选择器9.1、基础多选9.2、事…

机器学习二元分类 二元交叉熵 二元分类例子

二元交叉熵损失函数 深度学习中的二元分类损失函数通常采用二元交叉熵&#xff08;Binary Cross-Entropy&#xff09;作为损失函数。 二元交叉熵损失函数的基本公式是&#xff1a; L(y, y_pred) -y * log(y_pred) - (1 - y) * log(1 - y_pred)其中&#xff0c;y是真实标签&…

HarmonyOS开发(四):UIAbility组件

1、UIAbility概述 UIAbility 一种包含用户界面的应用组件用于与用户进行交互系统调度的单元为应用提供窗口在其中绘制界同 注&#xff1a;每一个UIAbility实例&#xff0c;都对应一个最近任务列表中的任务。 一个应用可以有一个UIAbility也可以有多个UIAbility。 如一般的…

【算法】二分查找-20231121

这里写目录标题 一、344. 反转字符串二、392. 判断子序列三、581. 最短无序连续子数组四、680. 验证回文串 II 一、344. 反转字符串 提示 简单 865 相关企业 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组…

(动手学习深度学习)第13章 实战kaggle竞赛:CIFAR-10

导入相关库 import collections import math import os import shutil import pandas as pd import torch import torchvision from torch import nn from d2l import torch as d2l下载数据集 d2l.DATA_HUB[cifar10_tiny] (d2l.DATA_URL kaggle_cifar10_tiny.zip,2068874e4…

内存学习(4):内存分类与常用概念3(ROM)

1 ROM介绍 ROM即为只读存储器&#xff0c;全拼是Read Only Memory。 1.1 “只读”的由来 ROM叫只读存储器是因为最早的ROM&#xff08;MROM&#xff09;确实是只能读取不能写入&#xff0c;一旦出厂不能再写&#xff0c;需要在出厂之前预设好它的数据&#xff0c;并且它是掉…

华为---OSPF网络虚连接(Virtual Link)简介及示例配置

OSPF网络虚连接&#xff08;Virtual Link&#xff09;简介 为了避免区域间的环路&#xff0c;OSPF规定不允许直接在两个非骨干区域之间发布路由信息&#xff0c;只允许在一个区域内部或者在骨干区域和非骨干区域之间发布路由信息。因此&#xff0c;每个ABR都必须连接到骨干区域…

Fourier分析导论——第6章——R^d 上的Fourier变换(E.M. Stein R. Shakarchi)

第6章 上的 Fourier 变换 It occurred to me that in order to improve treatment planning one had to know the distribution of the at- tenuation coefficient of tissues in the body. This in- formation would be useful for diagnostic purposes and would con…

[github配置] 远程访问仓库以及问题解决

作者&#xff1a;20岁爱吃必胜客&#xff08;坤制作人&#xff09;&#xff0c;近十年开发经验, 跨域学习者&#xff0c;目前于新西兰奥克兰大学攻读IT硕士学位。荣誉&#xff1a;阿里云博客专家认证、腾讯开发者社区优质创作者&#xff0c;在CTF省赛校赛多次取得好成绩。跨领域…

密码加密解密之路

1.背景 做数据采集&#xff0c;客户需要把他们那边的数据库连接信息存到我们系统里&#xff0c;那我们系统就要尽可能的保证这部分数据安全&#xff0c;不被盗。 2.我的思路 1.需要加密的地方有两处&#xff0c;一个是新增的时候前端传给后端的时候&#xff0c;一个是存到数…

浅析RSA非对称加密算法

目录 引言 凯撒密码 对称加密 非对称加密 ​编辑总结 引言 几月前在知乎上看到一个关于RSA公钥与私钥加解密的提问甚感兴趣&#xff0c;却一直没有时间去探究&#xff0c;今日浅得闲时以文记之。 在文章正式开始之前先讲一个小故事&#xff0c;在公元前58年时&#xff0c…

css 实现文字流光效果

经过调研发现大多滑块验证码中&#xff0c;有一些文字流光效果&#xff0c;因此在这里简单实现一下。 实现主要利用background 渐变背景以及backgorund-clip:text实现。具体代码如下 css部分 .slide {width: 300px;height: 40px;border: 1px solid #ccc;border-radius: 8px;…

猫12分类:使用yolov5训练检测模型

前言&#xff1a; 在使用yolov5之前&#xff0c;尝试过到百度飞桨平台&#xff08;小白不建议&#xff09;、AutoDL平台&#xff08;这个比较友好&#xff0c;经济实惠&#xff09;训练模型。但还是没有本地训练模型来的舒服。因此远程了一台学校电脑来搭建自己的检测模型。配置…

【鸿蒙最新全套教程】<HarmonyOS第一课>1、运行Hello World

下载与安装DevEco Studio 在HarmonyOS应用开发学习之前&#xff0c;需要进行一些准备工作&#xff0c;首先需要完成开发工具DevEco Studio的下载与安装以及环境配置。 进入DevEco Studio下载官网&#xff0c;单击“立即下载”进入下载页面。 DevEco Studio提供了Windows版本和…

pyQt主界面与子界面切换简易框架

本篇来介绍使用python中是Qt功能包&#xff0c;设置一个简易的多界面切换框架&#xff0c;实现主界面和多个子界面直接的切换显示。 1 主界面 设计的Demo主界面如下&#xff0c;主界面上有两个按钮图标&#xff0c;点击即可切换到对应的功能界面中&#xff0c;进入子界面后&a…

猫12分类:使用多线程爬取图片的Python程序

本文目标 对于猫12目标检测部分的数据集&#xff0c;采用网络爬虫来制作数据集。 在网络爬虫中&#xff0c;经常需要下载大量的图片。为了提高下载效率&#xff0c;可以使用多线程来并发地下载图片。本文将介绍如何使用Python编写一个多线程爬虫程序&#xff0c;用于爬取图片…

飞翔的小鸟

运行游戏如下&#xff1a; 碰到柱子就结束游戏 App GameApp类 package App;import main.GameFrame;public class GameApp {public static void main(String[] args) {//游戏的入口new GameFrame();} } main Barrier 类 package main;import util.Constant; import util.Ga…

Linux--网络编程

一、网络编程概述1.进程间通信&#xff1a; 1&#xff09;进程间通信的方式有**&#xff1a;管道&#xff0c;消息队列&#xff0c;共享内存&#xff0c;信号&#xff0c;信号量这么集中 2&#xff09;特点&#xff1a;依赖于linux内核&#xff0c;基本是通过内核来实现应用层…