image.png开山篇

开山篇.wmv

  1. 了解 基于springboot官网创建项目,并导入运行 https://spring.io/projects/spring-boot
  2. image.png
  3. 通过idea联网版创建springboot项目,web项目,并启动注意 parent�使用2.7.1

  4. 基于阿里云创建项目 https://start.aliyun.com

阿里云创建.wmv
image.png

  1. 手动创建SpringBoot项目,创建maven工程并导入springboot依赖,创建引导类和处理器启动即可

手动创建.wmv

  1. 文件隐藏

文件排除.wmv
image.png

parent和start的理解.wmv

  1. 解析spring-boot-starter-parent,主要使用来做统一的jar包版本管理,版本仲裁

  2. 开发SpringBoot程序要继承spring-boot-starter-parent
    2. spring-boot-starter-parent中定义了若干个依赖管理
    3. 继承parent模块可以避免多个依赖使用相同技术时出现依赖版本冲突
    4. 继承parent的形式也可以采用引入依赖的形式实现效果

    1. 解析starter快速启动器
      spring-boot-starter-xxx
      按照功能不同会有不同功能的starter,我们可以把starter理解成一个集成了多个依赖的pom文件一个封装好的依赖集合。
      1. 开发SpringBoot程序需要导入坐标时通常导入对应的starter
      2. 每个不同的starter根据功能不同,通常包含多个依赖坐标
      3. 使用starter可以实现快速配置的效果,达到简化配置的目的

parent的目的是避免版本冲突starter的目的是简化依赖配置
8.解析引导类,其实就是通过SpringApplication.run(Application.class, args);启动容器,扫描引导类所在包加载bean

9.解析内嵌服务器,tomcat,jetty

  1. <dependencies>
  2. <dependency>
  3. <groupid>org.springframework.boot</groupid>
  4. <artifactid>spring-boot-starter-web</artifactid>
  5. <exclusions>
  6. <!-- 去除Tomcat容器 -->
  7. <exclusion>
  8. <groupid>org.springframework.boot</groupid>
  9. <artifactid>spring-boot-starter-tomcat</artifactid>
  10. </exclusion>
  11. </exclusions>
  12. </dependency>
  13. <!-- 增加Jetty容器 -->
  14. <dependency>
  15. <groupid>org.springframework.boot</groupid>
  16. <artifactid>spring-boot-starter-jetty</artifactid>
  17. </dependency>
  18. </dependencies>

配置

1.复制模块,因为练习需要大量的模块,所以复制工程

原则

保留工程基础结构
抹掉原始工程痕迹
1. 在工作空间中复制对应工程,并修改工程名称
2. 删除与Idea相关配置文件,仅保留src目录与pom.xml文件
3. 修改pom.xml文件中的artifactId与新工程/模块名相同
4. 删除name标签(可选)
5. 保留备份工程供后期使用

2.基础配置

properties配置和yaml的使用方式.wmv
使用properties的方式修改端口,修改banner,日志,缺点是代码可读性低,配置冗长
然后引入yml,yaml的方式配置,以及三种配置并存的优先级:properties > yml > yaml
以及yaml提示不生效的原因:是因为idea不认为他是一个配置文件,在项目结构中进行配置即可
b31477f1bac957aaa9bce1f099dc2b86.rar
image.png

3.yml的使用

yaml数据格式

yml中的数据格式.wmv
YAML(YAML Ain’t Markup Language),一种数据序列化格式
优点:
容易阅读
容易与脚本语言交互
数据为核心重数据轻格式
YAML文件扩展名
.yml(主流)
.yaml
yaml语法规则
大小写敏感属性
层级关系使用多行描述,每行结尾使用冒号结束
使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
# 表示注释
核心规则:数据前面要加空格与冒号隔开

  1. #单一属性
  2. name: gouwa
  3. age: 18
  4. #对象
  5. user:
  6. name: goudan
  7. age: 18
  8. #数组
  9. like:
  10. - 抽烟
  11. - 喝酒
  12. - 烫头
  13. #数组缩略写法
  14. like2: [抽烟,喝酒,烫头]
  15. #对象数组
  16. users:
  17. - name: gousheng
  18. age: 18
  19. - name: goudan
  20. age: 20
  21. #对象数组 格式2
  22. users2:
  23. -
  24. name: gousheng
  25. age: 18
  26. -
  27. name: goudan
  28. age: 20
  29. #对象数组缩略格式
  30. user3: [{name: gouwa,age: 18},{name: goudan,age: 18}]

读取数据

yaml属性的读取方式.wmv
使用@Value读取单个数据,属性名引用方式:${一级属性名.二级属性名……}
1. 使用@Value配合SpEL读取单个数据
2. 如果数据存在多层级,依次书写层级名称即可

  1. @Value("${name}")
  2. private String name;
  3. @Value("${user.age}")
  4. private String age;
  5. @Value("${like[1]}")
  6. private String like;
  7. @Value("${users[1].name}")
  8. private String name2;
  9. @RequestMapping("/value")
  10. public String getValue(){
  11. return "name:"+name+",age:"+age+",like:"+like+",name2:"+name2;
  12. }

引用变量

引用变量.wmv

  1. center:
  2. dataDir: /usr/local/fire/data
  3. tmpDir: /usr/local/fire/tmp
  4. logDir: /usr/local/fire/log
  5. msgDir: /usr/local/fire/msgDir

如上👆🏻:如果配置中值出现大量重复的内容,则可以把他们提取成一个变量进行引用,如下:↓

  1. basedir: /usr/local/fire
  2. center:
  3. dataDir: ${basedir}/data
  4. tmpDir: ${basedir}/tmp
  5. logDir: ${basedir}/log
  6. msgDir: ${basedir}/msgDir

