小黑子的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[]{"/"};
    }//乱码处理@Overrideprotected 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 {//设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载@Overrideprotected 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-paginationclass="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 {
    @Autowiredprivate 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.mzph.cn/news/152603.shtml

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

相关文章

.Net6 Api Swagger配置

1、定义个Swagger版本&#xff08;组&#xff09;的枚举 namespace WebApp.Enums {/// <summary>/// api版本枚举/// </summary>public enum ApiVersion{/// <summary>/// v1版本/// </summary>v1 1,/// <summary>/// v2版本/// </summary&…

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

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

十四、Docker的基本操作

目录 &#xff08;一&#xff09;镜像命令 一、拉取Nginx 二、查看镜像 三、导出文件 四、删除镜像 五、加载镜像 &#xff08;二&#xff09;容器命令 一、例子&#xff1a;运行一个nginx容器 1、输入运行命令 2、使用命令查看宿主机ip 3、在外部浏览器访问 4、查看…

【机器学习】037_暂退法

一、实现原理 具有输入噪音的训练&#xff0c;等价于Tikhonov正则化 核心方法&#xff1a;在前向传播的过程中&#xff0c;计算每一内部层的同时注入噪声 从作用上来看&#xff0c;表面上来说是在训练过程中丢弃一些神经元 假设x是某一层神经网络层的输出&#xff0c;是下一…

【机器学习】036_权重衰退

一、范数 定义&#xff1a;向量的范数表示一个向量有多大&#xff08;分量的大小&#xff09; L1范数&#xff1a; 即向量元素绝对值之和&#xff0c;用符号 ‖ v ‖ 1 表示。 公式&#xff1a; L2范数&#xff1a; 即向量的模&#xff0c;向量各元素绝对值的平方之和再…

适合您的智能手机的 7 款优秀手机数据恢复软件分享

如今&#xff0c;我们做什么都用手机&#xff1b;从拍照到录音&#xff0c;甚至作为 MP3 播放器&#xff0c;我们已经对手机变得非常依恋。这导致我们在手机上留下了很多珍贵的回忆。 不幸的是&#xff0c;我们有可能会丢失手机上的部分甚至全部数据。幸运的是&#xff0c;这不…

1. hadoop环境准备

环境准备 准备三台虚拟机&#xff0c;配置最好是 2C 4G 以上 本文准备三台机器的内网ip分别为 172.17.0.10 172.17.0.11 172.17.0.12本机配置/etc/hosts cat >> /etc/hosts<<EOF 172.17.0.10 hadoop01 172.17.0.11 hadoop02 172.17.0.12 hadoop03 EOF本机设置与…

队列的实现和OJ练习

目录 概念 队列的实现 利用结构体存放队列结构 为什么单链表不使用这种方法&#xff1f; 初始化队列 小提示&#xff1a; 队尾入队列 队头出队列 获取队头元素 获取队尾元素 获取队列中有效元素个数 检测队列是否为空 销毁队列 最终代码 循环队列 队列的OJ题 …

MobaXterm如何连接CentOS7的Linux虚拟机?Redis可视化客户端工具如何连接Linux版Redis?

一、打开Lunix虚拟机,进入虚拟机中,在终端中输入ifconfig,得到以下信息&#xff0c;红框中为ip地址 二、打开MobaXterm&#xff0c;点击session 选择SSH&#xff0c;在Remote host中输入linux得到的IP地址&#xff0c;Specify username中可起一个任意的连接名称。 输入密码 四、…

【洛谷 P3743】kotori的设备 题解(二分答案+递归)

kotori的设备 题目背景 kotori 有 n n n 个可同时使用的设备。 题目描述 第 i i i 个设备每秒消耗 a i a_i ai​ 个单位能量。能量的使用是连续的&#xff0c;也就是说能量不是某时刻突然消耗的&#xff0c;而是匀速消耗。也就是说&#xff0c;对于任意实数&#xff0c;…

