SpringBoot入门
1、SpringBoot简介
SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程。
1.1、原生开发SpringMVC程序过程
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
/**
* 定义一个servlet容器启动的配置类,在里面加载spring的配置(需继承AbstractDispatcherServletInitializer,这样tomcat可以探测到这个类)
*/
public class ServletConfig extends AbstractDispatcherServletInitializer {
/*
* 注:Web容器(Tomcat)启动时,会加载SpringMvcConfig和SpringConfig配置类。
* 这两个配置类产生的容器对象不是同一个对象。
* SpringMVC容器能访问Spring容器,而Spring容器不能访问SpringMVC容器。
* */
// 加载SpringMVC容器配置
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}
// 拦截请求:设置哪些请求归属SpringMVC处理
@Override
protected String[] getServletMappings() {
return new String[]{"/"}; // 所有请求归SpringMVC进行处理
}
// 加载Spring容器的配置
@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
// 设置过滤器,处理中文乱码问题
@Override
protected Filter[] getServletFilters() {
// Post请求中文乱码处理:配置字符过滤器,为web容器添加过滤器并指定字符集,spring-web包中提供了专用的字符过滤器
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
@Configuration
@ComponentScan({"com.clp.service"})
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MyBatisConfig.class})
@EnableTransactionManagement // 开启事务管理器
public class SpringConfig {
}
@Configuration
@ComponentScan({"com.clp.controller"})
@EnableWebMvc // 开启由json数据转换成对象的功能
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private ProjectInterceptor projectInterceptor;
@Autowired
private ProjectInterceptor2 projectInterceptor2;
/**
* 配置拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// addPathPatterns()方法的参数表示拦截的url字符串匹配则进行拦截
// 拦截器链的执行顺序与添加顺序有关
registry.addInterceptor(projectInterceptor).addPathPatterns("/books", "/books/*");
registry.addInterceptor(projectInterceptor2).addPathPatterns("/books", "/books/*");
}
}
/*
* @RestController:设置当前控制器类为RESTful风格,等同于@Controller与@ResponseBody两个注解组合功能。
* @GetMapping & @PostMapping & @PutMapping & @DeleteMapping:
* 设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,例如@GetMapping对应GET请求。
* */
//@Controller
//@ResponseBody
@RestController // @RestController = @Controller + @ResponseBody
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService bookService;
// @RequestMapping(method = RequestMethod.POST)
@PostMapping // @PostMapping = @RequestMapping(method = RequestMethod.POST)
public Result post(@RequestBody Book book) {
boolean flag = bookService.save(book);
int i = 1 / 0;
return new Result(flag ? Code.SAVE_OK : Code.SAVE_ERR, flag);
}
// @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
@DeleteMapping("/{id}") // @DeleteMapping("/{id}") = @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public Result delete(@PathVariable Integer id) {
boolean flag = bookService.delete(id);
return new Result(flag ? Code.DELETE_OK : Code.DELETE_ERR, flag);
}
// @RequestMapping(method = RequestMethod.PUT)
@PutMapping // @PutMapping = @RequestMapping(method = RequestMethod.PUT)
public Result put(@RequestBody Book book) {
boolean flag = bookService.update(book);
return new Result(flag ? Code.UPDATE_OK : Code.UPDATE_ERR, flag);
}
// @RequestMapping(value = "/{id}}", method = RequestMethod.GET)
@GetMapping("/{id}") // @GetMapping("/{id}") = @RequestMapping(value = "/{id}}", method = RequestMethod.GET)
public Result get(@PathVariable Integer id) {
Book book = bookService.getById(id);
return new Result(
book != null ? Code.GET_OK : Code.GET_ERR,
book,
book != null ? "查询成功" : "查询失败"
);
}
// @RequestMapping(method = RequestMethod.GET)
@GetMapping // @GetMapping = @RequestMapping(method = RequestMethod.GET)
public Result getAll() {
List<Book> bookList = bookService.getAll();
return new Result(
bookList != null ? Code.GET_OK : Code.GET_ERR,
bookList,
bookList != null ? "查询成功" : "查询失败"
);
}
}
1.2、SpringBoot快速入门
1.2.1、创建SpringBoot项目
创建新模块,选择Spring初始化,并配置模块相关基础信息。然后选择当前模块需要使用的技术集。
1.2.2、开发控制器类
@RestController
@RequestMapping("/books")
public class BookController {
@GetMapping("/{id}")
public String getById(@PathVariable Integer id) {
System.out.println("id: " + id);
return "hello, springboot!";
}
}
1.2.3、运行自动生成的Application类(引导类)
@SpringBootApplication
public class Springboot0101QuickstartApplication {
public static void main(String[] args) {
//生成可配置的容器对象
ConfigurableApplicationContext ctx = SpringApplication.run(Springboot0101QuickstartApplication.class, args);
}
}
1.3、最简SpringBoot程序所包含的基础文件
1、pom.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.11</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.clp</groupId>
<artifactId>module_01_quickstart</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>module_01_quickstart</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
SpringBoot中常见项目名称,定义了当前项目使用的所有坐标依赖,以达到减少依赖配置的目的。
- 开发SpringBoot程序需要导入坐标时通常导入对应的starter;
- 每个不同的starter根据功能不同,通常包含多个依赖坐标;
- 使用starter可以实现快速配置的效果,达到简化配置的目的。
实际开发:
- 使用任意坐标时,仅书写GAV中的G和A,V由SpringBoot提供,除非SpringBoot未提供对应版本的V;
- 如发生坐标错误,再指定Version(要小心版本冲突)。
2、Application类(引导类)
@SpringBootApplication
public class Module01QuickstartApplication {
public static void main(String[] args) {
SpringApplication.run(Module01QuickstartApplication.class, args);
}
}
SpringBoot的引导类是Boot工程的执行入口,运行main()方法就可以启动项目;SpringBoot工程运行后初始化Spring容器,扫描引导类所在包加载bean。
1.4、Spring程序与SpringBoot程序对比
类/配置文件 | Spring | SpringBoot |
pom文件中的坐标 | 手工添加 | 勾选添加 |
web 3.0 配置类 | 手工制作 | 无 |
Spring/Spring MVC 配置类 | 手工制作 | 无 |
控制器 | 手工制作 | 手工制作 |
注意事项:基于idea开发SpringBoot程序需要确保联网且能够加载到程序框架结构。
1.5、SpringBoot项目打包运行
步骤:
- 对SpringBoot项目打包(执行Maven构建指令package);
- 执行启动指令:java -jar springboot.jar;
注意:jar支持命令行启动需要依赖maven插件支持,请确认打包时是否具有SpringBoot对应的maven插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
1.6、SpringBoot内嵌tomcat
内嵌Tomcat服务器是SpringBoot辅助功能之一;内嵌Tomcat工作原理是将Tomcat服务器作为对象运行,并将该对象交给Spring容器管理;变更内嵌服务器思想是去除现有服务器,添加全新的服务器。
SpringBoot内嵌有三款服务器:
- tomcat(默认):apache出品,粉丝多,应用面广,负载了若干较重的组件。
- jetty:更轻量级,负载性能远不及tomcat。
- undertow:undertow,负载性能勉强跑赢tomcat。
1.7、SpringBoot依赖管理
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
1、spring-boot-starter-parent
- 开发SpringBoot程序要继承spring-boot-starter-parent,所有SpringBoot项目要继承的项目,定义了若干个坐标版本号(依赖管理,而非依赖),以达到减少依赖冲突的目的;
- spring-boot-starter-parent中定义了若干依赖管理,各版本间存在着诸多坐标版本不同;
- 继承parent模块可以避免多个依赖使用相同技术时出现依赖版本冲突;
- 继承parent的形式也可以采用引入依赖的形式实现效果。
2、SpringBoot程序启动方式
@SpringBootApplication
public class Module01QuickstartApplication {
public static void main(String[] args) {
SpringApplication.run(Module01QuickstartApplication.class, args);
}
}
SpringBoot在创建项目时,采用jar的打包方式。SpringBoot的引导类是项目的入口,运行main()方法就可以启动项目。
3、可以使用maven依赖管理变更起步依赖项
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter.web</artifactId>
<!-- 在web起步依赖环境中,排除Tomcat起步依赖 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加Jetty起步依赖,版本由SpringBoot的starter控制-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
</dependencies>
Jetty比Tomcat更轻量级,可扩展性更强(相较于Tomcat),谷歌应用引擎(GAE)已经全面切换为Jetty。
2、基础配置
2.1、属性配置
2.1.1、修改配置
SpringBoot提供了多种属性配置方式:
- application.properties;
在./resource/application.properties中: 用(什么技术就开什么配置) # 服务器的端口配置 server.port=80 # 修改banner(运行日志图标) #spring.main.banner-mode=off # 关闭运行日志图标 #spring.banner.image.location=logo.png # 设置日志相关(设置启动日志) logging.level.root=debug logging.level.com.clp=warn
- application.yml(常用);
server: port: 80
- application.yaml;
server: port:80
不同配置文件中相同配置按照加载优先级相互覆盖,不同配置文件中不同配置全部保留。加载优先级:application.properties -> application.yml -> application.yaml
2.2、YMAL配置
yaml:YAML(YAML,Ain't Markup Language):一种数据序列化格式。优点:① 容易阅读;② 容易与脚本语言交互;③ 以数据为核心,重数据轻格式。
2.2.1、yaml语法规则
(核心规则:数据前面要加空格与冒号隔开):
- 大小写敏感;
- 属性层级关系使用多行描述,每行结尾使用冒号结束;
- 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用tab键);
- 属性值前面添加空格(属性名与属性值之间用冒号+空格作为分隔);
- # 表示注释。
# 设置端口 server: port: 80 user: name: zhangsan age: 18 users1: - name: zhangsan age: 18 - name: lisi age: 17 - name: wangwu age: 20 users2: [{name: zhangsan, age: 17}, {name: lisi, age: 18}] a: d: name: zhangsan hobbies: - game - music - sleep hobbies2: [game, music, sleep]
2.2.2、数据读取
使用@Value读取单个数据,属性名引用方式:${一级属性名.二级属性名...}
package com.clp.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/books")
public class BookController {
/**
* country: beijing
*/
//读取yml数据中的单一数据
@Value("${country}")
private String country;
/**
* user0:
* name: zhangsan
* age: 18
*/
@Value("${user0.name}")
private String name;
/**
* users:
* - name: zhangsan
* age: 18
* - name: lisi
* age: 20
* @return
*/
@Value("${users[0].age}")
private int age;
@GetMapping
public String getById() {
System.out.println("springboot is running..");
System.out.println("country: " + country);
System.out.println("user.name: " + name);
System.out.println("users[0].age: " + age);
return "springboot is running...";
}
}
封装全部数据到Environment对象:
package com.clp.controller;
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired //使用自动装配将application.yml中所有的数据封装到一个对象Environment中
private Environment env;
@GetMapping
public String getById() {
System.out.println("springboot is running..");
System.out.println(env.getProperty("server.port"));
return "springboot is running...";
}
}
封装部分数据:(注意:封装类需要定义为Spring管理的bean,否则无法进行属性注入)
package com.clp;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* application.yml:
* # 创建类,用于封装下面的数据
* # 由spring帮我们去加载数据到对象中,一定要告诉spring加载这些信息
* # 使用的时候从spring中直接获取信息使用
* datasource:
* driver: com.mysql.jdbc.Driver
* url: jdbc:mysql://localhost/springboot_db
* username: root
* pasword: 123456
*
* 步骤:
* 1、定义数据模型封装yaml文件中对应的数据(变量名要和键的名称一致)
* 2、定义为spring管控的bean(使用@Component)
* 3、指定加载的数据(使用@ConfigurationProperties)
*/
@Component
@ConfigurationProperties(prefix = "datasource") //指定加载的数据
public class MyDataSource {
private String driver;
private String url;
private String username;
private String 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;
}
@Override
public String toString() {
return "MyDataSource{" +
"driver='" + driver + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
package com.clp.controller;
import com.clp.MyDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private MyDataSource myDataSource;
@GetMapping
public String getById() {
System.out.println("springboot is running..");
System.out.println("myDataSource: " +myDataSource);
return "springboot is running...";
}
}
2.2.3、yml文件中的变量引用
application.yml:
baseDir: C:\windows
# 使用${属性名}的方式引用数据
tempDir1: ${baseDir}\temp # \t不解析为制表符
# 使用双引号包裹的字符串,其中的转义字符可以生效
tempDir2: "${baseDir}\temp" # \t解析为制表符
2.3、多环境开发配置
多环境启动:application.yaml
spring:
profiles:
active: pro
---
spring: # 设置生产环境
profiles: pro
server: # 生产环境具体参数设定
port: 80
---
spring: # 设置开发环境
profiles: dev
server: # 开发环境具体参数设定
port: 81
---
spring: # 设置测试环境
profiles: test
server: # 测试环境具体参数设定
port:82
带参数启动SpringBoot:java -jar spring.jar --spring.profiles.active=test
2.3.1、Maven与SpringBoot多环境兼容
1、Maven中设置多环境属性:
<profiles>
<profile>
<id>dev_env</id>
<properties>
<profile.active>dev</profile.active>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>pro_env</id>
<properties>
<profile.active>pro</profile.active>
</properties>
</profile>
<profile>
<id>test_env</id>
<properties>
<profile.active>test</profile.active>
</properties>
</profile>
</profiles>
2、SpringBoot中引用Maven环境:
spring:
profiles:
active: ${profile.active}
---
spring: # 设置生产环境
profiles: pro
server: # 生产环境具体参数设定
port: 80
---
spring: # 设置开发环境
profiles: dev
server: # 开发环境具体参数设定
port: 81
---
spring: # 设置测试环境
profiles: test
server: # 测试环境具体参数设定
port:82
3、执行Maven打包指令
Maven指令执行完毕后,生成了对应的包,其中类参与编译,但是配置文件并没有编译,而是复制到包中。
- 复制前:
spring:
profiles:
active: ${profile.active}
- 复制后
spring:
profiles:
active: ${profile.active}
解决思路:对于源码中非Java类的操作要求加载Maven对应的属性,解析${}占位符。如下:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<encoding>utf-8</encoding>
<useDefaultDelimiters>true</useDefaultDelimiters>
</configuration>
</plugin>
2.3.2、配置文件分类
SpringBoot中4级配置文件:
- 1级:file : config/application.yml 【最高】
- 2级:file : application.yml
- 3级:classpath: config/application.yml
- 4级:classpath: application.yml 【最低】
作用:
- 1级与2级留作系统打包后设置通用属性;
- 3级与4级用于系统开发阶段设置通用属性。
3、整合第三方技术
3.1、整合JUnit
步骤:
- 导入对应的starter(创建工程时会自动导入);
<!--默认导入测试相关模块--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
- 测试类使用@SpringBootTest修饰;
- 使用自动装配的形式添加要测试的对象。
名称:@SpringBootTest
类型:测试类注解
位置:测试类定义上方
作用:设置JUnit加载的SpringBoot启动类
相关属性:classes:设置SpringBoot启动类。注意:如果测试类在SpringBoot启动类或子包中,可以省略启动类的设置,也就是省略classes的设定;测试类如果不存在于引导类所在的包或子包中需要通过classes属性指定引导类。
范例:
package com.clp;
import com.clp.dao.BookDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(classes = Springboot03JunitApplication.class) //显式地写上引导类
class Springboot03JunitApplicationTests {
/**
* 测试步骤:
* 1、注入你要测试的对象;
* 2、执行要测试的对象对应的方法
*/
@Autowired
private BookDao bookDao;
@Test
void contextLoads() {
bookDao.save();
}
}
3.2、整合MyBatis
核心配置:数据库连接相关信息(连什么?连谁?什么权限?)
映射配置:SQL映射(XML/注解)
步骤:
- 创建新模块,选择Spring初始化,并配置模块相关基础信息;选择当前模块需要使用的技术集Dependencies(MyBatis Framework、MySQL Driver);
<!-- SpringBoot 整合 MyBatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- 配置druid数据源 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency>
- 数据库连接相关信息转换成配置。设置数据源参数(在application.yml中):
spring: datasource: # 配置数据源 type: com.alibaba.druid.pool.DruidDataSource # 配置数据源类型:druid数据源 driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://${api-server.host}:3306/mydb?serverTimezone=UTC username: root password: 123456
- 数据库SQL映射需要添加@Mapper被容器识别到。定义数据层接口与映射配置:
package com.clp.dao; import com.clp.domain.Book; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; @Mapper public interface BookDao { @Select("select * from tbl_book where id = #{id}") public Book getById(Integer id); }
3.3、整合MyBatis-Plus
3.4、整合Druid
步骤:
- 导入Druid对应的starter;
<!--导入druid相关依赖--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency>
- 配置对应的设置或采用默认配置。变更Druid的配置方式如下:
# 方式1(推荐) spring: datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc.mysql://localhost:3306/mydb?serverTimezone=UTC username: root password: 123456 # 方式2 #spring: # datasource: # driver-class-name: com.mysql.jdbc.Driver # url: jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC # username: root # password: 123456 # type: com.alibaba.druid.pool.DruidDataSource
整合第三方技术的通用方式:① 导入对应的starter;② 根据提供的配置格式,配置非默认值对应的配置项。
4、基于SpringBoot的SSM整合案例
1、pom.xml:配置起步依赖,必要的资源坐标(druid);
2、application.yaml:设置数据源、端口等;
3、配置类:全部删除;
4、dao:设置@Mapper;
5、测试类
4、基于SpringBoot的SSMP整合案例
案例实现方案分析:
- 实体类开发——使用Lombok快速制作实体类;
- Dao开发——整合MyBatisPlus,制作数据层测试类;
- Service开发——基于MyBatisPlus进行增量开发,制作业务层测试类;
- Controller开发——基于Restful开发,使用PostMan测试接口功能;
- Controller开发——前后端开发协议制作;
- 页面开发——基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理(列表、新增、修改、删除、分页、查询);
- 项目异常处理;
- 按条件查询——页面功能调整、Controller修正功能、Service修正功能。
起步依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<artifactId>3.4.1</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
设置Jdbc参数(application.yml):
spring:
datasource: # 配置数据源
type: com.alibaba.druid.pool.DruidDataSource # 配置数据源类型:druid数据源
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${api-server.host}:3306/mydb?serverTimezone=UTC
username: root
password: 123456
注意:如果使用Druid数据源,需要导入对应坐标:
<!-- 配置druid数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
4.1、实体类快速开发
Lombok是一个Java类库,提供了一组注解,简化POJO实体类开发。
常用注解:
- @Data:为当前实体类在编译期设置对应的get() / set()方法,toString()方法, hashCode()方法,equals()方法等。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
4.2、数据层开发
技术实现方案:MyBatisPlus、Druid。
步骤:
- 导入MyBatisPlus与Druid对应的starter:
<!--因为在parent中没有维护下面这2个版本,所以需要手动加--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency>
- 配置数据源与MyBatisPlus对应的配置。(application.yml):
# 配置端口号 server: port: 80 # 配置druid数据源 spring: datasource: druid: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/db username: root password: 123456 # 配置表前缀 mybatis-plus: global-config: db-config: table-prefix: tbl_ id-type: auto #设置自增策略,即执行insert语句时,id增加的方式
- 开发Dao接口,并继承BaseMapper并指定泛型:
package com.clp.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.clp.domain.Book; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; @Mapper public interface BookDao extends BaseMapper<Book> { // /** // * MyBatis的使用方式 // * @param id // * @return // */ // @Select("select * from tbl_book where id = #{id}") // Book getById(Integer id); }
为了方便调试可以开启MyBatisPlus的日志:
# 配置表前缀
mybatis-plus:
configuration:
# 配置日志方式,设置日志输出方式为标准输出
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
4.2.1、分页
分页操作需要设定分页对象IPage。
/**
* 分页功能需要添加一个拦截器(使用配置类进行配置)
*/
@Test
void testGetPage() {
IPage page = new Page(2, 5);
// 使用IPage封装分页数据,分页操作依赖MyBatis分页拦截器实现功能
IPage iPage = bookDao.selectPage(page, null); //ipage就是page
System.out.println(iPage.getCurrent()); // 2 当前页码值
System.out.println(iPage.getSize()); // 5
System.out.println(iPage.getTotal()); // 16
System.out.println(iPage.getPages()); // 4 页数
System.out.println(iPage.getRecords()); //记录
}
IPage对象中封装了分页操作中的所有数据:
- 数据;
- 当前页码值;
- 每页数据总量;
- 最大页码值;
- 数据总量。
分页操作是在MyBatisPlus的常规操作基础上增强得到,内部是动态的拼写SQL语句,因此需要增强对应的功能,使用MyBatis拦截器实现。
/**
* (配置类)
*/
@Configuration
public class MPConfig {
/**
* 配置MyBatisPlus拦截器,交给SpringBoot管理
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//创建mybatis拦截器(壳)
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//添加拦截器1(分页拦截器)
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
4.2.2、条件查询
使用QueryWrapper对象封装查询条件,推荐使用LambdaQueryWrapper对象,所有查询操作封装成方法调用。查询条件支持动态条件封装。
MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合。
/**
* 条件查询
*/
@Test
void testGetBy() {
// 方式1
QueryWrapper<Book> qw = new QueryWrapper<>();
//select * from tbl_book where name like 'a';
qw.like("name", "a");
List<Book> books = bookDao.selectList(qw);
System.out.println(books);
//方式2 使用Lambda表达式
String name = "a";
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
//lqw.like(Book::getName, name);
// 如果name不为null,则进行查询
lqw.like(name != null, Book::getName, name);
bookDao.selectList(lqw);
}
@Test
void test() {
// 查询结果包含模型中部分属性
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.select(User::getId, User::getName, User::getAge);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
// 查询结果包含模型类中未定义的属性
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.select("count(*) as nums, gender");
qw.groupBy("gender");
List<Map<String, Object>> maps = userDao.selectMaps(qw);
System.out.println(maps);
}
@Test
void test2() {
// 用户登录(eq匹配)
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.eq(User::getName, userQuery.getName()).eq(User::getPassword, userQuery.getPassword());
User loginUser = userDao.selectOne(lqw);
System.out.println(loginUser);
// 购物设定价格区间、户籍设定年龄区间(le ge匹配 或 between 匹配)
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
// 方案1:设定上限下限
lqw.le(User::getAge, userQuery.getAge()).ge(User::getAge, userQuery.getAge2());
// 方案2:设定范围
lqw.between(User::getAge, userQuery.getAge(), userQuery.getAge2());
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
// 查信息、搜索新闻(非全文检索版:like匹配)
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.likeLeft(User::getTel, userQuery.getTel());
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
// 统计报表
QueryWapper<User> qw = new QueryWrapper<User>();
qw.select("gender", "count(*) as nums");
qw.groupBy("gender");
List<Map<String, Object>> maps = userDao.selectMaps(qw);
System.out.println(maps);
}
package com.clp.dao;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.clp.domain.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class BookDaoTest1Case {
@Autowired
private BookDao bookDao;
@Test
void testGetById() {
System.out.println(bookDao.selectById(1));
}
@Test
void save() {
Book book = new Book();
book.setType("测试数据123");
book.setName("测试数据123");
book.setDescription("测试数据123");
bookDao.insert(book);
}
@Test
void testUpdate() {
Book book = new Book();
book.setId(17);
book.setType("abcdefg");
book.setName("测试数据123");
book.setDescription("测试数据123");
bookDao.updateById(book);
}
@Test
void testDelete() {
bookDao.deleteById(16);
}
@Test
void testGetAll() {
List<Book> books = bookDao.selectList(null);
System.out.println(books);
}
/**
* 分页功能需要添加一个拦截器(使用配置类进行配置)
*/
@Test
void testGetPage() {
IPage page = new Page(2, 5);
IPage iPage = bookDao.selectPage(page, null); //ipage就是page
System.out.println(iPage.getCurrent()); // 2 当前页码值
System.out.println(iPage.getSize()); // 5
System.out.println(iPage.getTotal()); // 16
System.out.println(iPage.getPages()); // 4 页数
System.out.println(iPage.getRecords()); //记录
}
/**
* 条件查询
*/
@Test
void testGetBy() {
// 方式1
QueryWrapper<Book> qw = new QueryWrapper<>();
//select * from tbl_book where name like 'a';
qw.like("name", "a");
List<Book> books = bookDao.selectList(qw);
System.out.println(books);
//方式2 使用Lambda表达式
String name = "a";
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
//lqw.like(Book::getName, name);
// 如果name不为null,则进行查询
lqw.like(name != null, Book::getName, name);
bookDao.selectList(lqw);
}
}
4.2.3、字段映射与表名映射
- 问题1:表字段与编码属性设计不同步。解决方案:
名称:@TableField
类型:属性注解
位置:模型类属性定义上方
作用:设置当前属性对应的数据库表中的字段关系
范例:
public class User {
@TableField(value="pwd") // 对应的数据库字段名为 pwd
private String password;
}
相关属性:
value(默认):设置数据库表字段名称
exist:设置属性在数据库表字段中是否存在,默认为true。此属性无法与value合并使用
select:设置属性是否参与查询,此属性与select()映射配置不冲突
- 问题2:编码中添加了数据库中未定义的属性。解决方案:
public class User {
private Long id;
private String name;
@TableField(value="pwd");
private String password;
private Intager age;
@TableField(exist=false)
private Intager online;
}
- 问题3:采用默认查询开放了更多的字段查看权限:SELECT id, name, pwd, age, tel, speciality FROM user
public class User {
private Long id;
private String name;
@TableField(value="pwd", select=false); // 设置该字段不参与查询
private String password;
private Intager age;
@TableField(exist=false)
private Intager online;
}
- 问题4:表名与编码开发设计不同步。如表名为tbl_user,实体类名为User
@TableName("tbl_user") // 设置对应的数据库表名
public class User {
private Long id;
private String name;
@TableField(value="pwd", select=false); // 设置该字段不参与查询
private String password;
private Intager age;
@TableField(exist=false)
private Intager online;
}
4.2.4、id生成策略控制
不同的表应用不同的id生成策略:
- 日志:自增(1,2,3,4,...);
- 购物订单:特殊规则(FQ12345A415);
- 外卖单:关联地区日期等信息(10 04 20200314 34 91);
- 关系表:可省略id;
- ...
@TableId(type = IdType.类型),类型有如下几类:
AUTO(0):使用数据库id自增策略控制id
NONE(1):不设置id生成策略
INPUT(2):用户手工输入id
ASSIGN_ID(3):雪花算法生成id(可兼容数值型与字符串型)
ASSIGN_UUID(4):以UUID生成算法作为id生成策略
@TableName("tbl_user") // 设置对应的数据库表名
public class User {
@TableId(type = IdType.AUTO) // 自动递增的id生成策略
private Long id;
private String name;
@TableField(value="pwd", select = false); // 设置该字段不参与查询
private String password;
private Intager age;
@TableField(exist=false)
private Intager online;
}
4.2.5、多记录操作
- 按照主键删除多条记录
List<Long> ids = Arrays.asList(new Long[]{2, 3});
userDao.deleteBatchIds(ids);
- 根据主键查询多条记录
List<Long> ids = Arrays.asList(new Long[]{2, 3});
List<User> userList = userDao.selectBatchIds(ids);
4.2.6、逻辑删除
删除操作业务问题:业务数据从数据库中丢弃。
逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中。
步骤:
- 数据库表中添加逻辑删除标记字段(如deleted字段);
- 实体类中添加对应字段,并设定当前字段为逻辑删除标记字段。
@TableName("tbl_user") // 设置对应的数据库表名 public class User { @TableId(type = IdType.AUTO) // 自动递增的id生成策略 private Long id; private String name; @TableField(value="pwd", select = false); // 设置该字段不参与查询 private String password; private Intager age; @TableField(exist=false) private Intager online; // @TableLogic(value = "0", delval = "1") // 不在配置文件中配置时的逻辑删除字段,标记挡墙记录是否被删除。标记为0为未删除,标记为1为逻辑删除 @TableLogic() // 不指定value和delval的值,而是在配置文件中统一配置 private Integer deleted; }
- 如果不想给每个value和delval定义值,可以在配置文件中统一配置逻辑删除字面值:
mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-not-deleted-value: 0 logic-delete-value: 1
它实际会执行SQL语句:UPDATE tab_user SET deleted=1 WHERE id=? AND deleted=0
4.2.7、乐观锁
业务并发现象带来的问题:如秒杀业务。
步骤:
- 数据库表中添加锁标记字段;
- 实体类中添加对应字段,并设定当前字段为逻辑删除标记字段:
-
@TableName("tbl_user") // 设置对应的数据库表名 public class User { @TableId(type = IdType.AUTO) // 自动递增的id生成策略 private Long id; private String name; @TableField(value="pwd", select = false); // 设置该字段不参与查询 private String password; private Intager age; @TableField(exist=false) private Intager online; @TableLogic // 不指定value和delval的值,而是在配置文件中统一配置 private Integer deleted; @Version private Integer version; }
- 需要配置乐观锁拦截器实现锁机制对应的动态SQL语句拼装:
@Configuration public class MpConfig { @Bean public MybatisPlusInterceptor mpInterceptor() { // 1、定义Mp拦截器 MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor(); // 2、添加分页拦截器 mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 3、添加乐观锁拦截器 mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return mpInterceptor; } }
- 使用乐观锁机制在修改前必须先获取到对应数据的version方可正常进行:
@Test void testUpdate() { // 先查询数据,获取到version数据 User user = userDao.selectById(1L); // 执行数据修改操作 user.setName("Tom and Jerry"); userDao.updateById(user); }
4.2.8、代码生成器
模板:MyBatisPlus提供。
数据库相关配置:读取数据库获取信息。
开发自定义配置:手工配置。
先导入所需坐标:
pom.xml:
<!-- 代码生成器 -->
<depedency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>2.3</version>
</depedency>
<!-- velocity模板引擎 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
public class Generator {
public static void main(String[] args) {
// 1、获取代码生成器的对象
AutoGenerator autoGenerator = new AutoGenerator();
// 2、设置数据库相关配置
DataSourceConfig dataSource = new DataSourceConfig();
dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("123456");
sutoGenerator.setDataSource(dataSource);
// 3、设置全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir") + "/project/src/main/java"); // 设置输出代码的位置
globalConfig.setOpen(false); // 设置生成完毕后是否打开生成代码所在的目录
globalConfig.setAuthor("xxx"); // 设置作者
globalConfig.setFileOverride(true); // 设置是否覆盖原始生成的文件
globalConfig.setMapperName("%sDao"); // 设置数据层接口名,%s为占位符,指代模块名称
globalConfig.setIdType(IdType.ASSIGN_ID); // 设置Id生成策略
autoGenerator.setGlobalConfig(globalConfig);
// 4、设置包名相关配置
PackageConfig packageInfo = new PackageConfig();
packageInfo.setParent("com.itheima"); // 设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径
packageInfo.setEntity("domain"); // 设置实体类包名
packageInfo.setMapper("dao"); // 设置数据层包名
autoGenerator.setPackageInfo(packageInfo);
// 5、策略设置
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setInclude("tbl_user"); // 设置当前参与生成的表名,参数为可变参数
strategyConfig.setTablePrefix("tbl_"); // 设置数据库表的前缀名称,模块名 = 数据库名称 - 前缀名。例如:tbl_user => User
strategyConfig.setRestControllerStyle(true); // 设置是否启用Rest风格
strategyConfig.setVersionFieldName("version"); // 设置乐观锁字段名
strategyConfig.setLogicDeleteFieldName("deleted"); // 设置逻辑删除字段名
strategyConfig.setEntityLombokModel(true); // 设置是否启用lombok
autoGenerator.setStrategy(strategyConfig);
autoGenerator.execute();
}
}
4.3、业务层开发
Service层接口定义和数据层接口定义具有较大区别,不要混用。Service接口名称定义成业务名称,并与Dao接口名称进行区分。
package com.clp.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.clp.domain.Book;
import java.util.List;
public interface BookService {
Boolean save(Book book);
Boolean update(Book book);
Boolean delete(Integer id);
Book getById(Integer id);
List<Book> getAll();
IPage<Book> getPage(int currentPage, int pageSize);
}
package com.clp.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.clp.dao.BookDao;
import com.clp.domain.Book;
import com.clp.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 该注解将该类定义成业务层对应的Bean
*/
@Service
public class BookServiceImpl implements BookService {
@Autowired
BookDao bookDao;
@Override
public Boolean save(Book book) {
return bookDao.insert(book) > 0;
}
@Override
public Boolean update(Book book) {
return bookDao.updateById(book) > 0;
}
@Override
public Boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}
@Override
public Book getById(Integer id) {
return bookDao.getById(id);
}
@Override
public List<Book> getAll() {
return bookDao.selectList(null);
}
@Override
public IPage<Book> getPage(int currentPage, int pageSize) {
IPage page = new Page(currentPage, pageSize);
return bookDao.selectPage(page, null);
}
}
package com.clp.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.clp.domain.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class BookServiceTestCase {
@Autowired
private BookService bookService;
@Test
void testGetById() {
Book book = bookService.getById(4);
System.out.println(book);
}
@Test
void save() {
Book book = new Book();
book.setType("测试数据123");
book.setName("测试数据123");
book.setDescription("测试数据123");
bookService.save(book);
}
@Test
void testUpdate() {
Book book = new Book();
book.setId(17);
book.setType("abcdefg");
book.setName("测试数据123");
book.setDescription("测试数据123");
bookService.update(book);
}
@Test
void testDelete() {
bookService.delete(16);
}
@Test
void testGetAll() {
List<Book> books = bookService.getAll();
System.out.println(books);
}
/**
* 分页功能需要添加一个拦截器(使用配置类进行配置)
*/
@Test
void testGetPage() {
IPage iPage = bookService.getPage(2,5); //ipage就是page
System.out.println(iPage.getCurrent()); // 2 当前页码值
System.out.println(iPage.getSize()); // 5
System.out.println(iPage.getTotal()); // 16
System.out.println(iPage.getPages()); // 4 页数
System.out.println(iPage.getRecords()); //记录
}
}
4.3.1、业务层开发——快速开发
快速开发方案:
- 使用MyBatisPlus提供有业务通用接口(IService<T>)与业务层通用实现类(ServiceImpl<M,T>);
- 在通用类基础上做功能重载或功能追加;
- 注意重载时不要覆盖原始操作,避免原始提供的功能丢失。
package com.clp.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.clp.domain.Book;
public interface IBookService extends IService<Book> {
}
package com.clp.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.clp.dao.BookDao;
import com.clp.domain.Book;
import com.clp.service.IBookService;
import org.springframework.stereotype.Service;
@Service
public class IBookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
}
4.4、表现层开发
- 基于Resftul进行表现层接口开发;
- 使用Postman测试表现层接口功能。
实体数据:@RequestBody
路径变量:@PathVariable
package com.clp.controller;
import com.clp.domain.Book;
import com.clp.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private IBookService iBookService;
@GetMapping
public List<Book> getAll() {
return iBookService.list();
}
}
package com.clp.controller;
import com.clp.domain.Book;
import com.clp.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private IBookService iBookService;
@GetMapping
public List<Book> getAll() {
return iBookService.list();
}
@PostMapping
public Boolean save(@RequestBody Book book) {
return iBookService.save(book);
}
@PutMapping
public Boolean update(@RequestBody Book book) {
return iBookService.modify(book);
}
@DeleteMapping("{id}")
public Boolean delete(@PathVariable Integer id) {
return iBookService.delete(id);
}
/**
* http://localhost/books/2
*/
@GetMapping("{id}")
public Book getById(@PathVariable Integer id) {
return iBookService.getById(id);
}
}
4.4.1、表现层数据一致性处理