�如果出现转义字符可以使用双引号包裹进行解析
转义字符的解析方式.wmv

  1. basedir: /usr/local/fire
  2. center:
  3. dataDir: ${basedir}/data
  4. tmpDir: "${basedir}/tmp \t1 \t2 \t3"
  5. logDir: ${basedir}/log
  6. msgDir: ${basedir}/msgDir
  1. @Value("${center.tmpDir}")
  2. private String tmpDir;
  3. @RequestMapping("/value")
  4. public String getValue(){
  5. System.out.println("tmpDir:"+tmpDir);
  6. return "name:"+name+",age:"+age+",like:"+like+",name2:"+name2;
  7. }

image.png

读取yaml全部属性数据

目前来说上一章引用变量的方式存在明显的痛点,因为如果想要引用一个属性就需要创建一个变量,随着属性的增多代码量也会增多
image.png
可以 1. 使用Environment对象封装全部配置信息 2. 使用@Autowired自动装配数据到Environment对象中
Environment.wmv
image.png

  1. @Autowired
  2. private Environment en;
  3. @RequestMapping("/en")
  4. public String getEn(){
  5. System.out.println(en.getProperty("user.name"));
  6. System.out.println(en.getProperty("like[0]"));
  7. System.out.println(en.getProperty("user3[0].name"));
  8. return null;
  9. }

image.png :::info 确实解决了变量过多的问题,但是我们会发现用起来其实并不简单,在实际的开发中也是SpringBoot内部引入配置属性的主流方式其实是把一组数据封装到一个对象中,诸君请往下看↓ :::

重点自定义对象封装指定数据 主流方式

1.定义一组数据

  1. datasource:
  2. driver-class-name: com.mysql.cj.jdbc.Driver
  3. url: jdbc:mysql://localhost:3306/xuezhi?serverTimezone=UTC
  4. username: root
  5. password: root

�2.创建一个类,用于封装上面的这组数据
@ConfigurationProperties.wmv :::info 1.使用@ConfigurationProperties注解指定前缀绑定配置信息到封装类中,注意数据的名字要和类的属性名一致
2.封装类需要定义为Spring管理的bean(@Component),否则无法进行属性注入 :::

  1. package com.xuezhi.pojo;
  2. import org.springframework.boot.context.properties.ConfigurationProperties;
  3. import org.springframework.stereotype.Component;
  4. // 定义数据模型封装yaml中的对应数据
  5. // 定义为让Spring管控的Bean
  6. @Component
  7. // 指定加载的数据
  8. @ConfigurationProperties(prefix = "datasource")
  9. public class DataSource {
  10. private String driverClassName;
  11. private String url;
  12. private String userName;
  13. private String password;
  14. public String getDriverClassName() {
  15. return driverClassName;
  16. }
  17. public void setDriverClassName(String driverClassName) {
  18. this.driverClassName = driverClassName;
  19. }
  20. public String getUrl() {
  21. return url;
  22. }
  23. public void setUrl(String url) {
  24. this.url = url;
  25. }
  26. public String getUserName() {
  27. return userName;
  28. }
  29. public void setUserName(String userName) {
  30. this.userName = userName;
  31. }
  32. public String getPassword() {
  33. return password;
  34. }
  35. public void setPassword(String password) {
  36. this.password = password;
  37. }
  38. @Override
  39. public String toString() {
  40. return "DataSource{" +
  41. "driverClassName='" + driverClassName + '\'' +
  42. ", url='" + url + '\'' +
  43. ", userName='" + userName + '\'' +
  44. ", password='" + password + '\'' +
  45. '}';
  46. }
  47. }

�测试:

  1. @Autowired
  2. private DataSource dataSource;
  3. @RequestMapping("/db")
  4. public String getDb(){
  5. System.out.println(dataSource);
  6. return null;
  7. }

:::info 总结:
1.我们要想封装局部数据,首先需要提供一组对象格式的数据
2.然后提供一个用来封装的模型类,这个模型类通过@ConfigurationProperties注解指定封装的是那一组数据,把前缀告诉他就行了
3.我们的数据是由springboot读取的,那么模型类也需要交给Spring管控,使用@Component注解SpringBoot会自动把模型类扫描进Spring容器
这种数据封装的方式应用场景非常多,后面我们会定义格式各样的数据,交给SpringBoot框架中的技术使用,这些技术本身有一些配置信息,我们想改就可以把这些配置信息数据定义出来,他就会读取成为一个对象去使用,这也是为什么我们在yaml中配置一个东西在一些技术上就生效了的原因。其实就是通过这种形式把我们定义的一组一组的配置,加载成了一个又一个的对象,提供给一个又一个的技术去使用的。 :::

整合第三方技术

我们要学习的是整合思想,其中整合JUnit是最简单的整合方式,通过学习这四种技术的整合方式,我们一通百通

整合JUnit

整合Junit.wmv
直接创建一个maven工程进行测试即可,测试所需的所有东西都自动配置好了
image.png
什么技术都不需要勾选,直接点击下一步
image.pngimage.png
创建用于测试的内容
image.png
在测试类中,通过自动装配注入测试对象,进行测试即可
image.png :::info springboot整合jUnit主要是通过@SpringBootTest注解,比起原始Spring的方式方便很多,具体步骤如下:
1. 导入测试对应的starter
2. 测试类使用@SpringBootTest修饰
3. 使用自动装配的形式添加要测试的对象 :::


