1 Spring Boot
1.1 Spring Boot概述
Spring Boot设计目的是用来简化Spring应用的创建、运行、调试、部署等。使用Spring Boot可以做到专注于Spring应用的开发,而无需过多关注XML的配置。Spring Boot使用“约定优于配置”的理念,简单来说,它提供了一堆依赖打包,并已经按照使用习惯解决了依赖问题。
1.2 Spring Boot的核心功能
-
可独立运行的Spring项目:Spring Boot可以以jar包的形式独立运行。
-
内嵌的Servlet容器:Spring Boot可以选择内嵌Tomcat、Jetty或者Undertow,无须以war包形式部署项目。
-
简化的Maven配置:Spring提供推荐的基础 POM 文件来简化Maven 配置。
-
自动配置Spring:Spring Boot会根据项目依赖来自动配置Spring 框架,极大地减少项目要使用的配置。
-
提供生产就绪型功能:提供可以直接在生产环境中使用的功能,如性能指标、应用信息和应用健康检查。
-
无代码生成和xml配置:Spring Boot不生成代码。完全不需要任何xml配置即可实现Spring的所有配置。
1.3 SpringBootB的相关好处
-
使用 Spring 项目引导页面可以在⼏秒构建⼀个项⽬;
-
方便对外输出各种形式的服务,如 REST API、WebSocket、Web、Streaming、Tasks;
-
非常简洁的安全策略集成;
-
⽀持关系数据库和⾮关系数据库;
-
⽀持运行期内嵌容器,如 Tomcat、Jetty;
-
强⼤的开发包,⽀持热启动;
-
⾃动管理依赖;
-
⾃带应⽤监控;
-
⽀持各种 IED,如 IntelliJ IDEA 、NetBeans。
和Spirng程序相比,SpringBoot程序在开发的过程中各个层面均具有优势
| 类配置文件 | Spring | SpringBoot |
|---|---|---|
| pom文件中的坐标 | 手工添加 | 勾选添加 |
| web3.0配置类 | 手工制作 | 无 |
| Spring/SpringMVC配置类 | 手工制作 | 无 |
| 控制器 | 手工制作 | 手工制作 |
2 Spring Boot快速创建
2.1 使用Spring Initializr引导页面进行创建
①创建新模块,选择Spring Initializr,并配置模块相关基础信息
②选择当前模块需要使用的技术集
③开发控制器类
package com.liu.controller;
import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;
@RestController@RequestMapping("/user")public class UserController {
@GetMapping @RequestMapping("quick1") public String findUsers() { System.out.println("Spring boot is running"); return "Spring is running"; }}④运行自动生成的Application类
2.2 进入Spring Boot官网进行创建
①点击Spring Initializr后进入到创建SpringBoot程序的界面上,下面是输入信息的过程,和前面的一样,只是界面变了而已,根据自己的要求,在左侧选择对应信息和输入对应的信息即可。右侧的ADD DEPENDENCIES用于选择使用何种技术,和之前勾选的Spring WEB是在做同一件事,仅仅是界面不同而已,点击后打开网页版的技术选择界面
②所有信息设置完毕后,点击下面左侧按钮,生成一个文件包,保存后得到一个压缩文件,这个文件打开后就是创建的SpringBoot工程文件夹。解压缩此文件后,得到工程目录,在Idea中导入即可使用,和之前创建的东西完全一样。下面就可以自己创建一个Controller测试一下是否能用了。
2.3 使用阿里云地址创建Spring Boot项目
①创建新模块,选择Spring Initializr,选择Server URL为start.aliyun.com,并配置模块相关基础信息
②选择当前模块需要使用的技术集
③运行自动生成的Application类进行测试
3 Spring Boot简介
3.1 parent:进行版本的统一管理
①parent概述
SpringBoot为了解决最合理的依赖版本配置方案,于是将所有的技术版本的常见使用方案都给开发者整理了出来,以后开发者使用时直接用它提供的版本方案,就不用担心冲突问题了,相当于SpringBoot做了无数个技术版本搭配的列表,这个技术搭配列表的名字叫做parent。
parent自身具有很多个版本,每个parent版本中包含有几百个其他技术的版本号,不同的parent间使用的各种技术的版本号有可能会发生变化。当开发者使用某些技术时,直接使用SpringBoot提供的parent就行了,由parent帮助开发者统一的进行各种技术的版本管理。
②Spring Boot项目中引用的parent
项目中的pom.xml中继承了一个坐标,打开后可以查阅到其中又继承了一个坐标,这个坐标中定义了两组信息。第一组是各式各样的依赖版本号属性,下面列出依赖版本属性的局部,可以看的出来,定义了若干个技术的依赖版本号;第二组是各式各样的的依赖坐标信息,可以看出依赖坐标定义中没有具体的依赖版本号,而是引用了第一组信息中定义的依赖版本属性值。
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.3</version> <relativePath/> <!-- lookup parent from repository --></parent>
③parent功能
- 定义了 Java 编译版本为 1.8 。
- 使用 UTF-8 格式编码。
- 继承自 spring-boot-dependencies,这个里边定义了依赖的版本,也正是因为继承了这个依赖,所以我们在写4、依赖时才不需要写版本号。
- 执行打包操作的配置。
- 自动化的资源过滤。
- 自动化的插件配置。
- 针对 application.properties 和 application.yml 的资源过滤,包括通过 profile 定义的不同环境的配置文件,例如 application-dev.properties 和 application-dev.yml。
3.2 starter:减少依赖配置
①starter概述
SpringBoot把所有的技术使用的固定搭配格式都给开发出来,开发者使用的时候,就不用一次写一堆依赖了,直接用Spring Boot做好的这个东西就好了,对于这样的固定技术搭配,SpringBoot给它起了个名字叫做starter。starter定义了使用某种技术时对于依赖的固定搭配格式,也是一种最佳解决方案,使用starter可以帮助开发者减少依赖配置。
②Spring Boot项目中引用的starter
项目中的pom.xml定义了使用SpringMVC技术,但是并没有写SpringMVC的坐标,而是添加了一个名字中包含starter的依赖,在spring-boot-starter-web中又定义了若干个具体依赖的坐标。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope></dependency>
③starter与parent的区别
- starter是一个坐标中定了若干个坐标,以前写多个的,现在写一个,是用来减少依赖配置的书写量的。
- parent是定义了几百个依赖版本号,以前写依赖需要自己手工控制版本,现在由SpringBoot统一管理,这样就不存在版本冲突了,是用来减少依赖冲突的。
3.3 引导类
运行这个类就可以启动SpringBoot工程:
@SpringBootApplicationpublic class Springboot0101QuickstartApplication { public static void main(String[] args) { SpringApplication.run(Springboot0101QuickstartApplication.class, args); }}SpringBoot本身是为了加速Spring程序的开发的,而Spring程序运行的基础是需要创建自己的Spring容器对象(IoC容器)并将所有的对象交给Spring的容器管理,也就是一个一个的Bean。当前这个类运行后就会产生一个Spring容器对象,并且可以将这个对象保存起来,通过容器对象直接操作Bean。
@SpringBootApplicationpublic class Springboot01QuickstartApplication { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(Springboot01QuickstartApplication.class, args); UserController userController = applicationContext.getBean(UserController.class); System.out.println(userController); }}
通过上述操作不难看出,其实SpringBoot程序启动还是创建了一个Spring容器对象。这个类在SpringBoot程序中是所有功能的入口,称这个类为引导类。作为一个引导类最典型的特征就是当前类上方声明了一个注解@SpringBootApplication打开该注解可以发现,这个类就是学习Spring的注解核心配置类。
3.4 内嵌tomcat
打开查看web的starter,发现有一个tomcat的starter,这里面有一个核心的坐标,tomcat-embed-core,叫做tomcat内嵌核心。就是这个东西把tomcat功能引入到了我们的程序中。
更换内嵌Tomcat
SpringBoot提供了3款内置的服务器
-
tomcat(默认):apache出品,粉丝多,应用面广,负载了若干较重的组件
-
jetty:更轻量级,负载性能远不及tomcat
-
undertow:负载性能勉强跑赢tomcat
想用哪个,加个坐标就OK。前提是把tomcat排除掉,因为tomcat是默认加载的。
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency></dependencies>现在就已经成功替换了web服务器,核心思想就是用什么加入对应坐标就可以了。如果有starter,优先使用starter。
4 Rest风格,RestFul开发
在学习Spring MVC时学过REST风格,但是讲课老师讲的不太深入,对于一些注解并没有进行讲解。于是重新复习一遍。
4.1 RESTFUL概述
RESTFUL是一种网络应用程序的设计风格和开发方式,基于HTTP,可以使用 XML 格式定义或 JSON 格式定义。最常用的数据格式是JSON。由于JSON能直接被JavaScript读取,所以,使用JSON格式的REST风格的API具有简单、易读、易用的特点。
- REST 是 Representational State Transfer 的缩写,如果一个架构符合 REST 原则,就称它为 RESTful 架构
- RESTful 架构可以充分的利用 HTTP 协议的各种功能,是 HTTP 协议的最佳实践。
- RESTful API 是一种软件架构风格、设计风格,可以让软件更加清晰,更简洁,更有层次,可维护性更好
4.2 RESTFUL API 请求设计
| 请求方式 | 含义 |
|---|---|
| GET(SELECT) | 从服务器取出资源(一项或多项) |
| POST(CREATE) | 在服务器新建一个资源 |
| PUT(UPDATE) | 在服务器更新资源(更新完整资源) |
| PATCH(UPDATE) | 在服务器更新资源, PATCH更新个别属性 |
| DELETE(DELETE) | 从服务器删除资源 |
4.3 在Spring MVC中使用RESTFUL开发设计
@Controllerpublic class ZoosController {
@RequestMapping(value = "/zoos",method = RequestMethod.GET) @ResponseBody public String findAll(){ System.out.println("ZoosController.findAll"); return "zoosController.findAll"; }
@RequestMapping(value = "/zoos/{id}",method = RequestMethod.GET) @ResponseBody public String findOne(@PathVariable Integer id){ System.out.println("ZoosController.findOne"); return "zoosController.findOne"; }
@RequestMapping(value = "/zoos",method = RequestMethod.PUT) @ResponseBody public String update(@RequestBody Zoo zoo){ System.out.println("ZoosController.update"); return "zoosController.update"; }
@RequestMapping(value = "/zoos/{id}",method = RequestMethod.DELETE) @ResponseBody public String delete(@PathVariable Integer id){ System.out.println("ZoosController.delete"); return "zoosController.delete"; }
@RequestMapping(value = "/zoos",method = RequestMethod.POST) @ResponseBody public String insert(){ System.out.println("ZoosController.insert"); return "zoosController.insert"; }}简化开发相关注解
| 注解 | 作用 |
|---|---|
| @RestController | 由 @Controller + @ResponseBody组成(返回 JSON 数据格式) |
| @PathVariable | URL 中的 {xxx} 占位符可以通过@PathVariable(“xxx“) 绑定到控制器处理方法的形参中 |
| @RequestMapping | 注解用于请求地址的解析,是最常用的一种注解 |
| @GetMapping | 查询请求 |
| @PostMapping | 添加请求 |
| @PutMapping | 更新请求 |
| @DeleteMapping | 删除请求 |
| @RequestParam | 将请求参数绑定到你控制器的方法参数上(是springmvc中接收普通参数的注解) |
@RestController@RequestMapping("/zoos")@ResponseBodypublic class ZoosController { @GetMapping public String findAll(){ System.out.println("ZoosController.findAll"); return "zoosController.findAll"; } @GetMapping(value = "/{id}") public String findOne(@PathVariable Integer id){ System.out.println("ZoosController.findOne"); return "zoosController.findOne"; } @PutMapping public String update(@RequestBody Zoo zoo){ System.out.println("ZoosController.update"); return "zoosController.update"; } @DeleteMapping(value = "/{id}") public String delete(@PathVariable Integer id){ System.out.println("ZoosController.delete"); return "zoosController.delete"; }
@PostMapping public String insert(){ System.out.println("ZoosController.insert"); return "zoosController.insert"; }}5 SpringBoot基础配置
5.1 属性配置
SpringBoot通过配置文件application.properties就可以修改默认的配置,properties格式的文件书写规范是key=value。
- SpringBoot程序可以在application.properties文件中进行属性配置。
- application.properties文件中只要输入要配置的属性关键字就可以根据提示进行设置。
- SpringBoot将配置信息集中在一个文件中写,不管你是服务器的配置,还是数据库的配置,总之都写在一起,逃离一个项目十几种配置文件格式的尴尬局面。
更改端口号
# 服务器的端口配置server.port=80
关闭运行日志图表(banner)
# 关闭bannerspring.main.banner-mode=off# 修改bannerspring.banner.image.location=wallhaven-2879mg.png
设置运行日志的显示级别
# 日志logging.level.root = error
5.2 配置文件分类
SpringBoot除了支持properties格式的配置文件,还支持另外两种格式的配置文件。分别如下:
- application.properties(properties格式)
server.port=80- application.yml(yml格式)
server: port: 81- application.yaml(yaml格式)
server: port: 82仔细看会发现yml格式和yaml格式除了文件名后缀不一样,格式完全一样,是这样的,yml和yaml文件格式就是一模一样的,只是文件后缀不同,所以可以合并成一种格式来看。
5.3 配置文件优先级
其实三个文件如果共存的话,谁生效说的就是配置文件加载的优先级别。
application.properties > application.yml > application.yaml配置文件间的加载优先级 properties(最高)> yml > yaml(最低),不同配置文件中相同配置按照加载优先级相互覆盖,不同配置文件中不同配置全部保留。
5.4 yaml文件的使用
YAML(YAML Ain’t Markup Language),一种数据序列化格式。具有容易阅读、容易与脚本语言交互、以数据为核心,重数据轻格式的特点。
常见的文件扩展名有两种:
- .yml格式(主流)
- .yaml格式
对于文件自身在书写时,具有严格的语法格式要求,具体如下:
- 大小写敏感
- 属性层级关系使用多行描述,每行结尾使用冒号结束
- 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
- 属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
- #号 表示注释
常见的数据书写格式:
boolean: TRUE #TRUE,true,True,FALSE,false,False均可float: 3.14 #6.8523015e+5 #支持科学计数法int: 123 #0b1010_0111_0100_1010_1110 #支持二进制、八进制、十六进制null: ~ #使用~表示nullstring: HelloWorld #字符串可以直接书写string2: "Hello World" #可以使用双引号包裹特殊字符date: 2018-02-17 #日期必须使用yyyy-MM-dd格式datetime: 2018-02-17T15:02:31+08:00 #时间和日期之间使用T连接,最后使用+代表时区subject: - Java - 前端 - 大数据enterprise: name: itcast age: 16 subject: - Java - 前端 - 大数据likes: [王者荣耀,刺激战场] #数组书写缩略格式users: #对象数组格式一 - name: Tom age: 4 - name: Jerry age: 5users: #对象数组格式二 - name: Tom age: 4 - name: Jerry age: 5users2: [ { name:Tom , age:4 } , { name:Jerry , age:5 } ] #对象数组缩略格式5.5 yaml文件数据读取
读取单一数据
yaml中保存的单个数据,可以使用Spring中的注解直接读取,使用@Value可以读取单个数据,属性名引用方式:${一级属性名.二级属性名……}
address: 河南
user: userName: admin password: 123456
likes: - game - music - video
baseDir: c:\windows\system32
tempDir: ${baseDir}\temp@RestController@RequestMapping("/user")public class UserController { @Value("${address}") private String address;
@Value("${user.userName}") private String userName;
@Value("${likes[1]}") private String likes1;
@Value("${tempDir}") private String tempDir;
@GetMapping("/quick1") public String findUsers() { System.out.println("Spring boot is running"); System.out.println(address); System.out.println(userName); System.out.println(likes1); System.out.println(tempDir); }读取全部数据
SpringBoot提供了一个对象,能够把所有的数据都封装到这一个对象中,这个对象叫做Environment,使用自动装配注解可以将所有的yaml数据封装到这个对象中。
@RestController@RequestMapping("/user")public class UserController { //自动装配,把所有对象加载到environment对象中 @Autowired private Environment environment;
@GetMapping("/quick1") public String findUsers() { System.out.println("Spring boot is running"); //使用environment获得yMl文件中的信息 String address = environment.getProperty("address"); System.out.println(address); }}读取对象数据
单一数据读取书写比较繁琐,全数据封装又封装的太厉害了,每次拿数据还要一个一个的getProperties(),总之用起来都不是很舒服。由于Java是一个面向对象的语言,很多情况下,我们会将一组数据封装成一个对象。SpringBoot也提供了可以将一组yaml对象数据封装一个Java对象的操作。
①首先定义一个对象,并将该对象纳入Spring管控的范围,也就是定义成一个bean,然后使用注解@ConfigurationProperties指定该对象加载哪一组yaml中配置的信息。
@Component@ConfigurationProperties(prefix = "datasource")public class MyDataSource { private String driver; private String url; private String username; private String password;
@Override public String toString() { return "MyDataSource{" + "driver='" + driver + '\'' + ", url='" + url + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; }
public String getDriver() { return driver; }
public void setDriver(String driver) { this.driver = driver; }
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }}这个@ConfigurationProperties必须告诉他加载的数据前缀是什么,这样当前前缀下的所有属性就封装到这个对象中。记得数据属性名要与对象的变量名一一对应啊,不然没法封装。其实以后如果你要定义一组数据自己使用,就可以先写一个对象,然后定义好属性,下面到配置中根据这个格式书写即可。
datasource: driver: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost username: root password: 131411
5.6 yaml配置中遇到的问题
①配置yml文件时,设置键名为大写报错:
dataSource: driver: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost username: root password: 131411
Invalid characters: 'S' Bean: userController Reason: Canonical names should be kebab-case ('-' separated), lowercase alpha-numeric characters and must start with a letter无效字符:'S'Bean:userController原因:规范名称应为 kebab-case('-' 分隔)、小写字母数字字符且必须以字母开头6 Spring Boot整合Junit
Spring整合JUnit的制作方式
@ContextConfiguration("classpath:applicationContext.xml")@RunWith(SpringJUnit4ClassRunner.class)public class TransactionManagerTest { @Autowired private AccountService accountService;
@Test public void test() { accountService.transfer(); }}@RunWith是设置Spring专用于测试的类运行器,简单说就是Spring程序执行程序有自己的一套独立的运行程序的方式,不能使用JUnit提供的类运行方式了,必须指定一下。
@ContextConfiguration是用来设置Spring核心配置文件或配置类的,简单说就是加载Spring的环境你要告诉Spring具体的环境配置是在哪里写的。
SpringBoot就抓住上述两条没有技术含量的内容书写进行开发简化,能走默认值的走默认值,能不写的就不写,具体格式如下:
①导入test的starter坐标
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope></dependency>②使用一个注解@SpringBootTest替换了前面两个注解。
@SpringBootTestclass SpringDemo1ApplicationTests {
@Autowired private BookMapper bookMapper;
@Test void contextLoads() { bookMapper.save(); }
}7 Spring Boot整合Mybatis
①创建模块时勾选要使用的技术,MyBatis,由于要操作数据库,还要勾选对应数据库
②配置数据源相关信息
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test username: root password: 131411③使用Lombok创建实体类
@Datapublic class User { private Integer id; private String username; private String password; private String gender; private String address;}④创建映射接口
@Mapperpublic interface UserMapper { @Select("select * from tb_user") List<User> findAll(); @Insert("insert into tb_user(username, password, gender, address) values (#{username},#{password},#{gender},#{address})") void insert(User user); @Update("update tb_user set username = #{username} where id=#{id}") int update(@Param("username") String username, @Param("id") int id); @Delete("delete from tb_user where id=#{id}") void delete(int id);}⑤进行测试
@SpringBootTestclass SpringBootMybatisApplicationTests {
@Autowired private UserMapper userMapper;
@Test void contextLoads() { List<User> userList = userMapper.findAll(); for (User user : userList) { System.out.println(user); } } @Test void userInserts() { User user = new User(); user.setUsername("刘畅"); user.setPassword("1314"); user.setGender("男"); user.setAddress("河南"); userMapper.insert(user); } @Test void userUpdates() { userMapper.update("六珍惜", 1); }
@Test void userDeletes() { userMapper.delete(1); }}⑥部分测试结果
8 Spring Boot整合Mybatis-plus
①导入对应的starter
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter --><dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version></dependency>②配置数据源相关信息
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test username: root password: 131411③创建映射接口
@Mapperpublic interface UserMapper extends BaseMapper<User> {
}④进行测试
@SpringBootTestclass SpringBootMybatisplusApplicationTests {
@Autowired private UserMapper userMapper; @Test void contextLoads() { List<User> userList = userMapper.selectList(null); for (User user : userList) { System.out.println(user); } } @Test void findUserById(){ User user = userMapper.selectById(1); System.out.println(user); }}9 Spring Boot整合druid数据源
①导入对应的starter
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.11</version></dependency>②配置数据源相关信息
spring: datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test username: root password: 131411③创建映射接口
@Mapperpublic interface UserMapper { @Select("select * from tb_user") List<User> findAll(); @Insert("insert into tb_user(username, password, gender, address) values (#{username},#{password},#{gender},#{address})") void insert(User user); @Update("update tb_user set username = #{username} where id=#{id}") int update(@Param("username") String username, @Param("id") int id); @Delete("delete from tb_user where id=#{id}") void delete(int id);}④进行测试
@SpringBootTestclass SpringBootMybatisApplicationTests {
@Autowired private UserMapper userMapper;
@Test void contextLoads() { List<User> userList = userMapper.findAll(); for (User user : userList) { System.out.println(user); } } @Test void userInserts() { User user = new User(); user.setUsername("刘畅"); user.setPassword("1314"); user.setGender("男"); user.setAddress("河南"); userMapper.insert(user); } @Test void userUpdates() { userMapper.update("六珍惜", 1); }
@Test void userDeletes() { userMapper.delete(1); }}学习总结:
本周学习进度有些慢,本周主要学习了Spring Boot基础篇的相关知识。通过本次的学习,对SpringMVC的相关知识更加的熟悉;通过进行SSM整合,使用注解开发Spring程序更加得心应手;对于Spring Boot的学习,必须要打牢Spring的基础,只要有牢固的Spring基础,SpringBoot的开发更加的简单。
下周学习计划:
完成Spring Boot基础篇、运维实用篇等课程。由于临近开学,需要整理解决一些开学事务,导致学习时间有所减少,SpringBoot的课程在开学前无法学习完毕,不过在开学后会尽快完成任务,达到Java全栈初级工程师水平。继续努力!
SpringBoot学习笔记 2
1 基于Spring Boot整合SSMP
1.1 模块创建
导入MyBatisPlus与Druid对应的starter,当然mysql的驱动不能少
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version></dependency><dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version></dependency>1.2 数据层开发
①创建实体类:User
@Datapublic class User { private Integer id; private String username; private String password; private String gender; private String address;}②配置数据源
server: port: 80
spring: datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test username: root password: 131411
mybatis-plus: global-config: db-config: table-prefix: tb_③编写映射接口
@Mapperpublic interface UserMapper extends BaseMapper<User> {}④测试
@SpringBootTestpublic class UserMapperTest { @Autowired private UserMapper userMapper;
@Test public void test(){ List<User> userList = userMapper.selectList(null); for (User user : userList) { System.out.println(user); } } @Test public void test2(){ User user = userMapper.selectById(1); System.out.println(user); } @Test public void test3(){ User user = new User(); user.setUsername("齐大"); user.setPassword("0000"); user.setGender("男"); user.setAddress("齐齐哈尔"); userMapper.insert(user); } @Test public void test4(){ userMapper.deleteById(191561735); }
@Test public void test5(){ User user = new User(); user.setId(15); user.setUsername("郑大"); user.setPassword("0000"); user.setGender("男"); user.setAddress("河南郑州"); userMapper.updateById(user); }}**发现问题一:**自动增长的Id值太大。
解决方式一:通过配置的方式:
mybatis-plus: global-config: db-config: table-prefix: tb_ id-type: auto #设置主键id字段的生成策略为参照数据库设定的策略,当前数据库设置id生成策略为自增解决方式二:通过注解:
在实体类User类的id属性上添加此注解。
@TableId(value = "id", type = IdType.AUTO)private Integer id;
**发现问题二:**控制台没有打印SQL语句,不利于查看
解决方式:通过配置的形式就可以查阅执行期SQL语句,配置如下:
mybatis-plus: global-config: db-config: table-prefix: tb_ id-type: auto configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启打印日志
⑤分页查询测试
前面仅仅是使用了MP提供的基础CRUD功能,实际上MP给我们提供了几乎所有的基础操作。
其中selectPage方法需要传入一个封装分页数据的对象,可以通过new的形式创建这个对象,当然这个对象也是MP提供的。创建此对象时就需要指定分页的两个基本数据
- 当前显示第几页
- 每页显示几条数据
可以通过创建Page对象时利用构造方法初始化这两个数据
IPage page = new Page(2,5);将该对象传入到查询方法selectPage后,可以得到查询结果,但是我们会发现当前操作查询结果返回值仍然是一个IPage对象。
IPage page = bookDao.selectPage(page, null);原来这个IPage对象中封装了若干个数据,而查询的结果作为IPage对象封装的一个数据存在的,可以理解为查询结果得到后,又塞到了这个IPage对象中,其实还是为了高度的封装,一个IPage描述了分页所有的信息。下面5个操作就是IPage对象中封装的所有信息了。
@Testpublic void test6() { IPage<User> page = new Page<User>(1,5); userMapper.selectPage(page,null); System.out.println(page.getCurrent()); System.out.println(page.getSize()); System.out.println(page.getPages()); System.out.println(page.getTotal()); System.out.println(page.getRecords());}到这里就知道这些数据如何获取了,但是当你去执行这个操作时,你会发现并不像我们分析的这样,实际上这个分页当前是无效的。为什么这样呢?这个要源于MP的内部机制。
对于MySQL的分页操作使用limit关键字进行,而并不是所有的数据库都使用limit关键字实现的,这个时候MP为了制作的兼容性强,将分页操作设置为基础查询操作的升级版,你可以理解为IPhone6与IPhone6S-PLUS的关系。
基础操作中有查询全部的功能,而在这个基础上只需要升级一下(PLUS)就可以得到分页操作。所以MP将分页操作做成了一个开关,你用分页功能就把开关开启,不用就不需要开启这个开关。而我们现在没有开启这个开关,所以分页操作是没有的。这个开关是通过MP的拦截器的形式存在的,其中的原理这里不分析了,有兴趣的小伙伴可以学习MyBatisPlus这门课程进行详细解读。具体设置方式如下
定义MP拦截器并将其设置为Spring管控的bean
@Configurationpublic class MPConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; }}上述代码第一行是创建MP的拦截器栈,这个时候拦截器栈中没有具体的拦截器,第二行是初始化了分页拦截器,并添加到拦截器栈中。如果后期开发其他功能,需要添加全新的拦截器,按照第二行的格式继续add进去新的拦截器就可以了。
⑥按条件查询
模糊匹配对应的操作,由like条件书写变为了like方法的调用。
@Testpublic void test7() { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.like("username","刘"); userMapper.selectList(queryWrapper);}第一句QueryWrapper对象是一个用于封装查询条件的对象,该对象可以动态使用API调用的方法添加条件,最终转化成对应的SQL语句。第二句就是一个条件了,需要什么条件,使用QueryWapper对象直接调用对应操作即可。比如做大于小于关系,就可以使用lt或gt方法,等于使用eq方法,等等。
这组API使用还是比较简单的,但是关于属性字段名的书写存在着安全隐患,比如查询字段name,当前是以字符串的形态书写的,万一写错,编译器还没有办法发现,只能将问题抛到运行器通过异常堆栈告诉开发者,不太友好。MP针对字段检查进行了功能升级,全面支持Lambda表达式,就有了下面这组API。由QueryWrapper对象升级为LambdaQueryWrapper对象,这下就解决了上述问题的出现。
@Testpublic void test8() { String name = "liu"; LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.like(name!=null,User::getUsername,name); userMapper.selectList(queryWrapper);}
1.3 业务层开发
传统业务层开发
①业务层接口定义
public interface UserService { Boolean save(User user); Boolean update(User user); Boolean delete(Integer id); User findById(Integer id); List<User> getUsers(); IPage<User> getPages(int currentPage, int pageSize);}②业务层实现类如下,转调数据层即可
@Servicepublic class UserServiceImpl implements UserService {
@Autowired private UserMapper userMapper;
@Override public Boolean save(User user) { return userMapper.insert(user) > 0; }
@Override public Boolean update(User user) { return userMapper.updateById(user) > 0; }
@Override public Boolean delete(Integer id) { return userMapper.deleteById(user) > 0; }
@Override public User findById(Integer id) { return userMapper.selectById(id); }
@Override public List<User> getUsers() { return userMapper.selectList(null); }
@Override public IPage<User> getPages(int currentPage, int pageSize) { Page<User> page = new Page<>(); return userMapper.selectPage(page,null); }}③对业务层接口进行测试
@SpringBootTestpublic class UserServiceTest {
@Autowired private UserService userService;
@Test public void test() { System.out.println(userService.findById(1)); } @Test public void test2() { IPage<User> page = userService.getPages(1, 5); System.out.println(page.getRecords()); } @Test public void test3() { List<User> userList = userService.getUsers(); for (User user : userList) { System.out.println(user); } } @Test public void test4() { User user = new User(); user.setId(15); user.setUsername("郑大"); user.setPassword("0000"); user.setGender("男"); user.setAddress("河南郑州"); userService.save(user); } @Test public void test5() { User user = new User(); user.setId(15); user.setUsername("he"); user.setPassword("0000"); user.setGender("男"); user.setAddress("河南郑州"); userService.update(user); }}使用mybatis-plus业务层快速开发
MP技术不仅提供了数据层快速开发方案,业务层MP也给了一个通用接口,实际开发慎用。
①业务层接口快速开发
public interface IUserService extends IService<User> {}②业务层接口实现类快速开发,关注继承的类需要传入两个泛型,一个是数据层接口,另一个是实体类
@Servicepublic class IUserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}③对业务层快速开发接口进行测试
@SpringBootTestpublic class IUserServiceTest {
@Autowired private IUserService userService;
@Test public void test() {
System.out.println(userService.getById(1)); } @Test public void test2() { IPage<User> page = new Page<User>(1, 5); userService.page(page); System.out.println(page.getRecords()); } @Test public void test3() { List<User> userList = userService.list(); for (User user : userList) { System.out.println(user); } } @Test public void test4() { User user = new User(); user.setId(15); user.setUsername("郑大"); user.setPassword("0000"); user.setGender("男"); user.setAddress("河南郑州"); userService.save(user); } @Test public void test5() { User user = new User(); user.setId(15); user.setUsername("he"); user.setPassword("0000"); user.setGender("男"); user.setAddress("河南郑州"); userService.updateById(user); } @Test public void test6() { userService.removeById(16); }}1.4 表现层开发
不规范的表现层开发如下
①编写表现层相关方法
@RestController@RequestMapping("/users")public class UserController {
@Autowired private IUserService userService;
@GetMapping public List<User> getUsers() { return userService.list(); }
@PostMapping public Boolean save(@RequestBody User user) { return userService.save(user); }
@PutMapping public Boolean update(@RequestBody User user) { return userService.updateById(user); }
@DeleteMapping("/{id}") public Boolean delete(@PathVariable Integer id) { return userService.removeById(id); } @GetMapping("/{id}") public User getUser(@PathVariable Integer id) { return userService.getById(id); }
@GetMapping("/{currentPage}/{pageSize}") public IPage<User> getPages(@PathVariable int currentPage,@PathVariable int pageSize) { return userService.getPages(currentPage, pageSize); }}②使用PostMan进行测试:
为什么说是不规范呢?
目前我们通过Postman测试后业务层接口功能时通的,但是这样的结果给到前端开发者会出现一个小问题。不同的操作结果所展示的数据格式差异化严重。每种不同操作返回的数据格式都不一样,而且还不知道以后还会有什么格式,这样的结果让前端人员看了是很容易让人崩溃的,必须将所有操作的操作结果数据格式统一起来,需要设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议。
规范表现层的开发
①创建一个标准的返回类型R,其中flag用于标识操作是否成功,data用于封装操作数据
package com.liu.controller.utils;
import lombok.Data;
/** * @author LiuChang * @version 1.0.0 * @description TODO * @date 2022/8/26 10:03 */@Datapublic class R { private boolean flag; private Object data;
public R() { }
public R(boolean flag) { this.flag = flag; }
public R(boolean flag, Object data) { this.flag = flag; this.data = data; }}②规范表现层方法
@RestController@RequestMapping("/users")public class UserController {
@Autowired private IUserService userService;
@GetMapping public R getUsers() { return new R(true,userService.list()); }
@PostMapping public R save(@RequestBody User user) { return new R(userService.save(user)); }
@PutMapping public R update(@RequestBody User user) { return new R(userService.updateById(user)); }
@DeleteMapping("/{id}") public R delete(@PathVariable Integer id) { return new R(userService.removeById(id)); }
@GetMapping("/{id}") public R getUser(@PathVariable Integer id) { return new R(true,userService.getById(id)); }
@GetMapping("/{currentPage}/{pageSize}") public R getPages(@PathVariable int currentPage,@PathVariable int pageSize) { return new R(true,userService.getPages(currentPage, pageSize)); }}1.5 页面基础功能开发
①查询所有功能
页面添加条件字段对应的数据模型绑定名称
<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="username" label="用户名称" align="center"></el-table-column> <el-table-column prop="password" label="用户密码" align="center"></el-table-column> <el-table-column prop="gender" label="用户性别" align="center"></el-table-column> <el-table-column prop="address" 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>页面封装字段
data:{ dataList: [],//当前页要展示的列表数据}编写方法获取数据
//钩子函数,VUE对象初始化完成后自动执行created() { this.getAll();},//列表getAll() { axios.get("/users").then((res)=>{ console.log(res.data); this.dataList = res.data.data; })},②添加功能
页面添加条件字段对应的数据模型绑定名称
<!-- 新增标签弹层 --><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="username"> <el-input v-model="formData.username"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="用户密码" prop="password"> <el-input v-model="formData.password"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="用户性别" prop="gender"> <el-input v-model="formData.gender"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="地址"> <el-input v-model="formData.address" type="address"></el-input> </el-form-item> </el-col> </el-row> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="cancel()">取消</el-button> <el-button type="primary" @click="handleAdd()">确定</el-button> </div> </el-dialog></div>页面封装字段
data:{ dialogFormVisible: false,//添加表单是否可见 formData: {},//表单数据}编写方法插入数据
//弹出添加窗口handleCreate() { this.dialogFormVisible = true; this.resetForm();},
//重置表单resetForm() { this.formData = {};},
//添加handleAdd () { axios.post("/users",this.formData).then((res)=> { //判断当前操作是否成功 if (res.data.flag) { this.dialogFormVisible = false; this.$message.success("添加成功"); }else { this.$message.error("添加失败"); } }).finally(()=> { this.getAll(); })},
//取消cancel(){ this.dialogFormVisible = false; this.$message.info("操作取消");},③删除功能
页面添加条件字段对应的数据模型绑定名称
<el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>编写方法删除数据
// 删除handleDelete(row) { console.log(row);
this.$confirm("是否删除?","提示",{type:"info",}).then(()=>{ axios.delete("/users/"+row.id).then((res)=>{ if (res.data.flag) { this.$message.success("删除成功"); }else { this.$message.error("删除失败"); } }).finally(()=> { this.getAll(); }) }).catch(()=>{ this.$message.info("取消操作"); })},④修改操作
页面添加条件字段对应的数据模型绑定名称
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button> <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="username"> <el-input v-model="formData.username"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="用户密码" prop="password"> <el-input v-model="formData.password"/> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="用户性别" prop="gender"> <el-input v-model="formData.gender"/> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="地址"> <el-input v-model="formData.address" type="address"></el-input> </el-form-item> </el-col> </el-row> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="cancel()">取消</el-button> <el-button type="primary" @click="handleEdit()">确定</el-button> </div> </el-dialog> </div>页面封装字段
data:{ dialogFormVisible4Edit:false,//编辑表单是否可见}编写方法更新数据
//弹出编辑窗口handleUpdate(row) { axios.get("/users/" + row.id).then((res)=>{ if (res.data.flag && res.data.data!=null) { this.dialogFormVisible4Edit = true; this.formData = res.data.data; }else { this.$message.error("数据同步失败,自动刷新"); } }).finally(()=> { this.getAll(); });},
//修改handleEdit() { axios.put("/users",this.formData).then((res)=> { //判断当前操作是否成功 if (res.data.flag) { this.dialogFormVisible4Edit = false; this.$message.success("修改成功"); }else { this.$message.error("修改失败"); } }).finally(()=> { this.getAll(); })},⑤分页查询
页面添加条件字段对应的数据模型绑定名称
<!--分页组件--><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>页面封装字段
data:{ pagination: {//分页相关模型数据 currentPage: 1,//当前页码 pageSize:10,//每页显示的记录数 total:0,//总记录数}编写方法分页查询数据
//列表getAll() { axios.get("/users/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((res)=>{ this.pagination.currentPage = res.data.data.current; this.pagination.pageSize = res.data.data.size; this.pagination.total = res.data.data.total; this.dataList = res.data.data.records; })},//切换页码handleCurrentChange(currentPage) { this.pagination.currentPage = currentPage; this.getAll();⑥条件查询
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.like(Strings.isNotEmpty(user.getUsername()),User::getUsername,user.getUsername());queryWrapper.like(Strings.isNotEmpty(user.getPassword()),User::getPassword,user.getPassword());queryWrapper.like(Strings.isNotEmpty(user.getGender()),User::getGender,user.getGender());queryWrapper.like(Strings.isNotEmpty(user.getAddress()),User::getAddress,user.getAddress());getAll() { //1.获取查询条件,拼接查询条件 param = "?username="+this.pagination.username; param += "&password="+this.pagination.password; param += "&gender="+this.pagination.gender; param += "&address="+this.pagination.address; axios.get("/users/"+this.pagination.currentPage+"/"+this.pagination.pageSize+param).then((res)=>{ this.pagination.currentPage = res.data.data.current; this.pagination.pageSize = res.data.data.size; this.pagination.total = res.data.data.total; this.dataList = res.data.data.records; })},2 Spring Boot项目打包
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration></plugin>
Spring Boot的jar包在Window环境中运行:
Spring Boot的jar包在Linux环境中运行:
先查看了自己的ubuntu系统版本号:
进入IDEA查看Maven自己是否配置成功:
查看java版本号:
使用Linux环境运行SpringBoot的jar包:
运行成功之后发现了相关错误:可以看到是由于用户权限不够,于是使用sudo加权
加权后发现本数据库中没有相关表,于是直接运行相关sql语句:
再次运行可以发现项目已经成功跑出来了:
可以看到后台输出的日志:
3 临时属性配置
SpringBoot提供了灵活的配置方式,如果你发现你的项目中有个别属性需要重新配置,可以使用临时属性的方式快速修改某些配置。
- 在启动的时候添加上对应参数就可以了。
- 打开SpringBoot引导类的运行界面,在里面找到配置项。其中Program arguments对应的位置就是添加临时属性的。
对于IDEA没有出现的需要在Add中寻找:
可以看到端口号已经修改成了8080端口:
- 也可以在启动类中进行相关设置:
4 SpringBoot高级配置
4.1 SpringBoot加载第三方Bean
使用@ConfigurationProperties注解其实可以为第三方bean加载属性。
@Component@Data@ConfigurationProperties(prefix = "servers")public class ServletConfig { private String ipAddress; private int port; private long timeout;}servers: ipAddress: 192.168.1.2 port: 8080 timeout: 30
datasource: driverClassName: com.mysql.jdbc.cj.Driver@SpringBootApplicationpublic class Springboot13configurationApplication { //加载第三方Bean @Bean @ConfigurationProperties(prefix = "datasource") public DruidDataSource dataSource(){ DruidDataSource dataSource = new DruidDataSource(); return dataSource; } public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Springboot13configurationApplication.class, args); ServletConfig config = context.getBean(ServletConfig.class); System.out.println(config); DruidDataSource dataSource = context.getBean(DruidDataSource.class); System.out.println(dataSource.getDriverClassName()); }}
绑定第三方数据源:
@DurationUnit(ChronoUnit.HOURS)private Duration serverTimeout;@DataSizeUnit(DataUnit.KILOBYTES)private DataSize dataSize;servers: ipAddress: 192.168.1.2 port: 8080 timeout: 30 serverTimeout: 3 dataSize: 128
4.2 校验配置
SpringBoot给出了强大的数据校验功能,可以有效的避免此类问题的发生。在JAVAEE的JSR303规范中给出了具体的数据校验标准,开发者可以根据自己的需要选择对应的校验框架,此处使用Hibernate提供的校验框架来作为实现进行数据校验。
<!--导入JSP303规范--><dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId></dependency><dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId></dependency>@Component@Data@ConfigurationProperties(prefix = "servers")//开启对当前Bean的属性注入校验@Validatedpublic class ServletConfig { private String ipAddress; @Max(value = 8888,message = "最大值不能超过8888") @Min(value = 202,message = "最小值不能小于202") private int port; private long timeout; @DurationUnit(ChronoUnit.HOURS) private Duration serverTimeout; @DataSizeUnit(DataUnit.KILOBYTES) private DataSize dataSize;}
4.3 测试配置
加载测试专用配置
@Configurationpublic class MsgConfig { @Bean public String msg() { return "Bean msg"; }}@SpringBootTest@Import({MsgConfig.class})public class ConfigurationTest {
@Autowired private String msg;
@Test void test1(){ System.out.println(msg); }}测试类中启动web环境
@RestController@RequestMapping("/books")public class BookController { @GetMapping public String getById(){ System.out.println("getById() called"); return "springboot"; }}//开启web环境@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)//开启虚拟调用MVC@AutoConfigureMockMvcpublic class WebTest { @Test void testWeb(@Autowired MockMvc mvc) throws Exception { //执行get请求 MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books"); mvc.perform(builder); }}
匹配响应执行状态
@Testvoid testStatus(@Autowired MockMvc mvc) throws Exception { MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books1"); ResultActions actions = mvc.perform(builder); //设定预期值,与真实的值进行比较,成功测试通过,失败测试失败 StatusResultMatchers status = MockMvcResultMatchers.status(); //预计本次调用时成功的:状态码为200 ResultMatcher ok = status.isOk(); //添加预计值到本次调用过程中进行匹配 actions.andExpect(ok);}
匹配响应体
@Testvoid testBody(@Autowired MockMvc mvc) throws Exception { MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books"); ResultActions actions = mvc.perform(builder); //设定预期值,与真实的值进行比较,成功测试通过,失败测试失败 ContentResultMatchers content = MockMvcResultMatchers.content(); //预计本次调用时成功的:body ResultMatcher result = content.string("springboot1"); //添加预计值到本次调用过程中进行匹配 actions.andExpect(result);}
匹配json响应体格式:
@Testvoid testBody(@Autowired MockMvc mvc) throws Exception { MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books"); ResultActions actions = mvc.perform(builder); //设定预期值,与真实的值进行比较,成功测试通过,失败测试失败 ContentResultMatchers content = MockMvcResultMatchers.content(); //预计本次调用时成功的:body ResultMatcher result = content.json("{\"name\":\"瓦尔登湖\",\"id\":1,\"type\":\"自然科学\",\"description\":\"一位隐士记录生活\"}"); //添加预计值到本次调用过程中进行匹配 actions.andExpect(result);}匹配响应头
@Testvoid testContentType(@Autowired MockMvc mvc) throws Exception { MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books"); ResultActions actions = mvc.perform(builder); //设定预期值,与真实的值进行比较,成功测试通过,失败测试失败 HeaderResultMatchers header = MockMvcResultMatchers.header(); ResultMatcher matcher = header.string("Content-Type", "application/json"); //添加预计值到本次调用过程中进行匹配 actions.andExpect(matcher);}业务层测试事务回滚
@SpringBootTest//Spring添加事务注解@Transactional@Rollback(true)class SpringBootMybatisApplicationTests {
@Autowired private UserMapper userMapper;
@Test void userInserts() { User user = new User(); user.setUsername("刘畅"); user.setPassword("1314"); user.setGender("男"); user.setAddress("河南"); userMapper.insert(user); }}测试用例设置随机数据
testcase: user: id: ${random.int} username: ${random.value} password: ${random.value} gender: ${random.value} address: ${random.value}@SpringBootTest//Spring添加事务注解@Transactional@Rollback(true)class SpringBootMybatisApplicationTests {
@Autowired private UserMapper userMapper;
@Autowired private User user;
@Test public void test(){ System.out.println(user.getId()); System.out.println(user.getUsername()); System.out.println(user.getPassword()); System.out.println(user.getGender()); System.out.println(user.getAddress()); }}
4.4 数据层解决方案
配置内嵌Hikari数据源
spring: datasource: url: jdbc:mysql://localhost:3306/test hikari: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 131411
配置内嵌 H2数据库:
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId></dependency>spring: h2: console: path: /h2 enabled: true datasource: url: jdbc:h2:~/test hikari: driver-class-name: org.h2.Driver username: sa password: 123456mybatis-plus: global-config: db-config: table-prefix: tb_server: port: 80连接H2数据库:
进入H2数据库界面:
查询表操作:
创建表操作:
插入表数据操作:
查询所有操作:
发生报错:
Database may be already in use: "C:/Users/77339/test.mv.db". Possible solutions: close all other connection(s); use the server mode [90020-214]经过查询发现:是数据库被占用,将服务器停掉之后,问题就解决掉了。
NoSQL:
Redis与SpringBoot框架结合:
创建新模块,选择Redis缓存。
spring: redis: host: localhost port: 6379 client-type: jedis@SpringBootTestclass SpringBootRedisApplicationTests {
@Autowired private RedisTemplate redisTemplate;
@Test void set() { ValueOperations ops = redisTemplate.opsForValue(); ops.set("age",23); }
@Test void get() { ValueOperations ops = redisTemplate.opsForValue(); Object age = ops.get("age"); System.out.println(age); }
@Test void setHash(){ HashOperations hash = redisTemplate.opsForHash(); hash.put("info","a","aa"); } @Test void getHash(){ HashOperations hash = redisTemplate.opsForHash(); Object o = hash.get("info", "a"); System.out.println(o); }
}@SpringBootTestpublic class StringRedis { @Autowired private StringRedisTemplate stringRedisTemplate;
@Test void get() { ValueOperations<String, String> ops = stringRedisTemplate.opsForValue(); String name = ops.get("name"); System.out.println(name); }}
ES分布式全文搜索引擎
1.启动ES
2.操作索引