SpringBoot
SpringBoot
概念
-
什么是
SpringBoot
SpringBoot
提供了一种快速使用 Spring
的方式,基于约定优于配置的思想,可以让开发人员不必再配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期
创建 SpringBoot 项目
-
配置服务地址
Server URL:https://start.springboot.io/
-
选择
Spring-Web
依赖项 speing-web
-
Service URL
https://start.springboot.io/
(推荐)https://start.aliyun.com/
(存在springboot
版本问题,需要手动更新创建好项目的版本号) https://start.spring.io/
(官网提供)
-
以上两个地址都可以在浏览器内打开,
并通过向导完成项目初始化, 下载后可以导入到 IDEA
进行使用
-
pom
解析 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<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.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 本项目 -->
<groupId>com.coderitl</groupId>
<artifactId>springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot</name>
<description>springboot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- SpringBoot 框架 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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> -
注意点
1
SpringBoot 项目代码必须放到 SpringbootApplication 类所在的同级目录或下级目录
- 原因:
是由于 SpringBoot
的默认包扫描
- 原因:
-
目录结构
springboot
目录结构 -
创建一个
springmvc
的 controller
1
2
3
4
5
6
7
8
public class HelloWBoot {
public String doSome() {
return "Hello SpringBoot Application";
}
} -
启动信息查看
查看启动信息 -
直接去访问
控制器
的请求,直接查看是一个 Error
地址 直接访问显示的页面, 并不是启动失败 访问 spring-mvc
请求的 /doSome
-
核心配置文件两种形式
(两者同时出现以 properties
为主, yml
互补差异内容) -
application.properties
1
2
3
4
5
6
7# 设置内嵌 Tomcat 启动端口
server.port=8080 # 两者同时配置端口,以此处为优先
# 设置上下文 根
# server.servlet.context-path 如果未配置,将以 yml 作为互补内容
... -
application.yml
(推荐)1
2
3
4
5
6
7
8# http://localhost:8080/mydev/xxx
server:
port: 8080 # 设置内嵌 Tomcat 启动端口
servlet:
context-path: /mydev # 设置上下文路径
...
-
-
多环境配置
1
2
3
4
5
6
7# 主配置文件
spring:
profiles:
# 激活的配置文件名称
active: dev
# 包含其他配置文件
include: devDB,devRedis1
2
3
4
5
6# dev 环境
# 给配置文件起名字,让主配置引用
spring:
config:
activate:
on-profile: dev文件结构 -
SpringBoot
从 2.4
版本开始使用 group
属性代替 include
属性, 降低了配置书写量 1
2
3
4
5
6
7
8
9
10
11# 主配置文件对其他环境配置文件的引用
spring:
profiles:
# dev 和 group:dev 的需要对应
active: dev
# devDB 等为自定义名称,约定大于配置 (application-devDB 的格式)
group:
# devDB,devRedis 有加载优先级,靠后的配置会覆盖前面的配置
"dev": devDB,devRedis
"pro": proDB,proRedis
"test": testDB,testRedis
-
文件隐藏
文件隐藏
热部署配置
-
添加依赖
1
2
3
4
5
6<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency> -
配置
IDEA
配置项目自动编译 项目重启: 低版本 ctrl + alt + shift + / 2021.3.2
迁移到如下 -
重新启动
IDEA
RestFul
-
认识
REST(Representational State Transfer)简称
REST 一种互联网软件架构设计的风格,
但它不是标准, 它只是提出了一组客户端和服务器交互时的架构理念和设计原则, 基于这种理念和原则设计的接口可以更简洁, 更有层次 比如要访问一个
http
接口: http://localhost:8080/boot/order?id=2022$status=1
采用
RESTFul
风格则地址为: http://localhost:8080/boot/order/2022/1
-
@PathVariable
1
2
3
4
5
6
7
8
9
10
11
12/**
* 名称: @PathVariable
* 参数: 形参注解
* 位置: SpringMVC 控制器方法形参定义前面
* 作用: 绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名 一 一 对应
*/
public String getById( { Integer id)
System.out.println("user save..." + id);
return "{'message':'getById'}";
} -
@RequestBody、@RequestParam、@PathVariable
-
区别
@RequestParam(请求体参数)
用于接受 url
地址传参或表单传参 @RequestBody
用于接受 json
数据 ( POST
请求) @PathVariable
用于接受路径参数, 使用 {参数名称}
描述路径参数
-
应用
- 后期开发中,
发送请求参数超过 1
个时, 以 json
格式为主, @RequestBody
应用较广 - 如果发送非
json
格式数据, 选用 @RequestParam
接受请求参数 - 采用
RESTFul
进行开发, 当参数数量较少时, 例如一个,可以采用 @PathVariable
接受请求路径变量, 通常用于传递 id
值
- 后期开发中,
-
简化
简化 -
测试请求
请求测试 -
方法注解
1
2
3
4
5
6
7
8位置: 基于 springMVC 的 RESTFul 开发控制器方法定义上方
作用: 设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作
value(默认): 请求访问路径
-
单一属性读取
-
配置文件
1
2# 自定义单一属性
developmentEnvironment: IDEA2021.3.2-
读取
1
2
3
4
5
6
7
8
9
10// 读取 yml 单一数据 developmentEnvironment
private String devEnv;
...
{
return devEnv;
} -
结果
读取结果
-
-
yml
内容多层级 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15hexo:
categories:
- Java
- SpringBoot
ItUser:
name: [ coder-itl,student ] # 上述 hexo 可以写为数组形式
age: 18
dataSource:
driver: com.mysql.jdbc.Driver
url: localhost:3306/springdb
username: root
password: root
-
读取
分级结构读取一 分级结构读取二
-
yml
内部引用 1
2
3
4
5
6
7baseDir: c:\win11
tempDir: ${baseDir}\code
# 有转义字符的使用 单引号('') 包裹即可 等等
baseDir: 'a\tb'
输出: a b-
输出
内部引用
-
-
自动装配所有
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27package com.coderitl.springboot.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.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
public class UserController {
// 自动装配将所有的数据封装到一个对象 Environment 中
private Environment env;
// @RequestMapping(value = "/delete/{id}", method = RequestMethod.DELETE)
public String delete( { Integer id)
System.out.println("user delete..." + id);
// 获取输出必须先访问请求才可以看到对应结果
System.out.println(env.getProperty("server.port"));
return "code: 200";
}
}-
获取属性
属性获取
-
-
数据封装
1
2
3
4
5
6# yml
dataSource:
driver: com.mysql.jdbc.Driver
url: localhost:3306/springdb
username: root
password: root-
数据封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66package com.coderitl.springboot.vo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* dataSource 需要写为 prefix="data-source" 才有效
* datasource:
* driver: com.mysql.jdbc.Driver
* url: localhost:3306/springdb
* username: root
* password: root
*/
// 1. 定义数据模型封装 yml 文件中对应的数据 2. 定义为 spring 管控的 bean 3. 指定加载的数据
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;
}
public String toString() {
return "MyDataSource{" +
"driver='" + driver + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
} -
获取测试
名称使用问题 获取测试
-
集成JUNIT
-
步骤
-
加入依赖
1
2
3
4
5
6
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency> -
dao
测试 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.coderitl.springboot.dao.impl;
import com.coderitl.springboot.dao.UserDao;
import org.springframework.stereotype.Repository;
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("save is running.........");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// 测试
package com.coderitl.springboot;
import com.coderitl.springboot.dao.UserDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
class SpringbootApplicationTests {
// 1. 注入你要测试的对象
private UserDao userDao;
void contextLoads() {
// 2. 执行要测试的对象对应的方法
userDao.save();
}
}
-
-
测试文件位置问题
-
改动
SpringbootApplicationTests
的位置, 如果不在 SpringbootApplication
引导类包的下级, 运行将会报错 测试类位置引起报错 -
解决方案
在测试启动类添加 @SpringBootTest(classes = SpringbootApplication.class【主启动类字节码】)
-
-
报错后扫描包的方式
-
从
主测试文件
位置出发,找注解 @SpringBootConfiguration
,看谁加载了它,在父包或子包中找不见,则报错 主启动类中包含 @SpringBootConfiguration
注解
-
-
注意事项
如果测试类在
SpringBoot
启动类的包或子包中, 可以省略启动类的设置, 也就是省略 classes
的设定 -
junit4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// junit4,还需要添加适配依赖
package com.coderitl;
// 主启动类字节码文件
class SpringbootJunitApplicationTests {
private UserService service;
void testAdd() {
service.add();
}
}1
2
3
4
5
6
7
8
9
10
11<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>说明
-
JUnit5
常用注解 @Test
: 表示方法是测试方法,但是与Junit4
的 @Test
不同,他的职责非常单一不能声明任何属性,拓展的测试将会由 Jupiter
提供额外测试 @ParameterizedTest
:表示方法是参数化测试 @RepeatedTest
: 表示方法可重复执行@DisplayName
: 为测试类或者测试方法设置展示名称@BeforeEach
: 表示为每个单元测试之前执行@AfterEach
: 表示为每个单元测试之后执行@BeforeAll
: 表示在所有单元测试之前执行@AfterAll
:表示在所有单元测试之后执行 @Tag()
: 表示单元测试类别,类似于JUint4
的 @Categories
@Disabled
: 表示测试类或测试方法不执行,类似与 JUint4
的 @Ignore
@Timeout()
: 表示测试方法运行如果超过了指定时间将会返回错误@ExtendWith()
: 为测试类或测试方法提供扩展类引用
-
JUnit5
官网文档 -
实例
基本使用
-
概述
断言是测试方法中的核心部分,用来对测试需要满足条件进行验证。这些断言方法都是
org.junit.jupiter.api.Assertions
的静态方法。 JUnit5
内置的断言可以分成如下几个类别 -
简单断言
方法 说明 Assertions.assertEquals();
判断两个对象或两个原始类型是否相等 Assertions.assertNotEquals();
判断两个对象或两个原始类型是否不相等 Assertions.assertSame();
判断两个对象引用是否指向同一个对象 Assertions.assertNotSame();
判断两个对象引用是否指向不同的对象 Assertions.assertTrue();
判断给定的布尔值是否为 true
Assertions.assertFalse();
判断给定的布尔值是否为 false
Assertions.assertNull();
判断给定的布尔值是否为 null
Assertions.assertNotNull();
判断给定的布尔值是否不为 null
集成MyBatis
- 通过向导选择依赖时,
添加 SQL
中的 MybatisFrameWork 和 MYSQL Driver
-
默认
MYSQL
依赖版本 依赖版本查看 -
更换依赖
-
查看已安装版本
已安装版本信息 -
修改依赖
5+
1
2
3
4
5
6<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
<scope>runtime</scope>
</dependency> -
配置数据库连接信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 5.7+
版本的 MYSQL datas 输入会有提示
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/bookshop
username: root
password: root
# 8.0+版本的 MYSQL
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
# 添加时区 serverTimezone=Asia/Shanghai 也相同
url: jdbc:mysql://localhost:3306/bookshop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: root
-
-
mybatis
起步依赖: 完成 mybatis
对象自动装配, 对象放在容器中 -
pom.xml
指定吧 src/main/java
目录中的 xml
包含到 classpath
中 1
2
3
4
5
6
7
8<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources> -
创建实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30package com.coderitl.springbootmybatis.entity;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* mysql> desc bookinfo;
* +-----------------+--------------+------+-----+---------+-------+
* | Field | Type | Null | Key | Default | Extra |
* +-----------------+--------------+------+-----+---------+-------+
* | bookId | varchar(100) | NO | PRI | NULL | |
* | bookName | varchar(100) | NO | | NULL | |
* | bookCategory | varchar(100) | YES | | NULL | |
* | bookPrice | double | YES | | NULL | |
* | bookProductDate | date | YES | | NULL | |
* | shoppingCart | int(11) | YES | | NULL | |
* +-----------------+--------------+------+-----+---------+-------+
*/
public class BookInfo {
private String bookId;
private String bookName;
private String bookCategory;
private Double bookPrice;
private Date bookProductDate;
private Integer shoppingCart;
...
} -
创建
dao
接口 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package com.coderitl.springbootmybatis.dao;
import com.coderitl.springbootmybatis.entity.BookInfo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* @Mapper 告诉 MyBatis 这就是 dao 接口,创建此接口的代理对象 每个接口都需要添加, 后续更新为新注解, 用以简化
*/
public interface BookInfoDao {
/**
* 查询所有
*
* @return
*/
List<BookInfo> selectAllBook();
} -
创建
dao
接口对应的 Mapper
文件 1
2
3
4
5
6
7
8
9
10
<mapper namespace="com.coderitl.springbootmybatis.dao.BookInfoDao">
<select id="selectAllBook" resultType="com.coderitl.springbootmybatis.entity.BookInfo">
select *
from bookinfo
</select>
</mapper> -
创建
service
层对象 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.coderitl.springbootmybatis.service.impl;
import com.coderitl.springbootmybatis.dao.BookInfoDao;
import com.coderitl.springbootmybatis.entity.BookInfo;
import com.coderitl.springbootmybatis.service.BookInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
public class BookInfoServiceImpl implements BookInfoService {
private BookInfoDao dao;
public List<BookInfo> showAllBook() {
List<BookInfo> bookInfos = dao.selectAllBook();
return bookInfos;
}
} -
创建
controller
,访问server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25package com.coderitl.springbootmybatis.controller;
import com.coderitl.springbootmybatis.entity.BookInfo;
import com.coderitl.springbootmybatis.service.BookInfoService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
// @RestController = @Controller + @ResponseBody
public class BookInfoController {
private BookInfoService service;
public List<BookInfo> showAllBookInfo() {
// 调用业务方法
List<BookInfo> bookInfos = service.showAllBook();
return bookInfos;
}
} -
配置数据库连接信息
-
访问测试
访问测试
-
@MapperScan
加在主启动类上 1
2
3
4
5
6
7
8
9
10
11
12
13
14package com.coderitl.springbootmybatis;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// MapperScan(basePackages = "com.coderitl.springbootmybatis.dao") 只需要指定 dao 所在位置即可
public class SpringbootMybatisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisApplication.class, args);
}
}
集成MybatisPlus 和集成 Druid
-
MP
是 MybatisPlus
的简称 -
仓库搜索注意
注意选择带有 stater
druid
-
项目依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40<dependencies>
<!-- springmvc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
<scope>runtime</scope>
</dependency>
<!-- boot test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- mybatis-plus -->
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!-- druid 连接池 -->
<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
</dependencies>
-
数据源配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# druid 数据源配置
spring:
datasource:
druid:
# mysql8: com.mysql.cj.jdbc.Driver
driver-class-name: com.mysql.jdbc.Driver
# mysql8: jdbc:mysql://localhost:3306/test_demo?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
url: jdbc:mysql://localhost:3306/myemployees?autoReconnect=true&useSSL=false&characterEncoding=utf-8
username: root
password: root
initial-size: 1
min-idle: 1
max-active: 20
# mybatis-plus 配置
mybatis-plus:
global-config:
db-config:
table-underline: false # 设置表明不使用下划线方式
configuration:
map-underscore-to-camel-case: false # 设置字段不使用下划线方式
-
添加所需依赖
-
实体类
-
dao
1
2
3
4
5
6
7
8
9
10package com.example.springbootmybatisplus.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.springbootmybatisplus.domain.BookInfo;
// @Mapper => @MapperScan(basePackage)
public interface BookInfoDao extends BaseMapper<BookInfo> {
// 可以自定义也可以不用
}-
不定义
xml
mybatis plus
实现了部分功能,只需要调用对应接口即可
-
-
Lombok
: 一个Java
类库, 提供了一组注解, 简化 POJO
实体类开发 -
依赖添加
1
2
3
4
5
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency> -
刷新
maven
右下角出现一个选项框, 选择 注解 Enable
未出现也可以在此处配置 ****
-
Lombok
具体使用 -
实体类
-
快捷键
Ctrl+F12
- 功能:查看当前类中的所有成员:
方法、属性、内部类
- 亮点:直接显示所有成员的列表,支持搜索功能快速定位到某个成员的位置上。
- 功能:查看当前类中的所有成员:
-
未添加对应
getter | setter
注解 -
添加注解
@Getter
注解添加 (setter 同理) -
复合注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package com.example.springbootmybatisplus.domain;
import lombok.Data;
import java.util.Date;
// @Data => @Getter + @Setter
public class BookInfo {
private String bookId;
private String bookName;
private String bookCategory;
private Double bookPrice;
private Date bookProductDate;
private Integer shoppingCart;
}
// 对应注解也可以实现 无参 | 全参 构造-
注意
Lombok 简化实体类, 缺少构造
-
-
-
-
BaseMapper
接口 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.baomidou.mybatisplus.core.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Param;
public interface BaseMapper<T> extends Mapper<T> {
// 添加方法
int insert(T entity);
// 根据 Serializable id 删除
int deleteById(Serializable id);
int delete(; Wrapper<T> queryWrapper)
int updateById(; T entity)
int update(; T entity, Wrapper<T> updateWrapper)
T selectById(Serializable id);
List<T> selectBatchIds(; Collection<? extends Serializable> idList)
List<T> selectByMap(; Map<String, Object> columnMap)
T selectOne(; Wrapper<T> queryWrapper)
Integer selectCount(; Wrapper<T> queryWrapper)
List<T> selectList(; Wrapper<T> queryWrapper)
List<Map<String, Object>> selectMaps(; Wrapper<T> queryWrapper)
List<Object> selectObjs(; Wrapper<T> queryWrapper)
}
-
MP
调试日志 1
2
3mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl-
测试
测试日志
-
-
拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package com.example.springbootmybatisplus.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class MPConfig {
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 做分页用的
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
} -
实体类
1
2
... -
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33package com.example.springbootmybatisplus;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.springbootmybatisplus.dao.BookInfoDao;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
class SpringbootMybatisPlusApplicationTests {
private BookInfoDao dao;
void testGetPage() {
// 接口 : 实现类
IPage page = new Page(2, 3);
dao.selectPage(page, null);
// 当前页码值
System.out.println("getCurrent: " + page.getCurrent());
// 每页数据总量
System.out.println("getSize: " + page.getSize());
// 最大页码值
System.out.println("getTotal: " + page.getTotal());
System.out.println("getPages: " + page.getPages());
System.out.println("getRecords: " + page.getRecords());
}
} -
输出
分页实现 -
条件查询
like、between..
1
2
3
4
5String name = "Ja";
LambdaQueryWrapper<BookInfo> lqw = new LambdaQueryWrapper<>();
// BookInfo::getBookName 条件 lqw.条件
lqw.like(name != null, BookInfo::getBookName, name);
dao.selectList(lqw);-
输出
条件查询
-
-
Service
层接口定义与数据层定义具有较大区别 selectByUserNameAndPassword(String username,String password) => 数据层定义名称
login(String username,String password) => 业务层
-
使用
MybatisPlus
快速开发 service
-
主键
ID
1
2
3
4
5
6
7
8
9
10
11
12
public class BookInfo {
// 需要为自增类型 还需要标识 否则后续使用解析id 方法使用会出现 where null=?
private Integer bookId;
private String bookName;
private String bookCategory;
private Double bookPrice;
private Date bookProductDate;
private Integer shoppingCart;
} -
service
接口 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package com.example.springbootmybatisplus.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.springbootmybatisplus.domain.BookInfo;
public interface BookInfoService extends IService<BookInfo> {
Boolean modify(BookInfo bookInfo);
Boolean saveBook(BookInfo bookInfo);
Boolean delete(Integer bookId);
// 分页
IPage<BookInfo> getPage(Integer current, Integer pageSize);
}-
实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40package com.example.springbootmybatisplus.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.springbootmybatisplus.dao.BookInfoDao;
import com.example.springbootmybatisplus.domain.BookInfo;
import com.example.springbootmybatisplus.service.BookInfoService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
public class BookInfoServiceImpl extends ServiceImpl<BookInfoDao, BookInfo> implements BookInfoService {
private BookInfoDao dao;
// 可以自行定义
public Boolean modify(BookInfo bookInfo) {
return dao.updateById(bookInfo) > 0;
}
public Boolean delete(Integer bookId) {
return dao.deleteById(bookId) > 0;
}
public Boolean saveBook(BookInfo bookInfo) {
return dao.insert(bookInfo) > 0;
}
public IPage<BookInfo> getPage(Integer current, Integer pageSize) {
IPage page = new Page(current, pageSize);
return dao.selectPage(page, null);
}
} -
解析
解析使用
-
-
dao
1
2
3
4
5
6
7
8
9
10package com.example.springbootmybatisplus.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.springbootmybatisplus.domain.BookInfo;
// @Mapper => @MapperScan(basePackage)
public interface BookInfoDao extends BaseMapper<BookInfo> {
// 也可以实现自定义内容
} -
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40package com.example.springbootmybatisplus;
import com.example.springbootmybatisplus.dao.BookInfoDao;
import com.example.springbootmybatisplus.service.BookInfoService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
class SpringbootMybatisPlusApplicationTests {
private BookInfoDao dao;
private BookInfoService service;
// 业务层测试
// 根据 id 查询
void getByIdTest() {
service.getById(2022041010);
}
// 查询所有
void listTest() {
service.list();
}
// 根据 id 删除
void deleteTest() {
service.removeById(2022041010);
}
} -
快速开发方案
- 使用
MybatisPlus
提供有业务层通用接口 ( IService<T>
)与业务层通用实现类 ServiceImpl<M,T>
- 在通用类基础功能上做功能重载或功能追加
- 注意重载时不要覆盖原始操作,
避免原始提供的功能丢失
- 使用
-
统一返回数据模型
- 设计统一的返回值结果类型便于前端开发读取数据
- 返回值结果类型可以根据需求自行设定,
没有固定格式 - 返回值结果模型类用于后端与前端进行数据格式统一,
也称前后端数据协议
日志
-
设置组的日志级别和包的日志级别
1
2
3
4
5
6
7logging:
group:
mygroup: com.example.controller
level:
root: warn
# 给组设置日志级别
mygroup: debug输出 -
输出信息说明
1
2022-11-21 16:29:07.774 DEBUG 928 --- [nio-8080-exec-1] c.e.controller.HelloWroldController : mygroup log test................
- 输出以下项目
- 日期和时间: 毫秒精度,
易于排序 - 日志级别:
ERROR、WARN、INFO、DEBUG
- 进程标识
---
用于区分实际日志消息开始的分隔符- 线程名称: 括在方括号内
(可能会被截断以用于控制台输出) - 记录器名称: 这通常是源类名称
(通常缩写) - 日志消息
- 日期和时间: 毫秒精度,
- 输出以下项目
-
自定义日志输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16logging:
group:
mygroup: com.example.controller
level:
root: warn
# 给组设置日志级别
mygroup: debug
# 输出到文件
file:
name: output.log
logback:
rollingpolicy:
# 文件大小
max-file-size: 2KB
# 保存格式
file-name-pattern: server.%d{yyyy-MM-dd}.%i.log日志文件 -
更换日志实现
默认的日志 -
更换日志实现
-
在
spring-boot-starter-web
中排除 spring-boot-starter-logging
的依赖 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.1</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency> -
添加新的日志实现依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
-
-
高级配置
-
临时属性配置
可以打包成
jar
,在运行 jar
包的时候传递参数 -
步骤
-
使用
maven
插件打 jar
包, 前提时必须引入 maven
的打包插件 1
2
3
4
5
6
7
8
9
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> -
使用
maven
中的 package
进行打包 打包 启动 jar
-
启动时添加临时参数:
在 application.yml
中的配置都可以指定 修改端口号 -
参数传递的原理
1
2
3
4public static void main(String[] args) {
SpringApplication.run(HelloWorld.class, args);
}
// args当传递参数时,替换原先配置
-
-
JSP-集成
-
加入一个处理
jsp
的依赖, 负责编译 jsp
文件 1
2
3
4
5<!-- 编译 jsp -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency> -
如果需要使用
servlet、jsp、jstl
的功能 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<!-- jstl -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!-- servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<!-- jsp -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
</dependency> -
创建一个存放
jsp
的目录, 一般叫做 webapp
1
2
3
4
5
6
7
8
9
10
11
12
13
14pom.xml 配置 jsp 编译后的文件位置
<resources>
<resource>
<!-- jsp 原来的目录 -->
<directory>src/main/webapp</directory>
<!-- 指定编译后的存放目录 -->
<targetPath>META-INF/resource</targetPath>
<!-- 指定处理的目录和文件 -->
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
集成 Redis
-
添加依赖
1
2
3
4
5
6
7
8
9
10
11<!-- 集成 redis: 直接在项目中使用 RedisTemplate(StringRedisTemplate) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
data-redis 使用的 lettuce 客户端库
在程序中使用 RedisTemplate 类的方法,操作 redis 数据库, 实际就是调用 lettuce 客户端中的方法 -
连接配置
1
2
3
4
5
6
7
8
9# 必须是可读可写的主机
(主从复制的从机不可连接) 复制配置文件修改如下
1. 注释掉 # bin 127.0.0.1 (原因:bind 127.0.0.1生效,只能本机访问 redis)
2. 将 protected-mode yes 改为:protected-mode no (原因:把yes 改成 no,允许外网访问)
# 关闭防火墙
systemctl stop firewalld.service
# 禁止开机启动启动防火墙
systemctl disable firewalld.servicedaemonize:yes
:redis
采用的是单进程多线程的模式。当 redis.conf
中选项 daemonize
设置成 yes
时,代表开启守护进程模式。在该模式下, redis
会在后台运行,并将进程 pid
号写入至 redis.conf
选项 pidfile
设置的文件中,此时 redis
将一直运行,除非手动 kill 该进程。 daemonize:no
: 当daemonize
选项设置成 no
时,当前界面将进入 redis
的命令行界面, exit
强制退出或者关闭连接工具 ( putty,xshell
等) 都会导致 redis
进程退出。 1
2
3
4# 获取静态 ip
dhclient
# 查看虚拟机 ip 地址
ip addr1
2
3
4
5
6spring:
redis:
host: 192.168.247.128 # 虚拟机 ip
port: 6380 # (配置过主从复制)必须使用 master 机器 的端口号
database: 0 # 选择的数据库实例
connect-timeout: 10000 # 超时时间 -
测试请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27package com.example.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
public class RedisController {
/**
*
*
* */
private RedisTemplate redisTemplate;
public String addToRedis(String name, String value) {
// 操作 Redis 中 String 类型数据 先获取 ValueOperations 对象
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("myname","lisi");
return "redis add string key and value";
}
}测试 Redis
连接 -
读取
1
2
3
4
5
6
7
8// 从 redis 获取数据
public String getRedisData(String k) {
ValueOperations valueOperations = redisTemplate.opsForValue();
Object v = valueOperations.get(k);
return "key 是: " + k + " 他的值是: " + v;
}`key·查看 -
RestFul
路径传参 使用 restful
风格 数据可读性提高 -
RedisTemplate
与StringRedisTemplate
对比 StringRedisTemplate
: 把k,v
都是作为 String
处理, 使用的是 String
的序列化, 可读性好 RedisTemplate
:把k,v
经过了序列化存到 redis
,k,v
是序列化的内容, 不能直接识别
监控
-
添加依赖
1
2
3
4
5
6
7
8
9<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency> -
配置
1
2
3
4
5# 开启健康检查的完整信息
management:
endpoint:
health:
show-details: always -
访问
快速定位第三方问题 连接 Redis
Redis 序列化
-
序列化: 把对象转换为可传输的字节序列化过程成为序列化
-
反序列化: 把字节序列还原为对象的过程称为反序列化
-
为什么需要序列化
序列化最终的目的是为了对象可以跨平台存储和进行网络传输,
而我们进行跨平台存储和网络传输的方式就是 IO
,而我们的IO
支持的数据格式就是字节数组, 我们必须把对象转成字节数组的时候就是制定一种规则 (序列化), 那么我们从 IO
流里面读取出数据的时候再以这种规则把对象还原回来 (反序列化) -
什么情况下需要序列化
我们已经知道凡是需要进行
跨平台存储
和网络传输
的数据,都需要进行序列化 本质上存储和网络传输,
都需要经过把一个对象状态保存成一种跨平台识别的字节风格, 然后把其他的平台才可以通过字节信息解析还原成对象信息 -
序列化的方式
序列化只是一种拆装组成对象的规则,
那么这种规则可定可能有多种多样, 比如现在常见的序列化方式有: JDK(不支持跨语言)、JSON、XML、Hessian、Kryo(不支持跨语言)
-
RedisTemplate
的序列化 1
2
3
4
5
6
7
8
9
10// RedisTemplate 的序列化
public String setRedisDataRestFul(String k, String val) {
// 设置 key String 序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 设置 value String 序列化
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.opsForValue().set(k, val);
return "redisTemplate 设置序列化";
}设置序列化
JSON 序列化
-
IDEA
配置, 之后通过 alt + enter
即可生成序列号 开启序列化 idea
生成序列化版本号 -
将实体类序列化为
JSON
数据格式存储在 redis
1
2
3
4
5
6
7
8
9
public String jsonSerialize() {
Student student = new Student("coder-itl", 18);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Student.class));
// 获取时 redisTemplate.opsForValue().get("studentInfo");
redisTemplate.opsForValue().set("studentInfo", student);
return "Student => json Serialize in redis";
}序列化为 json
格式存储数据
Thymeleaf
简介
JSP
必须依赖 Tomcat
运行, 不能直接运行在浏览器 HTML
可以直接运行在浏览器中,但是 不能接受控制器
传递的数据Thymeleaf
是一种既保留了 HTML
的后缀能够直接在浏览器运行的能力, 又实现了 JSP
显示动态数据的功能- 静能查看页面效果,
动则可以显示数据
使用
-
添加依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency> -
在
resources/template
下创建 HTML
文件 -
通过控制器访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.example.springbootssm.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
public class PageController {
public String showPage() {
return "HelloWorld";
}
}控制器 控制器访问测试 声明 -
重要说明
static
目录下的资源被定义为静态资源, SpringBoot
应用默认放行, 如果将 HTML
页面创建在 static
目录下是可以直接访问的 templates
目录下的文件会被定义为动态网页模板, SpringBoot
应用会拦截 temnplates
中定义的资源, 如果将 HTML
文件定义在 templates
目录, 则必须通过控制器跳转访问
配置
-
Thymeleaf
配置信息 1
2
3
4
5
6
7
8
9
10spring:
# Thymeleaf 配置:
thymeleaf:
# 开发阶段关闭 cache(模板缓存) false: 让修改立即生效
cache: false
# 以下内容为默认配置(可以省略)
encoding: UTF-8
mode: HTML
prefix: classpath:/templates/
suffix: .html
语法掌握
标准变量表达式
-
标准变量表达式
-
语法:
${key}
-
作用: 获取
key
对应的文本数据, key
是 request
作用域中的 key
,使用 request.setAttribute() | model.addAttribute()
在页面的 html
标签中, 使用 th:text="${key}"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.example.springbootssm.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
public class PageController {
public String showPage(Model model, HttpServletRequest request) {
// 使用 model 存放对象
model.addAttribute("data", "2101100123");
// 使用 request 存放数据
request.setAttribute("userInfo", "coder-itl");
// 指定视图(模板引擎使用的页面 html ) => 逻辑名称
return "HelloWorld";
}
}1
2
3
4
5
6
7
8
9
10
11
12
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
Hello World Data =>
<h1 th:text="${data}">数据 </h1>
<h1 th:text="${userInfo}"></h1>
</body>
</html>-
渲染
数据获取
-
-
选择变量表达式 (星号变量表达式)
-
语法:
*{key}
-
作用: 获取这个
key
对应的数据, *{key}
需要和 th:object
这个属性一起使用,目的是简单获取对象的属性值 -
使用
1
2
3
4
5
6<div th:object="${userInfo}">
<p>选择 (星号)</p>
<p th:text="*{id}"></p>
<p th:text="*{name}"></p>
<p th:text="*{password}"></p>
</div>-
简化书写
星号使用
-
链接表达式
-
语法:
@{url}
-
作用: 表示链接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<script src="...">
</script> 已显示
</p>
</div>-
文件目录层级
静态资源存放在 static 目录中
,使用 @{url} 时省略 static
文件目录层级 -
渲染
@{url}
使用
-
循环
-
each
循环, 可以循环 List,Array,Map
-
语法: 在一个
html
标签中, 使用 th:each
1
2
3
4
5
6
7
8
9
10
11
12
public String showEachListPage(Model model) {
List<User> users = new ArrayList<>();
users.add(new User(1001, "c1", "c1"));
users.add(new User(1002, "c2", "c1"));
users.add(new User(1003, "c3", "c1"));
users.add(new User(1004, "c4", "c1"));
// 存储数据
model.addAttribute("userList", users);
// 视图逻辑名称
return "list";
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<!-- 语法 -->
th:each="集合循环成员,循环的状态变量:${key}"
th:text="${集合循环成员}"
集合循环成员 | 循环的状态变量: 两个名称都是自定义的
循环的状态变量: 这个名称可以不自定义,默认是"集合循环的成员 State"
循环的状态变量: 是循环体的信息,通过该变量可以获取如下信息
index: 当前迭代对象的 index (从 0 开始)
count: 当前迭代对象的个数(从 1 开始计算)
size: 被迭代对象的大小
current: 当前迭代变量
even/odd: 布尔值,当前循环的是否是偶数|奇数 (从 0 开始计算)
first: 布尔值,当前循环是否为第一个
last: 布尔值,当前循环是否为最后一个
循环的状态变量也可以不定义,则默认采用迭代变量加上 Stat 后缀, 即 userStat
<h1>循环 </h1>
<div th:each="user:${userList}">
<p th:text="${user}"></p>
</div>-
渲染
each
默认形式 -
map
遍历 1
2
3
4
5
6<div th:each="mp:${userList}">
<p th:text="${mp.key}"></p>
<p th:text="${mp.value}"></p>
</div>
-
if
-
语法:
th:if="boolean 条件",
条件为 true 显示内容 -
th:unless
是 th:if
的一个相反操作 -
使用
1
2
3
4<div>
<p th:if="1<10"> 1<10=>true(条件为真显示内容) </p>
<p th:unless="1>10"> 1>10 =>false(条件为假显示内容) </p>
</div>
switch
-
和
java
语法一致 -
使用
1
2
3
4
5
6
7<p th:switch="${user.password}">
<span th:case="login">显示已登录 </span>
<span th:case="nologin">显示未登录 </span>
<span th:case="register"> 显示注册 </span>
<!-- 默认 -->
<span th:case="*"> 未知错误 </span>
</p>
th:inline(内联 )
-
内联
text
: 在html
标签外, 获取表达式的值 -
语法:
1
2
3
4
5<div th:inline="text">
aa=> [[${userInfo}]] 其他数据
</div>
<!-- th:inline="text" 可以省略 -->
aa=> [[${userInfo}]] 其他数据 -
内联脚本
1
2
3
4
5<!-- th:inline="javascript" 不可以省略 -->
<script th:inline="javascript">
let data = [[${data}]];
alert(data)
</script>
字符串连接
-
语法: 使用双竖线
| |
1
<p th:text="|我是 ${data} 123|"></p>
运算符
-
基本使用
-
算数运算:
+,-,*,/,%
-
关系比较:
>,<,>=,<=(gt,lt,ge,le)
-
相等判断:
==,!=(eq,ne)
-
使用
1
2
3
4
5
6
7
8
9
10<div>
<p th:text="|'1>2': ${1>2}|"></p>
<p th:text="|'1<2': ${1<2}|"></p>
<p th:text="|'1==2': ${1==2}|"></p>
<p th:text="|'1 eq 2': ${1 eq 2}|"></p>
<p th:text="|'1 ne 2': ${1 ne 2}|"></p>
<p th:text="|'1 gt 2': ${1 gt 2}|"></p>
<p th:text="|'1 lt 2': ${1 lt 2}|"></p>
<p th:text="|三目运算: ${data=='2101100123' ? 10:110}|"></p>
</div> -
渲染
运算符
内置对象
-
#request
表示 HttpServletRequest
-
#session
表示 HttpSession
对象 -
session
对象, 表示 HttpSession
对象, 是 #session
的简单表示方式 -
使用
1
2
3
4
5
6
7// .html 不可以少
public String builtInPage(HttpServletRequest request, HttpSession session) {
request.setAttribute("reqData", "request data...........");
request.getSession().setAttribute("reqSession", "SESSIONID=coder-itl");
session.setAttribute("jSession", "jSession data................");
return "builtin";
}1
2
3
4
5
6
7
8<div>
<!-- request 作用域 -->
<p th:text="${#request.getAttribute('reqData')}"> request作用域 </p>
<!-- request.getSession.setAttribute() -->
<p th:text="${#session.getAttribute('reqSession')}"></p>
<!-- 简化 session -->
<p th:text="${session.jSession}"></p>
</div>
-
碎片
-
碎片的概念
碎片,
就是 HTML
片段, 我们可以将多个页面中使用相同的 HTML
标签部分单独定义, 然后通过 th:include
可以在 HTML
网页中引入定义的碎片 -
定义碎片
1
2
3
4
5
6
7
8
9
10<!-- builtin.html 中的局部片段 -->
<div th:fragment="builtin">
将被引入
<!-- request 作用域 -->
<p th:text="${#request.getAttribute('reqData')}"> request作用域 </p>
<!-- request.getSession.setAttribute() -->
<p th:text="${#session.getAttribute('reqSession')}"></p>
<!-- 简化 session -->
<p th:text="${session.jSession}"></p>
</div> -
使用
1
2
3<!-- builtin::builtin => builtin.html 中定义了一个片段名称为 builtin 的碎片 -->
<h1>引入 builtin </h1>
<div th:include="builtin::builtin"></div>
文件上传与下载
-
上传文件开发步骤
-
在系统中开发一个可以进行文件上传的页面,
包含一个 form
表单, 在表单中开发一个可以选择本地计算机文件的入口 1
2
3
4
5
6
7
8<form
action="http://localhost:8080/file/upload"
enctype="multipart/form-data"
method="post"
>
<input type="file" name="file" />
<input type="submit" value="文件上传" />
</form>ApiFox
测试文件上传 -
form
表单, 提交方式 method="post"
form
的 method
提交方式必须是 post
form 的 enctype="application/x-www-form-urlencoded(文本) | multipart/form-data(二进制)"
-
开发
Controller
在控制器方法中使用 MultipartFile
进行文件的接收 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39package com.coderitl.com.coderitl.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
// 适用于 war 包,通用方式使用注入(配置文件将路径注入)
public class FileUpload {
public String upload( MultipartFile file, HttpServletRequest request)throws IOException {
log.info("文件信息: {}", file.toString());
// 获取文件原始名称
String originalFilename = file.getOriginalFilename();
System.out.println("originalFilename = " + originalFilename);
// 获取文件大小
long size = file.getSize();
System.out.println("size = " + size);
// 获取文件类型
String contentType = file.getContentType();
System.out.println("contentType = " + contentType);
// 文件上传
// 1. 根据 upload 相对路径获取部署到服务器之后的绝对路径
// 可以替代过时接口: String realPath = request.getSession().getServletContext().getRealPath("/uploadFile");
// 也可以进行对象存储上传
// 2. 将文件上传到 uploadFile 对应路径
file.transferTo(new File("E:\\springboot-demo\\springboot-junit\\src\\main\\resources\\uploadFile", Objects.requireNonNull(file.getOriginalFilename())));
return "ok";
}
} -
在
springmvc
配置文件中加入文件上传解析器配置 1
2
3<!-- CommonsMultipartResolver -->
<bean id="multipartResolver" class="">
<!-- 要求: 文件上传解析器必须存在 id 且 id 必须为 multipartResolver -->1
2
3
4
5
6# springboot 文件上传配置
spring:
servlet:
multipart:
max-file-size: 10MB # 默认 1 MB
max-request-size: 100MB # 整个请求上传文件大小 -
引入文件上传的相关依赖
1
2
3<!-- springmvc: commons-fileupload -->
<!-- springboot 可以不引入依赖 -->
-
文件下载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45package org.example.controller;
import lombok.extern.slf4j.Slf4j;
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;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
public class DownloadController {
private String downloadPath;
public void download(String fileName, HttpServletResponse response) throws Exception {
log.info("fileName: [{}]", fileName);
log.info("downloadPath: [{}]", downloadPath);
File file = new File(downloadPath, fileName);
// 将文件读取为文件输入流
FileInputStream fileInputStream = new FileInputStream(file);
// 获取响应流之前一定要设置以附件形式下载()
response.setContentType("image/jpeg");
// 获取相应输出流
ServletOutputStream fileOutputStream = response.getOutputStream();
// 输入流赋值给输出流
int len = 0;
byte[] b = new byte[1024];
while ((len = fileInputStream.read(b)) != -1) {
fileOutputStream.write(b, 0, len);
fileOutputStream.flush();
}
// 释放资源
fileInputStream.close();
}
}1
2
3
4
5
6
7
8
9//Content-Disposition
的作用:告知浏览器以何种方式显示响应返回的文件,用浏览器打开还是以附件的形式下载到本地保存
//attachment表示以附件方式下载 inline 表示在线打开 "Content-Disposition: inline; filename= 文件名.mp3"
// filename表示文件的默认名称,因为网络传输只支持 URL 编码的相关支付,因此需要将文件名 URL 编码后进行传输, 前端收到后需要反编码才能获取到真正的名称
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
// 告知浏览器文件的大小
response.addHeader("Content-Length", "" + file.length());
OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
response.setContentType("application/octet-stream");
-
-
多文件上传
-
前端
1
2
3
4<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" multiple>
<input type="submit" value="上传">
</form> -
后端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50package com.example.jsondemo.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
public class FileUploadController {
// 设置上传文件的存储路径
private String UPLOAD_DIR = "D:/mult/";
// 处理上传文件的接口
public String uploadFiles( { MultipartFile[] files)
// 用于存储上传文件的完整路径
StringBuilder fileNames = new StringBuilder();
// 遍历所有上传的文件
for (MultipartFile file : files) {
// 获取上传文件的文件名,并清理掉可能存在的非法字符
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
// 创建上传文件的存储路径,并写入到磁盘
File dest = new File(UPLOAD_DIR + fileName);
try {
if (!dest.getParentFile().exists()) { // 如果路径不存在则创建目录
dest.getParentFile().mkdirs();
}
file.transferTo(dest);
fileNames.append(dest.getAbsolutePath()).append(" "); // 将上传文件的完整路径追加到字符串中
} catch (IOException e) {
e.printStackTrace();
}
}
// 返回上传文件的完整路径
return "Files uploaded successfully: " + fileNames.toString();
}
}
-
集成 Dubbo
-
环境问题
- 升级到
2.7.x
的注意事项和兼容性问题总结:2.7.0
版本在改造的过程中遵循了一个原则,即保持与低版本的兼容性,因此从功能层面来说它是与2.6.x
及更低版本完全兼容的,而接下来将要提到的兼容性问题主要是包重命名带来的。另外,虽然功能用法保持向后兼容,但参考本文能帮助您尽快用到 2.7.0
版本的新特性。 - 环境要求:需要
Java 8
及以上版本。
- 升级到
-
添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13<!-- dubbo 起步依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
<!-- zookeeper 注册中心 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<version>2.7.8</version>
<type>pom</type>
</dependency> -
创建聚合项目
-
创建
provider(spring-boot)
-
依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25<dependencies>
<!-- dubbo 起步依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<type>pom</type>
<!-- 排除 lof4j(根据添加日志情况选择添加) -->
<exclusions>
<exclusion>
<groupId>slf4j-log4jl2</groupId>
<artifactId>org.slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.coderitl.dubbo</groupId>
<artifactId>api-interface</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies> -
配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23server:
port: 8000
# dubbo 配置
# 配置服务名称 xml: dubbo:application name="名称"
spring:
application:
name: dubbo-provider
# 配置扫描包 扫描 @DubboService
# application.yml
dubbo:
registry:
address: zookeeper://43.142.80.9:2181 # 注册中心
timeout: 600000 # 超时时间
application:
name: dubbo-provider # 服务名称,如果多个启动的服务进程命名一致,自动组成集群
protocol:
name: dubbo # 协议
port: 20880 # 端口
config-center: # 配置中心超时时间
timeout: 600000 -
实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28package com.coderitl.dubbo.service.impl;
import com.coderitl.dubbo.domain.UserInfo;
import com.coderitl.dubbo.service.UserInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
// 使用 Dubbo 注解暴露服务
public class UserServiceImpl implements UserInfoService {
public UserInfo getUserById(Integer userId) {
log.info("userId: {}", userId);
UserInfo user = new UserInfo(1001, "coder-itl", "男");
return user;
}
}
// 主启动
public class DubboProviderMain {
public static void main(String[] args) {
SpringApplication.run(DubboProviderMain.class, args);
}
}
-
-
创建
consomer(springboot)
-
依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.coderitl.dubbo</groupId>
<artifactId>api-interface</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<type>pom</type>
<!-- 排除 lof4j -->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies> -
配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17server:
port: 8001
dubbo:
registry:
address: zookeeper://43.142.80.9:2181
timeout: 600000
application:
name: dubbo-consumer
protocol: # 可不配置
name: dubbo
port: 20880
config-center:
timeout: 600000
scan:
base-packages: com.coderitl.dubbo.service
-
实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class UserController {
// 远程注入
private UserInfoService userInfoService;
public UserInfo getInfo() {
return userInfoService.getUserById(1001);
}
}
// 主启动
public class DubboWebMain {
public static void main(String[] args) {
SpringApplication.run(DubboWebMain.class, args);
}
}
-
-
创建公共接口模块
( quick
)1
2
3
4
5
6
7
8
9
10
11
12
13
14// 序列化
public class UserInfo implements Serializable {
private Integer userId;
private String userName;
private String sex;
}
// 接口
public interface UserInfoService {
UserInfo getUserById(Integer userId);
}
-
项目部署
- 打包方式
jar (推荐)
war
聚合工程
-
创建
maven-父工程
-
修改父工程的
pom.xml
打包方式为 pom
1
2
3
4
5<!-- 父工程打包方式 -->
<packaging>pom</packaging> -
在父工程下创建子工程
右键- new
-Model
-
依赖继承
依赖继承 -
依赖管理
1
2
3
4
5
6
7
8
9
10
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.6.1</version>
</dependency>
</dependencies>
</dependencyManagement>
Swageer
-
添加
swageer
依赖 ( api
)1
2
3
4
5
6
7
8
9
10<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency> -
Java
配置, 在 api
下创建 config
包, 并创建配置类 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51package com.coderitl.fmmall.config;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
public class SwaggerConfig {
/**
* Swagger 会帮助我们生成接口文档
* 1. 配置生成的文档信息
* 2. 配置生成规则
*/
/**
* Docket 封装接口文档信息
*/
public Docket getDocket() {
/**
* 指定文档风格
*/
ApiInfoBuilder apiInfoBuilder = new ApiInfoBuilder();
apiInfoBuilder
// 文档标题
.title("<> )后端接口说明文档"
// 文档描述
.description("后端接口说明自动化实现")
// 文档版本信息 v
.version("v 2.0.0")
// 作者信息
.contact(new Contact("coder-itl", "https://www.lovobin.github.io", "3327511395@qq.com"));
ApiInfo apiInfo = apiInfoBuilder.build();
// 指定生成的文档中的封面信息: 文档的标题 版本 作者
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo)
.select()
// 指定包
.apis(RequestHandlerSelectors.basePackage("com.coderitl.fmmall.controller"))
// 指定 那些请求
.paths(PathSelectors.any()).build();
return docket;
}
}-
启动报错
启动报错 -
解决方案
1
2
3
4
5# 在 api 的 application.yml 加入如下配置信息
spring:
mvc:
pathmatch:
matching-strategy: ANT_PATH_MATCHER -
访问
http://localhost:8080/swagger-ui.html
-
-
-
注解说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31package com.coderitl.fmmall.controller.user;
...
// 类注解,在控制器类添加此注解, 可以对控制器类进行功能说明
public class UserController {
private UserService service;
// 方法注解: 说明接口方法的作用
// 方法注解: 说明接口方法的参数
public R<User> loginUser(String id, String password) {
R<User> login = service.login(id, password);
if (login != null) {
return login;
}
return R.error("登录失败!");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package com.coderitl.fmmall.entity;
...
// 当接口参数和返回值为对象类型时,在实体类中添加注解说明
// 类注解
public class User {
private Integer id;
private String name;
private String password;
}
// 加在不需要显示的地方 => 接口文档忽略显示
-
Swagger-UI
插件 -
添加依赖
1
2
3
4
5<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency> -
启动项目,
更新访问路径: http://localhost:8080/doc.html
-
调式
接口调试
-
Jasypt 加密
-
添加依赖
1
2
3
4
5<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency> -
编写配置
1
2
3
4jasypt:
encryptor:
algorithm: PBEWITHHMACSHA512ANDAES_256 # 指定加密算法
password: root # 指定密钥(运行时参数) -
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class JasyptTest {
private StringEncryptor stringEncryptor;
public JasyptTest(StringEncryptor stringEncryptor) {
this.stringEncryptor = stringEncryptor;
}
public void testSecret() {
// 加密
String secrty = stringEncryptor.encrypt("root");
System.out.println("secrty = " + secrty);
// 解密
String decrypt = stringEncryptor.decrypt("6jiQYevMPC0AbOAcrO7+3tHE1o7SYhxV7ynMfKL+mQf94WSA/bq36iCm2KVXtrL9");
System.out.println("decrypt = " + decrypt);
}
} -
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14mysql:
host: ENC(密文)
# jvm 运行参数 配置密文
-Djasypt.encryptor.password=xxxx
# 数据源配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/jdbc?autoReconnect=true&useSSL=false&characterEncoding=utf-8
username: ENC(Khmy4ImDX1McS9qMJIP/98FBGxaOzpCCKwIWJ01CaPK8XkxvpQBBEJ51PQAS5zlP)
password: ENC(Khmy4ImDX1McS9qMJIP/98FBGxaOzpCCKwIWJ01CaPK8XkxvpQBBEJ51PQAS5zlP)添加配置
集成-ES
-
添加依赖
1
2
3
4
5
6
7
8
9
10
11<properties>
<java.version>1.8</java.version>
<!-- 覆盖默认的版本【当前客户端安装的是 7。12.1 】 -->
<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>${elasticsearch.version}</version>
</dependency> -
初始化
RestHeightLevelClient
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class HotelIndexTest {
private RestHighLevelClient client;
void setUp() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.2.3:9200")
));
}
/****************************************************************************************************/
void testCreateIndex() throws IOException {
// 1.准备 Request PUT /hotel
CreateIndexRequest request = new CreateIndexRequest("hotel");
// 2.准备请求参数
request.source(MAPPING_TEMPLATE, XContentType.JSON);
// 3.发送请求
client.indices().create(request, RequestOptions.DEFAULT);
}
void testExistsIndex() throws IOException {
// 1.准备 Request
GetIndexRequest request = new GetIndexRequest("hotel");
// 3.发送请求
boolean isExists = client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(isExists ? "存在" : "不存在");
}
void testDeleteIndex() throws IOException {
// 1.准备 Request
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
// 3.发送请求
client.indices().delete(request, RequestOptions.DEFAULT);
}
/****************************************************************************************************/
void tearDown() throws IOException {
client.close();
}
} -
将
RestHighLevelClient
注入 Spring
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HotelDemoApplication {
public static void main(String[] args) {
SpringApplication.run(HotelDemoApplication.class, args);
}
// 注入 RestHighLevelClient
public RestHighLevelClient client() {
return new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.2.3:9200")));
}
} -
业务使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
private RestHighLevelClient client;
public PageResult search(RequestParams requestParams) {
SearchResponse response = null;
try {
// 1. 准备 Resuest
SearchRequest request = new SearchRequest("hotel");
// 2. 准备 DSL
/****************************/
// 2.1 准备 query
String key = requestParams.getKey();
if (key == null || "".equals(key)) {
// 前端未传递 key
request.source().query(QueryBuilders.matchAllQuery());
} else {
request.source().query(QueryBuilders.matchQuery("all", key));
}
// 2.2 分页
int page = requestParams.getPage();
int size = requestParams.getSize();
request.source().from((page - 1) * size).size(size);
/****************************/
// 3. 发送请求,得到响应
response = client.search(request, RequestOptions.DEFAULT);
} catch (IOException e) {
throw new RuntimeException(e);
}
// 4. 解析响应
return handleResponse(response);
}
// 4. 解析响应
private PageResult handleResponse(SearchResponse response) {
// 解析响应
SearchHits searchHits = response.getHits();
// 获取总条数
long total = searchHits.getTotalHits().value;
// 文档数组
SearchHit[] hits = searchHits.getHits();
List<HotelDoc> hotels = new ArrayList<>();
for (SearchHit hit : hits) {
// 获取文档 source
String json = hit.getSourceAsString();
// 反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
hotels.add(hotelDoc);
}
// 封装返回
return new PageResult(total, hotels);
}
} -
【BUG】解决方案
: 对查询结果进行校验,如果当前页码指大于最大页码值, 使用最大页码值作为当前页码值重新查询 1
2
3
4
5
6
7
8
9
10
11
public R getPage(
int currentPage,
int pageSize) {
Ipage<Book> page = bookService.getPage(currentPage,pageSize);
// 如果当前页码值大于了总页码值,那么重新执行查询操作, 使用最大页码值最为当前页码值
if(currentPage>page.getPages()){
page = bookService.getPage((int)page.getPages(),pageSize);
}
return new R();
}
底层注解
-
@Configuration
理解 -
测试是否成功创建对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DemoSpringBootApplication {
public static void main(String[] args) {
// 1. 返回 IOC 容器
ConfigurableApplicationContext run = SpringApplication.run(DemoSpringBootApplication.class, args);
// 2. 查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
// 3. 从容器中获取组件
User user = run.getBean("ConfigUser", User.class);
// User(userName=coder-itl, gender=男)
System.out.println(user);
} -
说明
- 配置类里面使用
@Bean
标注再方法尚给容器注册组件, 默认 单实例
的 - 配置类本身也是组件
proxyBeanMethods(@Configuration(proxyBeanMethods = true))
:代理Bean
的方法 (可以解决循环依赖)
- 配置类里面使用
-
最佳实战
- 配置 类组件之间无依赖关系用
Lite(proxyBeanMethods = false))
模式加速容器启动过程、减少判断 - 配置类组件之间有依赖关系,方法会用到之前单实例组件,用
Full(proxyBeanMethods = true))
模式
- 配置 类组件之间无依赖关系用
-
-
@Import({xxx.class,xxx.class,...})
,导入配置 -
@Conditional
条件装配: 满足 Conditional
指定的条件,则进行组件注入 -
@ConditionalOnBean(name="user")
当存在 user
组件时, 装配该配置 1
2
3
4
5
6
7
8
9
10
11// 可以加在类上该注解
public class MyConfig {
// 当存在`user`组件时,装配该配置
public User ConfigUser() {
return new User("coder-itl", "男");
}
} -
@ConditionalOnMissingBean(name="user")
当不存在时,装配该配置
-
-
@ImportResource("classpath:XmlConfig.xml")
,导入 xml
的配置文件
配置绑定: @ConfigurationProperties
-
绑定
1
2
3
4# 配置文件
my-car:
brand: BYD
price: 1000001
2
3
4
5
6
7
// 只有在 容器中的组件,才会拥有 springboot 提供的强大功能
public class Car {
private String brand;
private Integer price;
}通过如下依赖解决 1
2
3
4
5
6
7
8<!-- 文档:
https://docs.spring.io/spring-boot/docs/2.7.4/reference/html/configuration-metadata.html#appendix.configuration-metadata.annotation-processor
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<!-- 打包时排除该依赖 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>1
2
3
4
5
6
7
8
9
10
11
12
13// 测试是否绑定成功
public class CarController {
private Car car;
public Car car(){
return car;
}
}测试访问 -
第三方包绑定方式
1
2
3
4
5
6
7
8
// @Component 假如是第三方的,不能指定 @Component
public class Car {
private String brand;
private Integer price;
}1
2
3
4
5
6
7
8
9// 必须在配置类中
// 1. 开启 Car 配置绑定功能
// 2. 把这个 Car 组件自动注册到容器中
public class MyConfig {
...
}名称规定: kebab-case
方式命名
数据校验
-
添加依赖
1
2
3
4
5
6
7
8<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency> -
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package com.example.vo;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Size;
public class UserInfo {
private Integer id;
private String info;
}数据校验
@SpringBootApplication
-
@SpringBootConfiguration
@Configuration
: 代表当前是一个配置类
-
@EnableAutoConfiguration
-
@AutoConfigurationPackage
: 自动配置包-
@Import({AutoConfigurationPackages.Registrar.class})
1
2
3
4
5
6
7// 给容器导入一个组件
public AutoConfigurationPackage {}
// 利用 Registrar 给容器导入一系列组件
// AutoConfigurationPackage 将指定的一个包下的所有组件导入进来
// 导入的是启动类所在包下
-
-
@Import({AutoConfigurationImportSelector.class})
1
2
3
4
5
6
7
8public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
-
-
@ComponentScan
1
2
3
4
5
6
7
8
9 -
自动装配原理
-
SpringBoot
先加载所有的自动配置类: xxxAutoConfiguration
-
每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值,
xxProperties
里面拿, xxxProperties
和配置文件进行绑定 -
生效的配置类就会给容器中装配很多组件
-
只要容器中有了这些组件,相当于这些功能都有了
-
定制化配置
-
用户直接自己
@Bean
替换底层的组件 -
用户去看这个组件是获取配置文件什么值就去修改
字符集 (根据 prefix 在配置文件中修改) 修改
-
-
WEB 场景
-
静态资源规则与定制化
-
静态资源目录
在
static or public or resources or /META-INF/resources
目录中的静态资源都可以被访问 -
使用静态资源访问前缀
1
2
3
4
5# 配置文件
spring:
mvc:
# res 为自定义内容
static-path-pattern: /res/**必须添加访问前缀 原始目录无变化 -
指定唯一的静态资源目录
1
2
3
4
5# 配置
spring:
web:
resources:
static-locations: classpath:/public指定静态资源目录
-
-
自定义
Favicon.ico
1
2
3
4# 此项配置会影响显示 图标,
需要关闭
spring:
mvc:
static-path-pattern: /res/** # res 为自定义内容
JavaMail -发送简单邮件
-
添加依赖
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency> -
定义
service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33package com.example.javamail.service.impl;
import com.example.javamail.service.SendMailService;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
public class SendMailServiceImpl implements SendMailService {
private JavaMailSender javaMailSender;
// 发送人
private String from = "@qq.com";
// 接收人 @qq.com
private String to = "@qq.com";
// 主题
private String subject = "测试邮件";
// 正文
private String context = "邮件正文内容";
public void sendMail() {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from+"(小甜甜)");
message.setTo(to);
message.setSubject(subject);
message.setText(context);
javaMailSender.send(message);
}
} -
配置
1
2
3
4
5spring:
mail:
host: smtp.qq.com # qq/126/xxx
username: xxxx .com
password: xxxpassword
获取 -
发送多部邮件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42package com.example.javamail.service.impl;
import com.example.javamail.service.SendMailService;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMailMessage;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
public class SendMailServiceImpl implements SendMailService {
private JavaMailSender javaMailSender;
// 发送人
private String from = "@qq.com";
// 接收人 @qq.com
private String to = "@qq.com";
// 主题
private String subject = "测试邮件";
// 正文
private String context = "点开有惊喜 ";
public void sendMail() {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message);
try {
helper.setFrom(from + "(小甜甜)");
helper.setTo(to);
helper.setSubject(subject);
helper.setText(context,true);
} catch (Exception e) {
throw new RuntimeException(e);
}
javaMailSender.send(message);
}
} -
添加附件发送
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47package com.example.javamail.service.impl;
import com.example.javamail.service.SendMailService;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMailMessage;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;
public class SendMailServiceImpl implements SendMailService {
private JavaMailSender javaMailSender;
// 发送人
private String from = "@qq.com";
// 接收人 @qq.com
private String to = "@qq.com";
// 主题
private String subject = "测试邮件";
// 正文
private String context = "点开有惊喜 ";
public void sendMail() throws MessagingException {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
try {
helper.setFrom(from + "(小甜甜)");
helper.setTo(to);
helper.setSubject(subject);
helper.setText(context, true);
// 发送附件
File f1 = new File("D:\\GithubCode\\Private\\source\\_posts\\2022\\04\\Es6-Es11.md");
helper.addAttachment("Es6学习笔记.md", f1);
javaMailSender.send(message);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}