:::info 注意:
1. 测试类如果存在于引导类所在包或子包中无需指定引导类
2. 测试类如果不存在于引导类所在的包或子包中需要通过classes 属性指定引导类 ::: 比如我们把测试类提到com包下,而不放在和引导类所在的com.xuezhi包下
image.png
那么这时他就会抛出异常:

  1. java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test

这个异常的意思是在当前包下没有找到使用@SpringBootConfiguration修饰的类(其实就是引导类,引导类使用了@SpringBootConfiguration修饰),我们的Spring容器要通过启动引导类生成,找不到Spring容器,就导致了自动装配失败,所以报错,异常信息往后看可以看到它提示我们使用@ContextConfiguration或者@SpringBootTest(classes=…)指定引导类解决该问题。那么我们只需要如下: :::info 通过@SpringBootTest的classes属性手动指定引导类,从而加载Spring容器 ::: image.png :::info 或者如原始Spring一样使用@ContextConfiguration手动指定引导类,从而加载Spring容器 ::: image.png
这两种方式都可以,产生这个问题的原因在于,测试类如果不存在于引导类所在的包或子包中则无法找到Spring容器从而导致了自动装配失败

整合MyBatis

整合MyBatis.wmv :::info 知识加油站:@Mapper
如果Mapper.xml与Mapper.class在同一个包下且同名,spring的Mapper组件扫描器扫描Mapper.class的同时会自动扫描同名的Mapper.xml并装配到Mapper.class。
如果Mapper.xml与Mapper.class不在同一个包下或者不同名,就必须使用配置mapperLocations指定mapper.xml的位置。而使用了@Mapper注解的类采用注解开发不需要写xml映射文件,并会生成动态代理类交Mapper组件扫描器扫描即可
@Mapper是由Mybatis框架中定义的一个描述数据层接口的注解,注解往往起到的都是一个描述性作用,目的就是为了不再写mapper映射文件并被Spring容器识别到并产生自动代理的对象,并将其实现类对象存储到spring容器中。
@Mapper使用MyBatis或者搭配原始Spring去写的话是可以省略的,但是使用SpringBoot是必须要写的
搭配SpringBoot使用,@Mapper会告诉spring框架的此接口的实现类由Mybatis负责创建,并将其实现类对象存储到spring容器中。 ::: 整合MyBatis只要创建一个SpringBoot项目并勾选相对应的技术栈即可
image.png
选择对应的技术栈
image.png
idea会自动帮我们导入MyBatis-spring-boot-starter的启动器,自动帮我们配置好整合的配置,我们只需要指定数据源即可
image.png
在yaml文件中配置数据源

  1. spring:
  2. datasource:
  3. driver-class-name: com.mysql.cj.jdbc.Driver
  4. url: jdbc:mysql://localhost:3306/mybatis
  5. username: root
  6. password: xzjyroot

pojo:

  1. package com.xuezhi.pojo;
  2. import lombok.Data;
  3. import lombok.ToString;
  4. import java.time.LocalDateTime;
  5. @Data
  6. @ToString
  7. public class Items {
  8. private Integer id;
  9. private String name;
  10. private double price;
  11. private String detail;
  12. private String pic;
  13. private LocalDateTime createtime;
  14. }

�mapper接口:

  1. package com.xuezhi.mapper;
  2. import com.xuezhi.pojo.Items;
  3. import org.apache.ibatis.annotations.Mapper;
  4. import org.apache.ibatis.annotations.Select;
  5. import java.util.List;
  6. @Mapper
  7. public interface ItemsMapper {
  8. @Select("select * from items")
  9. List<Items> getItems();
  10. }

测试:

  1. package com.xuezhi;
  2. import com.xuezhi.mapper.ItemsMapper;
  3. import com.xuezhi.pojo.Items;
  4. import org.junit.jupiter.api.Test;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.boot.test.context.SpringBootTest;
  7. import java.util.List;
  8. @SpringBootTest
  9. class SpringBoot05MyBatisApplicationTests {
  10. @Autowired
  11. private ItemsMapper itemsMapper;
  12. @Test
  13. void contextLoads() {
  14. List<Items> items = itemsMapper.getItems();
  15. System.out.println(items);
  16. }
  17. }

2.4.1,2.3.9版本SpringBoot会出现时区和驱动过时问题
1. MySQL 8.X驱动强制要求设置时区
 修改url,添加serverTimezone设定
 修改MySQL数据库配置(略)
2. 驱动类过时,提醒更换为com.mysql.cj.jdbc.Driver :::info 总结:1. 勾选MyBatis技术,也就是导入MyBatis对应的starter
2. 数据库连接相关信息转换成配置
3. 数据库SQL映射需要添加@Mapper被容器识别到 :::

整合MyBatis-Plus

整合MyBatisPllus.wmv :::info mp是国人开发的技术,在SpringBoot中并没有收录启动器,所以我们可以去maven的仓库自己去查找启动器的坐标,整合方式和MyBatis大同小异 ::: image.png

  1. <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
  2. <dependency>
  3. <groupId>com.baomidou</groupId>
  4. <artifactId>mybatis-plus-boot-starter</artifactId>
  5. <version>3.5.1</version>
  6. </dependency>

:::info 总结:
1.导入对应的starter
2.数据层使用BaseMapper简化开发
3.配置数据源
4.测试
由于SpringBoot中未收录MyBatis-Plus的坐标版本,需要指定对应的Version
需要使用的第三方技术无法通过勾选确定时,需要手工添加坐标 :::

整合Druid

