小黑子的SSM整合

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>

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

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

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

外观模式 rust和java的实现

外观模式 外观模式&#xff08;Facade Pattern&#xff09;隐藏系统的复杂性&#xff0c;并向客户端提供了一个客户端可以访问系统的接口。它向现有的系统添加一个接口&#xff0c;来隐藏系统的复杂性。 举个例子 &#xff1a;就像电脑的usb接口&#xff0c;自己内部实现了复杂…

怎么在echarts图上左右滑动切换数据区间

说在前面 不管前端还是后端&#xff0c;大家或多或少都了解使用过echarts图表吧&#xff0c;很多时候我们只是需要展示指定区间的数据&#xff0c;但有时我们希望在图表上能够轻松地切换数据的展示区间&#xff0c;以便更清晰地观察特定时间段或区域的变化。在本文中&#xff0…

浅析RSA非对称加密算法

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

表内容的操作(增删查改)【MySQL】

文章目录 表的 CRUDCreate&#xff08;增加&#xff09;插入记录插入冲突则更新记录替换记录 Retrieve&#xff08;查找&#xff09;查找记录指定表达式的别名为结果去重WHERE 子句运算符条件查询区间查询模糊查询空值查询 对结果排序筛选分页结果 Update&#xff08;修改&…

面试题c/c++ --STL 算法与数据结构

1.6 STL 模板 模板底层实现&#xff1a;编译器会对函数模板进行两次编译&#xff0c; 在声明的地方对模板代码本身进行编译&#xff0c; 在调用的地方对参数替换后的代码进行编译。 模板传参分析 模板重载 vector 是动态空间&#xff0c; 随着元素的加入&#xff0c; 它的内…

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

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

Apache Airflow (十一) :HiveOperator及调度HQL

&#x1f3e1; 个人主页&#xff1a;IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 &#x1f6a9; 私聊博主&#xff1a;加入大数据技术讨论群聊&#xff0c;获取更多大数据资料。 &#x1f514; 博主个人B栈地址&#xff1a;豹哥教你大数据的个人空间-豹…

cpu飙高问题,案例分析(一)

一、复习知识点&#xff1a; CPU性能指标&#xff1a; load average&#xff1a;负载&#xff0c;linux查看的时候&#xff0c;通常显示如下&#xff1a; load average后面有三段数字&#xff1a;代表了系统1分钟&#xff0c;5分钟&#xff0c;15分钟平均负载。 形象的类别可…

【每日刷题——语音信号篇】

思考与练习 练习2.1 语音信号在产生的过程中&#xff0c;以及被感知的过程中&#xff0c;分别要经过人体的哪些器官&#xff1f; 1.产生过程&#xff1a; 肺部空气 → \rightarrow →冲击声带 → \rightarrow →通过声道&#xff08;可以调节&#xff09; → \rightarrow →…

【ArcGIS Pro微课1000例】0033:ArcGIS Pro处理cad数据(格式转换、投影变换)

文章目录 一、cad dwg转shp1. 导出为shp2. cad至地理数据库3. data interoperability tools二、shp投影变换一、cad dwg转shp 1. 导出为shp 加载cad数据,显示如下: 选择需要导出的数据,如面状,右键→数据→导出要素: 导出要素参数如下,点击确定。 导出的要素不带空间参…

el-table 对循环产生的空白列赋默认值

1. el-table 空白列赋值 对el-table中未传数据存在空白的列赋默认值0。使用el-table 提供的插槽 slot-scope&#xff1a;{{ row || ‘0’ }} 原数据&#xff1a; <el-table-column label"集镇" :propcity ><template slot-scope"{row}">{{…

Linux 命令补充

目录 tr 命令 命令举例 cut 命令 命令举例 uniq 命令 命令举例 sort 命令 命令举例 面试题 1. 给你一个文件如何提取前 10 的 IP 2. 如何提前 ss 中的状态 tr 命令 作用tr转换tr -d删除tr -c取反tr -s压缩 命令举例 cut 命令 作用cut提取cut -f指定列cut -d指定分…

MyBatis的xml实现

1.下载插件MyBatisX 2.添加依赖 <!--Mybatis 依赖包--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.1</version></dependency><!--…

人工智能给我们的生活带来了巨大的影响?

1. 人工智能从哪些方面给我们带来了影响&#xff1f; 人工智能出现&#xff0c;极大地影响了人类的生活&#xff0c;下面是人工智能所影响的领域&#xff1a; 1. 日常生活 智能家居: AI驱动的设备&#xff0c;如智能扬声器、灯光、恒温器&#xff0c;正在改变我们与家居环境的…

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

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

.NET 8.0 AOT 教程 和使用 和 .NET ORM 操作

NET AOT编译是一种.NET运行时的编译方式&#xff0c;它与传统的JIT编译方式不同。在传统的JIT编译中&#xff0c;.NET应用程序的代码在运行时才会被编译成本地机器码&#xff0c;而在AOT编译中&#xff0c;代码在运行之前就被提前编译成本地机器码。这样可以在代码运行的时候不…

Docker搭建Redis集群

Docker搭建Redis集群 创建一个专属redis的网络 docker network create redis --subnet 172.38.0.0/16通过shell脚本创建并启动6个redis服务 #通过脚本一次创建6个redis配置 for port in $(seq 1 6); \ do \ mkdir -p /mydata/redis/node-${port}/conf touch /mydata/redis/n…

CentOS7安装部署Kafka with KRaft

文章目录 CentOS7安装部署Kafka with KRaft一、前言1.简介2.架构3.环境 二、正文1.部署服务器2.基础环境1&#xff09;主机名2&#xff09;Hosts文件3&#xff09;关闭防火墙4&#xff09;JDK 安装部署 3.单机部署1&#xff09;下载软件包2&#xff09;修改配置文件3&#xff0…

VS2019编译安装GDAL(C++)程序库

一、GDAL简介 GDAL&#xff0c;全称Geospatial Data Abstraction Library&#xff0c;即地理空间数据抽象库&#xff0c;是一个在X/MIT许可协议下读写空间数据的开源库&#xff0c;可以通过命令行工具来进行数据的转换和处理。而在调用中我们常用的OGR&#xff08;OpenGIS Simp…