60 权限提升-MYMSORA等SQL数据库提权

目录 数据库应用提权在权限提升中的意义WEB或本地环境如何探针数据库应用数据库提权权限用户密码收集等方法目前数据库提权对应的技术及方法等 演示案例Mysql数据库提权演示-脚本&MSF1.UDF提权知识点: (基于MYSQL调用命令执行函数&#xff09;读取数据库存储或备份文件 (了…

GaussDB新特性Ustore存储引擎介绍

1、 Ustore和Astore存储引擎介绍 Ustore存储引擎&#xff0c;又名In-place Update存储引擎&#xff08;原地更新&#xff09;&#xff0c;是openGauss 内核新增的一种存储模式。此前的版本使用的行存储引擎是Append Update&#xff08;追加更新&#xff09;模式。相比于Append…

在网络攻击之前、期间和之后应采取的步骤

在当今复杂的威胁形势下&#xff0c;网络攻击是不可避免的。 恶意行为者变得越来越复杂&#xff0c;出于经济动机的攻击变得越来越普遍&#xff0c;并且每天都会发现新的恶意软件系列。 这使得对于各种规模和跨行业的组织来说&#xff0c;制定适当的攻击计划变得更加重要。 …

【Linux】进程间通信 -- 管道

对于进程间通信的理解 首先&#xff0c;进程间通信的本质是&#xff0c;让不同的进程看到同一份资源&#xff08;这份资源不能隶属于任何一个进程&#xff0c;即应该是共享的&#xff09;。而进程间通信的目的是为了实现多进程之间的协同。 但由于进程运行具有独立性&#xff…

密码加密解密之路

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

异步爬取+多线程+redis构建一个运转丝滑且免费http-ip代理池 (三)

内容提要: 如果说,爬取网页数据的时候,我们使用了异步,那么将数据放入redis里面,其实也需要进行异步;当然,如果使用多线程或者redis线程池技术也是可以的,但那会造成冗余; 因此,在测试完多线程redis搭配异步爬虫的时候,我发现效率直接在redis这里被无限拉低下来! 因此: 最终的r…

从0开始学习JavaScript--JavaScript中的集合类

JavaScript中的集合类是处理数据的关键&#xff0c;涵盖了数组、Set、Map等多种数据结构。本文将深入研究这些集合类的创建、操作&#xff0c;以及实际应用场景&#xff0c;并通过丰富的示例代码&#xff0c;帮助大家更全面地了解和应用这些概念。 数组&#xff08;Array&…

SystemVerilog学习 (11)——覆盖率

目录 一、概述 二、覆盖率的种类 1、概述 2、分类 三、代码覆盖率 四、功能覆盖率 五、从功能描述到覆盖率 一、概述 “验证如果没有量化&#xff0c;那么就意味着没有尽头。” 伴随着复杂SoC系统的验证难度系数成倍增加&#xff0c;无论是定向测试还是随机测试&#xff…

安全框架springSecurity+Jwt+Vue-1(vue环境搭建、动态路由、动态标签页)

一、安装vue环境&#xff0c;并新建Vue项目 ①&#xff1a;安装node.js 官网(https://nodejs.org/zh-cn/) 2.安装完成之后检查下版本信息&#xff1a; ②&#xff1a;创建vue项目 1.接下来&#xff0c;我们安装vue的环境 # 安装淘宝npm npm install -g cnpm --registryhttps:/…

软件测试/测试开发/人工智能丨基于Spark的分布式造数工具:加速大规模测试数据构建

随着软件开发规模的扩大&#xff0c;测试数据的构建变得越来越复杂&#xff0c;传统的造数方法难以应对大规模数据需求。本文将介绍如何使用Apache Spark构建分布式造数工具&#xff0c;以提升测试数据构建的效率和规模。 为什么选择Spark&#xff1f; 分布式计算&#xff1a;…