整合Druid.wmv :::info SpringBoot有默认的数据源(HikariDataSource),如果想用第三方的数据源比如Druid可以导入相应的starter,然后配置数据源即可。 ::: 数据源主要是搭配数据库技术使用的所以导入MyBatis或MyBatisPlus的启动器
image.png
导入start

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>druid-spring-boot-starter</artifactId>
  4. <version>1.2.17</version>
  5. </dependency>

�配置数据源有两种方式,推荐第一种

  1. #第一种
  2. #spring:
  3. # datasource:
  4. # druid:
  5. # driver-class-name: com.mysql.cj.jdbc.Driver
  6. # url: jdbc:mysql://localhost:3306/mybatis
  7. # username: root
  8. # password: xzjyroot
  9. #第二种
  10. spring:
  11. datasource:
  12. driver-class-name: com.mysql.cj.jdbc.Driver
  13. url: jdbc:mysql://localhost:3306/mybatis
  14. username: root
  15. password: xzjyroot
  16. type: com.alibaba.druid.pool.DruidDataSource

:::info 总结:
1. 整合Druid需要导入Druid对应的starter
2. 根据Druid提供的配置方式进行配置
整合第三方技术通用方式
导入对应技术的starter
根据提供的配置格式,配置非默认值对应的配置项 :::

使用SpringBoot整合SSMP案例

项目搭建.wmv

  1. 实体类开发————使用Lombok快速制作实体类
  2. Dao开发————整合MyBatisPlus,制作数据层测试类
  3. Service开发————基于MyBatisPlus进行增量开发,制作业务层测试类
  4. Controller开发————基于Restful开发,使用PostMan测试接口功能
  5. Controller开发————前后端开发协议制作
  6. 页面开发————基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理
  7. 列表、新增、修改、删除、分页、查询
  8. 项目异常处理
  9. 按条件查询————页面功能调整、Controller修正功能、Service修正功能

环境准备

:::info 数据库脚本 :::

  1. DROP TABLE IF EXISTS `tbl_book`;
  2. CREATE TABLE `tbl_book` (
  3. `id` int(11) NOT NULL AUTO_INCREMENT,
  4. `type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  5. `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  6. `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  7. PRIMARY KEY (`id`) USING BTREE
  8. ) ENGINE = InnoDB AUTO_INCREMENT = 51 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
  9. INSERT INTO `tbl_book` VALUES (1, '计算机理论', 'Spring实战 第5版', 'Spring入门经典教程,深入理解Spring原理技术内幕');
  10. INSERT INTO `tbl_book` VALUES (2, '计算机理论', 'Spring 5核心原理与30个类手写实战', '十年沉淀之作,手写Spring精华思想');
  11. INSERT INTO `tbl_book` VALUES (3, '计算机理论', 'Spring 5 设计模式', '深入Spring源码剖析Spring源码中蕴含的10大设计模式');
  12. INSERT INTO `tbl_book` VALUES (4, '计算机理论', 'Spring MVC+MyBatis开发从入门到项目实战', '全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手');
  13. INSERT INTO `tbl_book` VALUES (5, '计算机理论', '轻量级Java Web企业应用实战', '源码级剖析Spring框架,适合已掌握Java基础的读者');
  14. INSERT INTO `tbl_book` VALUES (6, '计算机理论', 'Java核心技术 卷I 基础知识(原书第11版)', 'Core Java 第11版,Jolt大奖获奖作品,针对Java SE9、10、11全面更新');
  15. INSERT INTO `tbl_book` VALUES (7, '计算机理论', '深入理解Java虚拟机', '5个维度全面剖析JVM,大厂面试知识点全覆盖');
  16. INSERT INTO `tbl_book` VALUES (8, '计算机理论', 'Java编程思想(第4版)', 'Java学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉');
  17. INSERT INTO `tbl_book` VALUES (9, '计算机理论', '零基础学Java(全彩版)', '零基础自学编程的入门图书,由浅入深,详解Java语言的编程思想和核心技术');
  18. INSERT INTO `tbl_book` VALUES (10, '市场营销', '直播就该这么做:主播高效沟通实战指南', '李子柒、李佳琦、薇娅成长为网红的秘密都在书中');
  19. INSERT INTO `tbl_book` VALUES (11, '市场营销', '直播销讲实战一本通', '和秋叶一起学系列网络营销书籍');
  20. INSERT INTO `tbl_book` VALUES (12, '市场营销', '直播带货:淘宝、天猫直播从新手到高手', '一本教你如何玩转直播的书,10堂课轻松实现带货月入3W+');

:::info pojo :::

  1. package com.xuezhi.pojo;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. import lombok.ToString;
  6. @Data
  7. @ToString
  8. @NoArgsConstructor
  9. @AllArgsConstructor
  10. public class Book {
  11. private Integer id;
  12. private String type;
  13. private String name;
  14. private String description;
  15. }

:::info 创建模块,导入启动器 :::

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <groupId>com.xuezhi</groupId>
  6. <artifactId>SpringBoot_08_SSMP</artifactId>
  7. <version>0.0.1-SNAPSHOT</version>
  8. <properties>
  9. <java.version>1.8</java.version>
  10. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  11. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  12. <spring-boot.version>2.7.1</spring-boot.version>
  13. </properties>
  14. <dependencies>
  15. <dependency>
  16. <groupId>org.springframework.boot</groupId>
  17. <artifactId>spring-boot-starter-web</artifactId>
  18. </dependency>
  19. <dependency>
  20. <groupId>com.alibaba</groupId>
  21. <artifactId>druid-spring-boot-starter</artifactId>
  22. <version>1.1.22</version>
  23. </dependency>
  24. <dependency>
  25. <groupId>com.baomidou</groupId>
  26. <artifactId>mybatis-plus-boot-starter</artifactId>
  27. <version>3.5.1</version>
  28. </dependency>
  29. <dependency>
  30. <groupId>mysql</groupId>
  31. <artifactId>mysql-connector-java</artifactId>
  32. </dependency>
  33. <dependency>
  34. <groupId>org.projectlombok</groupId>
  35. <artifactId>lombok</artifactId>
  36. </dependency>
  37. <dependency>
  38. <groupId>org.springframework.boot</groupId>
  39. <artifactId>spring-boot-starter-test</artifactId>
  40. <scope>test</scope>
  41. </dependency>
  42. </dependencies>
  43. <dependencyManagement>
  44. <dependencies>
  45. <dependency>
  46. <groupId>org.springframework.boot</groupId>
  47. <artifactId>spring-boot-dependencies</artifactId>
  48. <version>${spring-boot.version}</version>
  49. <type>pom</type>
  50. <scope>import</scope>
  51. </dependency>
  52. </dependencies>
  53. </dependencyManagement>
  54. </project>

:::info yml :::

  1. spring:
  2. datasource:
  3. driver-class-name: com.mysql.cj.jdbc.Driver
  4. url: jdbc:mysql://localhost:3306/mybatis
  5. username: root
  6. password: xzjyroot
  7. type: com.alibaba.druid.pool.DruidDataSource
  8. mybatis-plus:
  9. global-config:
  10. db-config:
  11. # 表前缀
  12. table-prefix: tbl_
  13. # 日志
  14. configuration:
  15. log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

� :::info dao测试 :::

image.png

image.png :::info 配置分页拦截器 :::

  1. package com.xuezhi.config;
  2. import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
  3. import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. @Configuration
  7. public class MpConfig {
  8. @Bean
  9. public MybatisPlusInterceptor mybatisPlusInterceptor(){
  10. MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
  11. mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
  12. return mybatisPlusInterceptor;
  13. }
  14. }

:::info 测试分页查询 :::

  1. @Test
  2. public void test02(){
  3. Page<Book> bookPage = new Page<Book>(1, 5);
  4. Page<Book> bookPage1 = bookMapper.selectPage(bookPage, null);
  5. System.out.println(bookPage1.getRecords());
  6. }

image.png

页面

放到static目录下
页面.zip
访问pages/books.html

统一返回结果集

  1. package com.xuezhi.pojo;
  2. import lombok.Data;
  3. @Data
  4. public class R {
  5. private Boolean flag;
  6. private Object data;
  7. private String msg;
  8. public R(){
  9. }
  10. public R(Boolean flag){
  11. this.flag = flag;
  12. }
  13. public R(Boolean flag,Object data){
  14. this.flag = flag;
  15. this.data = data;
  16. }
  17. public R(Boolean flag,Object data,String msg){
  18. this.flag = flag;
  19. this.data = data;
  20. this.msg = msg;
  21. }
  22. public R(Boolean flag,String msg){
  23. this.flag = flag;
  24. this.msg = msg;
  25. }
  26. public R(String msg){
  27. this.flag = false;
  28. this.msg = msg;
  29. }
  30. }

:::info

  1. 设计统一的返回值结果类型便于前端开发读取数据
    2. 返回值结果类型可以根据需求自行设定,没有固定格式
    3. 返回值结果模型类用于后端与前端进行数据格式统一,也称为前后端数据协议 :::

    查询列表

    列表展现.wmv :::info dataList:里面放入当前页要展示的列表数据 ::: image.png :::info 让钩子函数,在Vue对象初始化之后自动调用getAll方法,getAll使用axios发送get请求,调用then函数处理结果集,把查询出的列表数据放入到 Vue对象的dataList中,将查询数据返回到页面,利用前端数据双向绑定进行数据展示 ::: image.png

    增删改

    数据新增.wmv
    删除操作.wmv
    修改功能.wmv :::info 增删改Controller ::: ```java package com.xuezhi.controller;

import com.xuezhi.pojo.Book; import com.xuezhi.pojo.R; import com.xuezhi.service.BooksService; import org.apache.ibatis.annotations.Delete; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*;

import java.sql.SQLException;

@RestController @RequestMapping(“/books”) public class BooksController { @Autowired private BooksService booksService; @GetMapping public R getAll(){ return new R(true,booksService.list()); }

  1. @GetMapping("/{id}")
  2. public R getById(@PathVariable Integer id){
  3. return new R(true,booksService.getById(id));
  4. }
  5. @PostMapping
  6. public R save(@RequestBody Book book) throws SQLException{
  7. return new R(booksService.save(book));
  8. }
  9. @DeleteMapping("/{id}")
  10. public R delete(@PathVariable Integer id){
  11. System.out.println(id);
  12. return new R(booksService.removeById(id));
  13. }
  14. @PutMapping
  15. public R update(@RequestBody Book books){
  16. return new R(booksService.updateById(books));
  17. }

}

  1. :::info
  2. 增删改页面逻辑
  3. :::
  4. ```html
  5. <!DOCTYPE html>
  6. <html>
  7. <head>
  8. <!-- 页面meta -->
  9. <meta charset="utf-8">
  10. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  11. <title>基于SpringBoot整合SSM案例</title>
  12. <meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">
  13. <!-- 引入样式 -->
  14. <link rel="stylesheet" href="../plugins/elementui/index.css">
  15. <link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min.css">
  16. <link rel="stylesheet" href="../css/style.css">
  17. </head>
  18. <body class="hold-transition">
  19. <div id="app">
  20. <div class="content-header">
  21. <h1>图书管理</h1>
  22. </div>
  23. <div class="app-container">
  24. <div class="box">
  25. <div class="filter-container">
  26. <el-input placeholder="图书类别" style="width: 200px;" class="filter-item"></el-input>
  27. <el-input placeholder="图书名称" style="width: 200px;" class="filter-item"></el-input>
  28. <el-input placeholder="图书描述" style="width: 200px;" class="filter-item"></el-input>
  29. <el-button @click="getAll()" class="dalfBut">查询</el-button>
  30. <el-button type="primary" class="butT" @click="handleCreate()">新建</el-button>
  31. </div>
  32. <el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row>
  33. <el-table-column type="index" align="center" label="序号"></el-table-column>
  34. <el-table-column prop="type" label="图书类别" align="center"></el-table-column>
  35. <el-table-column prop="name" label="图书名称" align="center"></el-table-column>
  36. <el-table-column prop="description" label="描述" align="center"></el-table-column>
  37. <el-table-column label="操作" align="center">
  38. <template slot-scope="scope">
  39. <el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button>
  40. <el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>
  41. </template>
  42. </el-table-column>
  43. </el-table>
  44. <!--分页组件-->
  45. <div class="pagination-container">
  46. <el-pagination
  47. class="pagiantion"
  48. @current-change="handleCurrentChange"
  49. :current-page="pagination.currentPage"
  50. :page-size="pagination.pageSize"
  51. layout="total, prev, pager, next, jumper"
  52. :total="pagination.total">
  53. </el-pagination>
  54. </div>
  55. <!-- 新增标签弹层 -->
  56. <div class="add-form">
  57. <el-dialog title="新增图书" :visible.sync="dialogFormVisible">
  58. <el-form ref="dataAddForm" :model="formData" :rules="rules" label-position="right" label-width="100px">
  59. <el-row>
  60. <el-col :span="12">
  61. <el-form-item label="图书类别" prop="type">
  62. <el-input v-model="formData.type"/>
  63. </el-form-item>
  64. </el-col>
  65. <el-col :span="12">
  66. <el-form-item label="图书名称" prop="name">
  67. <el-input v-model="formData.name"/>
  68. </el-form-item>
  69. </el-col>
  70. </el-row>
  71. <el-row>
  72. <el-col :span="24">
  73. <el-form-item label="描述">
  74. <el-input v-model="formData.description" type="textarea"></el-input>
  75. </el-form-item>
  76. </el-col>
  77. </el-row>
  78. </el-form>
  79. <div slot="footer" class="dialog-footer">
  80. <el-button @click="cancel()">取消</el-button>
  81. <el-button type="primary" @click="handleAdd()">确定</el-button>
  82. </div>
  83. </el-dialog>
  84. </div>
  85. <!-- 编辑标签弹层 -->
  86. <div class="eidt-form">
  87. <el-dialog title="编辑检查项" :visible.sync="dialogFormVisible4Edit">
  88. <el-form ref="dataEditForm" :model="formData" :rules="rules" label-position="right" label-width="100px">
  89. <el-row>
  90. <el-col :span="12">
  91. <el-form-item label="图书类别" prop="type">
  92. <el-input v-model="formData.type"/>
  93. </el-form-item>
  94. </el-col>
  95. <el-col :span="12">
  96. <el-form-item label="图书名称" prop="name">
  97. <el-input v-model="formData.name"/>
  98. </el-form-item>
  99. </el-col>
  100. </el-row>
  101. <el-row>
  102. <el-col :span="24">
  103. <el-form-item label="描述">
  104. <el-input v-model="formData.description" type="textarea"></el-input>
  105. </el-form-item>
  106. </el-col>
  107. </el-row>
  108. </el-form>
  109. <div slot="footer" class="dialog-footer">
  110. <el-button @click="cancel()">取消</el-button>
  111. <el-button type="primary" @click="handleEdit()">确定</el-button>
  112. </div>
  113. </el-dialog>
  114. </div>
  115. </div>
  116. </div>
  117. </div>
  118. </body>
  119. <!-- 引入组件库 -->
  120. <script src="../js/vue.js"></script>
  121. <script src="../plugins/elementui/index.js"></script>
  122. <script type="text/javascript" src="../js/jquery.min.js"></script>
  123. <script src="../js/axios-0.18.0.js"></script>
  124. <script>
  125. var vue = new Vue({
  126. el: '#app',
  127. data:{
  128. dataList: [],//当前页要展示的列表数据
  129. dialogFormVisible: false,//添加表单是否可见
  130. dialogFormVisible4Edit:false,//编辑表单是否可见
  131. formData: {},//表单数据
  132. rules: {//校验规则
  133. type: [{ required: true, message: '图书类别为必填项', trigger: 'blur' }],
  134. name: [{ required: true, message: '图书名称为必填项', trigger: 'blur' }]
  135. },
  136. pagination: {//分页相关模型数据
  137. currentPage: 1,//当前页码
  138. pageSize:10,//每页显示的记录数
  139. total:0//总记录数
  140. }
  141. },
  142. //钩子函数,VUE对象初始化完成后自动执行
  143. created() {
  144. this.getAll();
  145. },
  146. methods: {
  147. //列表
  148. getAll() {
  149. axios.get("/books").then((rs)=>{
  150. this.dataList = rs.data.data;
  151. });
  152. },
  153. //弹出添加窗口
  154. handleCreate() {
  155. //弹出新增弹层
  156. this.dialogFormVisible = true;
  157. //重置表单
  158. this.resetForm();
  159. },
  160. //重置表单
  161. resetForm() {
  162. //重置表单数据
  163. this.formData={};
  164. },
  165. //添加
  166. handleAdd () {
  167. axios.post("/books",this.formData).then((res)=>{
  168. if(res.data.flag){
  169. //提示
  170. this.$message.success("新增成功");
  171. //关闭新增弹层
  172. this.dialogFormVisible = false;
  173. }else{
  174. this.$message.error("新增失败");
  175. }
  176. }).finally(()=>{
  177. //不管成功与否,都要重新查询列表
  178. this.getAll();
  179. })
  180. },
  181. //取消
  182. cancel(){
  183. this.dialogFormVisible = false;
  184. this.dialogFormVisible4Edit = false;
  185. this.$message.info("新增操作取消");
  186. },
  187. // 删除
  188. handleDelete(row) {
  189. this.$confirm("是否永久删除本条数据?","提示",{type:"info"}).then(()=>{
  190. axios.delete("/books/"+row.id).then((res)=>{
  191. if(res.data.flag){
  192. this.$message.success("删除成功");
  193. }else{
  194. this.$message.error("数据同步失败,已刷新页面");
  195. }
  196. }).finally(()=>{
  197. this.getAll();
  198. });
  199. }).catch(()=>{
  200. this.$message.info("删除操作取消");
  201. })
  202. },
  203. //弹出编辑窗口
  204. handleUpdate(row) {
  205. this.resetForm();
  206. //查询当前用户并数据回显
  207. axios.get("/books/"+row.id).then((res)=>{
  208. if(res.data.flag && res.data.data != null){
  209. this.formData = res.data.data;
  210. //弹出编辑弹出层
  211. this.dialogFormVisible4Edit = true;
  212. }else{
  213. this.$message.error("数据同步失败,已刷新页面");
  214. }
  215. }).finally(()=>{
  216. this.getAll();
  217. });
  218. // 直接把row中的数据赋值给formData会有数据同步问题
  219. // this.formData = {"id":row.id,"type":row.type,"name":row.name,"description":row.description}
  220. },
  221. //修改
  222. handleEdit() {
  223. console.log(this.formData);
  224. axios.put("/books",this.formData).then((res)=>{
  225. if(res.data.flag){
  226. this.$message.success("修改成功");
  227. this.dialogFormVisible4Edit = false;
  228. }else{
  229. this.$message.error("数据同步失败,刷新页面");
  230. }
  231. }).finally(()=>{
  232. this.getAll();
  233. })
  234. },
  235. //分页查询
  236. //切换页码
  237. handleCurrentChange(currentPage) {
  238. },
  239. //条件查询
  240. }
  241. })
  242. </script>
  243. </html>

全局异常处理

全局异常处理器和统一返回格式.wmv
后台系统bug导致数据格式不统一
image.png :::info 可以使用SpringMVC提供的@RestControllerAdvice用来处理全局数据
顾名思义,@ControllerAdvice就是@Controller 的增强版。@ControllerAdvice主要用来处理全局数据,一般搭配@ExceptionHandler、@ModelAttribute以及@InitBinder使用。 :::

  1. package com.xuezhi.config;
  2. import com.xuezhi.pojo.R;
  3. import org.springframework.web.bind.annotation.ControllerAdvice;
  4. import org.springframework.web.bind.annotation.ExceptionHandler;
  5. import org.springframework.web.bind.annotation.RestControllerAdvice;
  6. @RestControllerAdvice
  7. public class ProjectExceptionAdvice {
  8. @ExceptionHandler
  9. public R doException(Exception e){
  10. e.printStackTrace();
  11. return new R("系统错误,请稍后再试!");
  12. }
  13. }

:::info 模拟异常 :::

  1. @PostMapping
  2. public R save(@RequestBody Book book) throws SQLException{
  3. if("123".equals(book.getName())){
  4. // 异常外抛
  5. throw new SQLException();
  6. }
  7. return new R(booksService.save(book));
  8. }
  1. //添加
  2. handleAdd () {
  3. axios.post("/books",this.formData).then((res)=>{
  4. if(res.data.flag){
  5. //提示
  6. this.$message.success("新增成功");
  7. //关闭新增弹层
  8. this.dialogFormVisible = false;
  9. }else{
  10. // 使用后台提供的消息
  11. this.$message.error(res.data.msg);
  12. }
  13. }).finally(()=>{
  14. //不管成功与否,都要重新查询列表
  15. this.getAll();
  16. })
  17. },

image.png

业务消息一致性处理

目前我们的业务消息有来自于前端的也有来自于后端的,我们可以在后端统一处理
image.png :::info 例如: :::

  1. package com.xuezhi.controller;
  2. import com.xuezhi.pojo.Book;
  3. import com.xuezhi.pojo.R;
  4. import com.xuezhi.service.BooksService;
  5. import org.apache.ibatis.annotations.Delete;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.stereotype.Controller;
  8. import org.springframework.web.bind.annotation.*;
  9. import java.sql.SQLException;
  10. @RestController
  11. @RequestMapping("/books")
  12. public class BooksController {
  13. @Autowired
  14. private BooksService booksService;
  15. @GetMapping
  16. public R getAll(){
  17. return new R(true,booksService.list());
  18. }
  19. @GetMapping("/{id}")
  20. public R getById(@PathVariable Integer id){
  21. Book book = booksService.getById(id);
  22. return new R(true,book,book == null?"数据同步失败,已刷新页面!":"");
  23. }
  24. @PostMapping
  25. public R save(@RequestBody Book book) throws SQLException{
  26. if("123".equals(book.getName())){
  27. throw new SQLException();
  28. }
  29. boolean flag = booksService.save(book);
  30. return new R(flag,flag?"新增成功^_^":"新增失败-_-!");
  31. }
  32. @DeleteMapping("/{id}")
  33. public R delete(@PathVariable Integer id){
  34. boolean flag = booksService.removeById(id);
  35. return new R(flag,flag?"删除成功^_^":"数据同步失败,已刷新页面!");
  36. }
  37. @PutMapping
  38. public R update(@RequestBody Book books){
  39. boolean flag = booksService.updateById(books);
  40. return new R(flag,flag?"修改成功^_^":"修改失败-_-!");
  41. }
  42. }

image.png
image.png
image.png
目的是方便做国际化

分页

分页查询.wmv :::info 分页数据模版 ::: image.png
image.png :::info 把getAll方法,改成分页查询 :::

  1. //分页查询
  2. getAll() {
  3. axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((rs)=>{
  4. this.pagination.currentPage = rs.data.data.current;
  5. this.pagination.total = rs.data.data.total;
  6. this.dataList = rs.data.data.records;
  7. });
  8. },
  9. //切换页码
  10. handleCurrentChange(currentPage) {
  11. this.pagination.currentPage = currentPage;
  12. this.getAll();
  13. },
  1. @GetMapping("/{currentPage}/{pageSize}")
  2. public R getByPage(@PathVariable Integer currentPage , @PathVariable Integer pageSize){
  3. Page<Book> bookPage = new Page<Book>(currentPage, pageSize);
  4. Page<Book> page = booksService.page(bookPage);
  5. return new R(true,page);
  6. }

2023-04-24 17.09.40.gif

分页删除bug

:::info 如果最后一页只有一条数据,并且删除他,会导致一下bug ::: 2023-04-24 17.13.02.gif :::info 解决: :::

  1. @GetMapping("/{currentPage}/{pageSize}")
  2. public R getByPage(@PathVariable Integer currentPage , @PathVariable Integer pageSize){
  3. Page<Book> bookPage = new Page<Book>(currentPage, pageSize);
  4. Page<Book> page = booksService.page(bookPage);
  5. // 如果当前页大于总页码数,则查询最后一页数据
  6. if(currentPage>page.getPages()){
  7. bookPage = new Page<Book>(page.getPages(), pageSize);
  8. page = booksService.page(bookPage);
  9. }
  10. return new R(true,page);
  11. }

2023-04-24 17.18.25.gif

条件查询

条件查询.wmv
image.png
image.png
我们发现查询查询的模版并没有绑定模型数据,所以我们需要自己绑定一下,因为条件查询是跟着分页走的所以我们把数据写到 分页的模型里
image.png
image.png :::info 组装查询参数,并拼接到url,携带到后台 :::

  1. //分页查询
  2. getAll() {
  3. //组装条件查询请求参数
  4. let param = "?q";
  5. param += "&type="+this.pagination.type;
  6. param += "&name="+this.pagination.name;
  7. param += "&description="+this.pagination.description;
  8. axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize+param).then((rs)=>{
  9. //分页
  10. this.pagination.currentPage = rs.data.data.current;
  11. this.pagination.total = rs.data.data.total;
  12. this.dataList = rs.data.data.records;
  13. });
  14. },

:::info 修改分页查询Controller,把业务逻辑放到Service层 :::

  1. @GetMapping("/{currentPage}/{pageSize}")
  2. public R getByPage(@PathVariable Integer currentPage , @PathVariable Integer pageSize,Book book){
  3. IPage<Book> page = booksService.getPage(currentPage,pageSize,book);
  4. return new R(true,page);
  5. }

:::info 根据条件查询接口 :::

  1. public interface BooksService extends IService<Book> {
  2. IPage<Book> getPage(Integer currentPage, Integer pageSize, Book book);
  3. }

:::info 根据条件查询实现 :::

  1. @Service
  2. public class BooksServiceImpl extends ServiceImpl<BookMapper, Book> implements BooksService {
  3. @Autowired
  4. private BookMapper bookMapper;
  5. public IPage<Book> getPage(Integer currentPage, Integer pageSize, Book book) {
  6. Page<Book> page = new Page(currentPage, pageSize);
  7. // 组装查询条件
  8. LambdaQueryWrapper<Book> wrapper = new LambdaQueryWrapper<Book>();
  9. wrapper.like(Strings.isNotEmpty(book.getType()),Book::getType,book.getType())
  10. .like(Strings.isNotEmpty(book.getName()),Book::getName,book.getName())
  11. .like(Strings.isNotEmpty(book.getDescription()),Book::getDescription,book.getDescription());
  12. // 分页查询
  13. Page<Book> bookPage = bookMapper.selectPage(page, wrapper);
  14. // 避免删除之后分页bug,如果当前页>总页数,则查询最后一页
  15. if(currentPage>bookPage.getPages()){
  16. page = new Page(bookPage.getPages(), pageSize);
  17. bookPage = bookMapper.selectPage(page, wrapper);
  18. }
  19. return bookPage;
  20. }
  21. }

总结

  1. 1. pom.xml
  2. 配置起步依赖
  3. 2. application.yml
  4. 设置数据源、端口、框架技术相关配置等
  5. 3. dao
  6. 继承BaseMapper、设置@Mapper
  7. 4. dao测试类
  8. 5. service
  9. 调用数据层接口或MyBatis-Plus提供的接口快速开发
  10. 6. service测试类
  11. 7. controller
  12. 基于Restful开发,使用Postman测试跑通功能
  13. 8. 页面
  14. 放置在resources目录下的static目录中