Spring Boot 3 整合 SSM 场景进行数据访问

熟悉 SSM 框架

SSM 框架简介

SSM 框架是 Spring、Spring MVC 和 MyBatis 三个框架的整合,是 Java 企业级开发中经典的技术栈组合。其中:

  • Spring 提供了依赖注入(DI)和面向切面编程(AOP)等核心功能,用于管理对象生命周期和业务逻辑分层

  • Spring MVC 是基于 MVC 设计模式的 Web 框架,负责处理 Web 请求和响应

  • MyBatis 是持久层框架,用于实现数据库操作的 ORM 映射

Spring Boot 3 整合 SSM 的优势很明显

Spring Boot 3 对 SSM 框架的整合带来了以下核心优势:

  • 自动化配置:通过 Starter 依赖自动配置 Spring、Spring MVC 和 MyBatis 的基础环境

  • 简化部署:内置 Tomcat 等容器,可直接打包为可执行 JAR

  • 性能优化:基于 Spring 6 的响应式编程模型,支持 Reactive Stream

  • 微服务支持:天然适配 Spring Cloud 生态,便于构建微服务架构

SSM 各组件在场景中的角色

SpringBoot 整合 Spring 、SpringMVC 、MyBatis 进行数据访问场景开发

Spring 作为整个架构的基石,负责管理应用中的所有组件。它提供了两大核心功能:

  • 依赖注入 (DI):通过控制反转 (IoC) 实现组件间的松耦合
  • 面向切面编程 (AOP):用于处理事务、日志、安全等横切关注点

在 Spring Boot 3 中,不会再使用 Spring Framework 6 中复杂的配置,配置更加简化,通常使用 Java 注解声明为配置类代替 XML 配置:

1
2
3
4
5
6
7
8
9
@Configuration
@EnableTransactionManagement
public class AppConfig {
// Bean 定义和配置
@Bean
public UserService userService() {
return new UserServiceImpl();
}
}

Spring MVC 控制器层负责处理 Web 请求,遵循 MVC 设计模式:

  • Model:业务数据和逻辑
  • View:用户界面
  • Controller:处理请求并协调 Model 和 View

负责接收前端请求并返回响应,通常与 RESTful 接口设计结合:

在 Spring Boot 3 中,RESTful API 开发更加便捷:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
@RequestMapping("/api/users")
public class UserController {

@Autowired
private UserService userService;

@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}

MyBatis 作为 ORM 框架,负责处理数据库操作:

  • SQL 映射:通过 XML 或注解定义 SQL 语句
  • 结果映射:将查询结果映射到 Java 对象
  • 参数映射:将 Java 对象转换为 SQL 参数

在 Spring Boot 3 中,通常使用 Mapper 接口和注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Mapper
public interface UserMapper {

@Select("SELECT * FROM users WHERE id = #{id}")
User findById(Long id);

@Insert("INSERT INTO users(name, email) VALUES(#{name}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void save(User user);

@Update("UPDATE users SET name = #{name} WHERE id = #{id}")
void update(User user);

@Delete("DELETE FROM users WHERE id = #{id}")
void delete(Long id);
}

组件间协作流程

sequenceDiagram
Client->>Controller: HTTP 请求
Controller->>Service: 调用业务方法
Service->>Mapper: 调用数据访问方法
Mapper->>Database: 执行 SQL 操作
Database-->>Mapper: 返回结果
Mapper-->>Service: 返回数据对象
Service-->>Controller: 返回业务结果
Controller-->>Client: HTTP 响应

数据访问流程

2023

整合 SSM 场景的 Spring Boot 程序

创建SSM整合项目

Spring Initializier部分

image-20250611172727771
image-20250611172833188

所以,需要导入的关键依赖如下

核心依赖

Spring 核心依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • 作用:提供 Spring MVC 框架支持,包括 DispatcherServlet、控制器、视图解析等功能
  • 包含组件:Spring MVC、Spring Web、Tomcat 嵌入式服务器

MyBatis 集成依赖

1
2
3
4
5
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
  • 作用:MyBatis 与 Spring Boot 的集成包,自动配置 MyBatis 环境
  • 包含组件:MyBatis 核心库、MyBatis-Spring 集成、SQL 会话工厂等

如果你不使用 MyBatis,Spring 的 JPA 依赖的实现会与 MyBatis 的基本功能重叠,在标准 SSM 架构中,通常使用 MyBatis 而非 JPA。若同时存在两者,需注意配置冲突

数据库连接依赖

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
  • spring-boot-starter-jdbc:提供 JDBC 抽象层,包含 HikariCP 连接池
  • mysql-connector-j:MySQL 数据库驱动,用于连接 MySQL 数据库

相关配置项

基本配置略过了

数据源相关配置

  • 采用 MySQL 数据库,使用 HikariCP 连接池 (默认)
  • 连接参数包含时区设置、字符编码等关键配置
  • 连接池参数根据应用并发量可调整
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
# 数据库连接信息
# 数据库连接URL
spring.datasource.url=jdbc:mysql://localhost:3306/ssm_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
# 用户名和密码
spring.datasource.username=root
spring.datasource.password=root
# MySQL 8.x驱动类名
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# 连接池配置 (HikariCP)
spring.datasource.hikari.minimum-idle=5
# 连接池保持的最小空闲连接数,确保有足够连接处理突发请求

spring.datasource.hikari.maximum-pool-size=15
# 连接池允许的最大连接数,根据服务器资源和应用并发量调整
# 计算公式参考:CPU核心数 * 2 + 磁盘数

spring.datasource.hikari.auto-commit=true
# 是否自动提交事务,默认true,适合大多数场景
# 若需要手动控制事务,可设置为false

spring.datasource.hikari.idle-timeout=30000
# 空闲连接的超时时间(毫秒),超过此时间的空闲连接将被释放

spring.datasource.hikari.pool-name=HikariCP
# 连接池名称,用于日志和JMX监控,方便识别

spring.datasource.hikari.max-lifetime=1800000
# 连接的最大生命周期(毫秒),超过此时间的连接将被强制关闭

spring.datasource.hikari.connection-timeout=30000
# 获取连接的超时时间(毫秒),超过此时间将抛出异常

spring.datasource.hikari.connection-test-query=SELECT 1
# 连接测试SQL,用于验证连接是否有效
  1. 连接池大小调优
    • 过小会导致连接争用,影响性能
    • 过大会占用过多资源,增加 GC 压力
    • 计算公式:(核心数 * 2) + 磁盘数 作为参考值
  2. 安全性考虑
    • 生产环境应使用更安全的密码管理方式(如配置中心、Vault 等)
    • 考虑启用 SSL 连接(移除useSSL=false参数)
  3. 性能优化
    • 根据业务特性调整minimum-idlemaximum-pool-size
    • 对于读写比例高的应用,可考虑读写分离配置
  4. 监控与调优
    • 结合 Actuator 监控连接池指标(活跃连接数、等待队列等)
    • 根据监控数据动态调整连接池参数

MyBatis 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ===========================================
# MyBatis配置
# ===========================================
# Mapper XML文件位置,指定 mapper 映射文件的位置
mybatis.mapper-locations=classpath:mapper/*.xml

# 实体类包路径,简化XML中类型引用
mybatis.type-aliases-package=com.example.entity

# MyBatis核心配置
mybatis.configuration.map-underscore-to-camel-case=true # 开启驼峰命名自动映射
mybatis.configuration.cache-enabled=true # 开启二级缓存
mybatis.configuration.lazy-loading-enabled=true # 开启懒加载
mybatis.configuration.aggressive-lazy-loading=false # 关闭激进懒加载
mybatis.configuration.default-fetch-size=100 # 默认获取记录数
mybatis.configuration.default-statement-timeout=30 # 默认SQL超时时间

安装 MyBatisX 插件,帮我们⽣成 Mapper 接⼝的 xml 文件即可

实体类相关编写

实体类就使用简单的 User 类

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
66
67
68
69
70
71
72
73
package edu.software.ergoutree.springbootssmdataassess.entity;

import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
* 用户实体类
* 使用@Data注解自动生成getter、setter、equals、hashCode和toString方法
* 实现Serializable接口使对象可序列化,便于网络传输和对象持久化
*/
@Data
public class User implements Serializable {

/**
* 用户ID,主键
* 在数据库中通常设置为自增长
*/
private Long id;

/**
* 用户名
* 用户登录系统的唯一标识
*/
private String username;

/**
* 密码
* 存储时应当加密处理,不应明文存储
*/
private String password;

/**
* 用户真实姓名
*/
private String realName;

/**
* 用户邮箱
*/
private String email;

/**
* 用户手机号
*/
private String phone;

/**
* 用户状态
* 0: 禁用
* 1: 启用
*/
private Integer status;

/**
* 创建时间
* 记录用户创建的时间戳
*/
private Date createTime;

/**
* 更新时间
* 记录用户信息最后一次更新的时间戳
*/
private Date updateTime;

/**
* 序列化版本ID
* 用于序列化和反序列化过程中的版本控制
*/
private static final long serialVersionUID = 1L;
}

MyBatis 相关 mapper

首先,来复习一下 MyBatis,MyBatis 是一个半自动化的 ORM(对象关系映射)框架,核心功能是将 SQL 语句与 Java 对象进行映射。在 SSM 项目中,MyBatis 主要负责数据持久层的操作,其核心组件包括:

  1. SqlSessionFactory:创建 SqlSession 的工厂,通过配置文件或代码构建
  2. SqlSession:提供数据库操作的接口,如查询、更新、事务管理等
  3. Mapper 接口:定义数据库操作方法的接口
  4. Mapper XML 文件:实现 Mapper 接口方法的 SQL 映射配置
  5. TypeHandler:处理 Java 类型与数据库类型的转换
  6. ResultMap:定义复杂的结果集映射规则

所以,我们包含 CURD 操作的 UserMapper 就如下

那么,如何进行 CURD 的开发呢,主要开发流程如下

  • 编写 Bean
  • 编写 Mapper
  • 使用插件或手写 MapperXML
  • 编写 CURD 语句
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package edu.software.ergoutree.springbootssmdataassess.mapper;

import edu.software.ergoutree.springbootssmdataassess.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
* 用户数据访问层接口
* 使用@Mapper注解标识该接口为MyBatis的Mapper接口
* MyBatis会自动为该接口创建代理对象,实现与数据库的交互
*/
@Mapper
public interface UserMapper {

/**
* 插入新用户
*
* @param user 用户对象
* @return 影响的行数
*/
int insert(User user);

/**
* 根据主键删除用户
*
* @param id 用户ID
* @return 影响的行数
*/
int deleteById(Long id);

/**
* 更新用户信息
*
* @param user 用户对象
* @return 影响的行数
*/
int update(User user);

/**
* 根据主键查询用户
*
* @param id 用户ID
* @return 用户对象
*/
User selectById(Long id);

/**
* 查询所有用户
*
* @return 用户列表
*/
List<User> selectAll();

/**
* 根据用户名查询用户
*
* @param username 用户名
* @return 用户对象
*/
User selectByUsername(String username);

/**
* 根据条件查询用户列表
*
* @param user 查询条件
* @return 用户列表
*/
List<User> selectByCondition(User user);

/**
* 批量删除用户
*
* @param ids 用户ID数组
* @return 影响的行数
*/
int batchDelete(@Param("ids") List<Long> ids);

/**
* 统计用户总数
*
* @return 用户总数
*/
int count();
}

然后,我们就需要为 Mapper 编写对应的 xml 文件,在这里可以使用对应的 MyBatisX 插件自动生成

在这里复习以下 MyBatis 提供的 SQL 标签:

  • <where>:自动处理 SQL 语句中的 WHERE 关键字和多余的 AND/OR
  • <if>:根据条件判断是否包含某段 SQL
  • <>set:自动处理 UPDATE 语句中的 SET 关键字和多余的逗号
  • <foreach>:遍历集合参数,生成 IN 条件或批量操作
  • <choose>/<when>/<otherwise>:类似 Java 的 switch-case 结构
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="edu.software.ergoutree.springbootssmdataassess.mapper.UserMapper">

<!-- 结果映射,将数据库字段映射到实体类属性 -->
<resultMap id="BaseResultMap" type="edu.software.ergoutree.springbootssmdataassess.entity.User">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="username" jdbcType="VARCHAR" property="username" />
<result column="password" jdbcType="VARCHAR" property="password" />
<result column="real_name" jdbcType="VARCHAR" property="realName" />
<result column="email" jdbcType="VARCHAR" property="email" />
<result column="phone" jdbcType="VARCHAR" property="phone" />
<result column="status" jdbcType="INTEGER" property="status" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
</resultMap>

<!-- 所有字段 -->
<sql id="Base_Column_List">
id, username, password, real_name, email, phone, status, create_time, update_time
</sql>

<!-- 插入用户记录 -->
<insert id="insert" parameterType="edu.software.ergoutree.springbootssmdataassess.entity.User" useGeneratedKeys="true" keyProperty="id">
insert into user (
username, password, real_name, email, phone, status, create_time, update_time
) values (
#{username,jdbcType=VARCHAR},
#{password,jdbcType=VARCHAR},
#{realName,jdbcType=VARCHAR},
#{email,jdbcType=VARCHAR},
#{phone,jdbcType=VARCHAR},
#{status,jdbcType=INTEGER},
now(),
now()
)
</insert>

<!-- 根据主键删除用户 -->
<delete id="deleteById" parameterType="java.lang.Long">
delete from user where id = #{id,jdbcType=BIGINT}
</delete>

<!-- 批量删除用户 -->
<delete id="batchDelete" parameterType="java.util.List">
delete from user where id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>

<!-- 更新用户信息 -->
<update id="update" parameterType="edu.software.ergoutree.springbootssmdataassess.entity.User">
update user
<set>
<if test="username != null">username = #{username,jdbcType=VARCHAR},</if>
<if test="password != null">password = #{password,jdbcType=VARCHAR},</if>
<if test="realName != null">real_name = #{realName,jdbcType=VARCHAR},</if>
<if test="email != null">email = #{email,jdbcType=VARCHAR},</if>
<if test="phone != null">phone = #{phone,jdbcType=VARCHAR},</if>
<if test="status != null">status = #{status,jdbcType=INTEGER},</if>
update_time = now()
</set>
where id = #{id,jdbcType=BIGINT}
</update>

<!-- 根据主键查询用户 -->
<select id="selectById" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from user
where id = #{id,jdbcType=BIGINT}
</select>

<!-- 查询所有用户 -->
<select id="selectAll" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from user
order by id desc
</select>

<!-- 根据用户名查询用户 -->
<select id="selectByUsername" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from user
where username = #{username,jdbcType=VARCHAR}
</select>

<!-- 根据条件查询用户列表 -->
<select id="selectByCondition" parameterType="edu.software.ergoutree.springbootssmdataassess.entity.User" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from user
<where>
<if test="username != null and username != ''">
and username like concat('%', #{username}, '%')
</if>
<if test="realName != null and realName != ''">
and real_name like concat('%', #{realName}, '%')
</if>
<if test="email != null and email != ''">
and email = #{email}
</if>
<if test="phone != null and phone != ''">
and phone = #{phone}
</if>
<if test="status != null">
and status = #{status}
</if>
</where>
order by id desc
</select>

<!-- 统计用户总数 -->
<select id="count" resultType="java.lang.Integer">
select count(*) from user
</select>

</mapper>

之后就是对应的 Service 层 和 Controller 层

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
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package edu.software.ergoutree.springbootssmdataassess.service;

import edu.software.ergoutree.springbootssmdataassess.entity.User;

import java.util.List;

/**
* 用户服务接口
* 定义用户相关的业务逻辑操作
*/
public interface UserService {

/**
* 添加用户
*
* @param user 用户信息
* @return 添加成功返回true,否则返回false
*/
boolean addUser(User user);

/**
* 删除用户
*
* @param id 用户ID
* @return 删除成功返回true,否则返回false
*/
boolean deleteUser(Long id);

/**
* 更新用户信息
*
* @param user 用户信息
* @return 更新成功返回true,否则返回false
*/
boolean updateUser(User user);

/**
* 根据ID查询用户
*
* @param id 用户ID
* @return 用户对象,如果不存在返回null
*/
User getUserById(Long id);

/**
* 查询所有用户
*
* @return 用户列表
*/
List<User> getAllUsers();

/**
* 根据用户名查询用户
*
* @param username 用户名
* @return 用户对象,如果不存在返回null
*/
User getUserByUsername(String username);

/**
* 根据条件查询用户
*
* @param user 查询条件
* @return 符合条件的用户列表
*/
List<User> getUsersByCondition(User user);

/**
* 批量删除用户
*
* @param ids 用户ID列表
* @return 删除成功返回true,否则返回false
*/
boolean batchDeleteUsers(List<Long> ids);

/**
* 获取用户总数
*
* @return 用户总数
*/
int countUsers();
}

实现类

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package edu.software.ergoutree.springbootssmdataassess.service.impl;

import edu.software.ergoutree.springbootssmdataassess.entity.User;
import edu.software.ergoutree.springbootssmdataassess.mapper.UserMapper;
import edu.software.ergoutree.springbootssmdataassess.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
* 用户服务实现类
* 使用@Service注解标识该类为Spring的服务层组件
*/
@Service
public class UserServiceImpl implements UserService {

/**
* 用户数据访问对象
* 使用@Autowired注解自动注入UserMapper依赖
*/
@Autowired
private UserMapper userMapper;

/**
* 添加用户
* 使用@Transactional注解声明事务,确保数据一致性
*
* @param user 用户信息
* @return 添加成功返回true,否则返回false
*/
@Override
@Transactional
public boolean addUser(User user) {
// 设置默认状态为启用
if (user.getStatus() == null) {
user.setStatus(1);
}
return userMapper.insert(user) > 0;
}

/**
* 删除用户
*
* @param id 用户ID
* @return 删除成功返回true,否则返回false
*/
@Override
@Transactional
public boolean deleteUser(Long id) {
return userMapper.deleteById(id) > 0;
}

/**
* 更新用户信息
*
* @param user 用户信息
* @return 更新成功返回true,否则返回false
*/
@Override
@Transactional
public boolean updateUser(User user) {
return userMapper.update(user) > 0;
}

/**
* 根据ID查询用户
*
* @param id 用户ID
* @return 用户对象,如果不存在返回null
*/
@Override
public User getUserById(Long id) {
return userMapper.selectById(id);
}

/**
* 查询所有用户
*
* @return 用户列表
*/
@Override
public List<User> getAllUsers() {
return userMapper.selectAll();
}

/**
* 根据用户名查询用户
*
* @param username 用户名
* @return 用户对象,如果不存在返回null
*/
@Override
public User getUserByUsername(String username) {
return userMapper.selectByUsername(username);
}

/**
* 根据条件查询用户
*
* @param user 查询条件
* @return 符合条件的用户列表
*/
@Override
public List<User> getUsersByCondition(User user) {
return userMapper.selectByCondition(user);
}

/**
* 批量删除用户
*
* @param ids 用户ID列表
* @return 删除成功返回true,否则返回false
*/
@Override
@Transactional
public boolean batchDeleteUsers(List<Long> ids) {
return userMapper.batchDelete(ids) > 0;
}

/**
* 获取用户总数
*
* @return 用户总数
*/
@Override
public int countUsers() {
return userMapper.count();
}
}

Controller 层的编写和之前没有什么差别

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package edu.software.ergoutree.springbootssmdataassess.controller;

import edu.software.ergoutree.springbootssmdataassess.entity.User;
import edu.software.ergoutree.springbootssmdataassess.service.UserService;
import edu.software.ergoutree.springbootssmdataassess.common.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* 用户控制器
* 处理用户相关的HTTP请求
* 使用@RestController注解标识该类为控制器,并且方法的返回值会自动转换为JSON格式
* 使用@RequestMapping注解指定该控制器的基础URL路径
*/
@RestController
@RequestMapping("/users")
public class UserController {

/**
* 用户服务
* 使用@Autowired注解自动注入UserService依赖
*/
@Autowired
private UserService userService;

/**
* 创建用户
* 使用@PostMapping注解处理POST请求
*
* @param user 用户信息,从请求体中获取
* @return 创建结果
*/
@PostMapping
public Result<User> createUser(@RequestBody User user) {
boolean result = userService.addUser(user);
if (result) {
return Result.success("用户创建成功", user);
} else {
return Result.error("用户创建失败");
}
}

/**
* 根据ID获取用户
* 使用@GetMapping注解处理GET请求
* 使用@PathVariable注解获取URL路径中的参数
*
* @param id 用户ID
* @return 用户信息
*/
@GetMapping("/{id}")
public Result<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
if (user != null) {
return Result.success(user);
} else {
return Result.error(404, "用户不存在");
}
}

/**
* 获取所有用户
*
* @return 用户列表
*/
@GetMapping
public Result<List<User>> getAllUsers() {
List<User> users = userService.getAllUsers();
return Result.success(users);
}

/**
* 更新用户
* 使用@PutMapping注解处理PUT请求
*
* @param id 用户ID
* @param user 更新的用户信息
* @return 更新结果
*/
@PutMapping("/{id}")
public Result<Void> updateUser(@PathVariable Long id, @RequestBody User user) {
// 使用lombok生成的setter方法设置ID
user.setId(id);
boolean result = userService.updateUser(user);
if (result) {
return Result.success("用户更新成功", null);
} else {
return Result.error("用户更新失败");
}
}

/**
* 删除用户
* 使用@DeleteMapping注解处理DELETE请求
*
* @param id 用户ID
* @return 删除结果
*/
@DeleteMapping("/{id}")
public Result<Void> deleteUser(@PathVariable Long id) {
boolean result = userService.deleteUser(id);
if (result) {
return Result.success("用户删除成功", null);
} else {
return Result.error("用户删除失败");
}
}

/**
* 根据用户名查询用户
* 使用@GetMapping注解处理GET请求
* 使用@RequestParam注解获取查询参数
*
* @param username 用户名
* @return 用户信息
*/
@GetMapping("/by-username")
public Result<User> getUserByUsername(@RequestParam String username) {
User user = userService.getUserByUsername(username);
if (user != null) {
return Result.success(user);
} else {
return Result.error(404, "用户不存在");
}
}

/**
* 条件查询用户
*
* @param user 查询条件
* @return 符合条件的用户列表
*/
@PostMapping("/search")
public Result<List<User>> searchUsers(@RequestBody User user) {
List<User> users = userService.getUsersByCondition(user);
return Result.success(users);
}

/**
* 批量删除用户
*
* @param ids 用户ID列表
* @return 删除结果
*/
@DeleteMapping("/batch")
public Result<Void> batchDeleteUsers(@RequestBody List<Long> ids) {
boolean result = userService.batchDeleteUsers(ids);
if (result) {
return Result.success("批量删除成功", null);
} else {
return Result.error("批量删除失败");
}
}

/**
* 获取用户总数
*
* @return 用户总数
*/
@GetMapping("/count")
public Result<Integer> countUsers() {
int count = userService.countUsers();
return Result.success(count);
}
}

启动类

启动类也要有相应改变

  • @MapperScan(basePackages = ““) mapper 文件的包位置,批量扫描注解,供扫描哪个包下的所有接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package edu.software.ergoutree.springbootssmdataassess;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MapperScan(basePackages = "edu.software.ergoutree.springbootssmdataassess.mapper")
@SpringBootApplication
public class SpringBootSsmDataAssessApplication {

public static void main(String[] args) {
SpringApplication.run(SpringBootSsmDataAssessApplication.class, args);
}

}

配置类中也要配置每个接口的xml 的位置

1
mybatis.mapper-locations=classpath:mapper/*.xml

这样就会自动进行绑定

所以说,接口的全类名和namespace的值是一一对应的

测试发现接口都能正常使用获得信息

image-20250613105849633

SSM 整合总结

SSM 的核心整合的目标

就是基于 Spring Boot 3.5,通过 自动配置 简化传统 SSM 繁琐的 XML 配置,快速搭建 “控制层(SpringMVC)+ 业务层(Spring)+ 持久层(MyBatis)” 三层架构,实现:

  • 浏览器 / 前端 → SpringMVC(Controller) 接收请求
  • 业务逻辑 → Spring(Service) 处理(含事务管理)
  • 数据库操作 → MyBatis(Mapper) 执行 SQL

整合步骤如下:

依赖导入:用 Starter 简化配置

  • spring-boot-starter-web:包含 SpringMVC 核心功能,自动配置 DispatcherServletHandlerMapping 等。

  • mybatis-spring-boot-starter:自动配置 SqlSessionFactoryMapperScannerConfigurer,无需手动写 MyBatis 的 XML 配置。

数据源配置:连接数据库

  • Spring Boot 会读取 application.yml/application.properties 中的配置,自动创建数据源(默认用 HikariCP 连接池)。

持久层(MyBatis Mapper):数据库操作

  • MyBatis 负责执行 SQL,通过 Mapper 接口 + XML 映射文件注解 实现。
  • 首先先定义 Mapper 接口,创建 Mapper 接口,声明数据库操作方法(类似 DAO 层)
  • 编写 XML 映射文件,在 resources/mapper/ 下创建 UserMapper.xml,编写复杂 SQL(与 Mapper 接口方法对应):

业务层(Spring Service):事务与逻辑

  • Service 层通过 @Service 被 Spring 管理,依赖 Mapper 操作数据库,并用 @Transactional 管理事务。

控制层(SpringMVC Controller):接收请求

  • Controller 负责接收前端请求,调用 Service 处理业务,返回响应(视图或 JSON)。

启动类:开启自动扫描

  • Spring Boot 3.5 中,启动类需标注 @SpringBootApplication,自动扫描同包及子包的 Bean。
  • 需要开启@MapperScan:批量扫描 Mapper 接口包,无需每个接口加 @Mapper,更简洁

SSM 自动配置原理和分析

Spring Boot 能简化 SSM 整合,核心依赖 “自动配置(Auto-Configuration)” 机制。其底层通过以下 3 个核心组件实现:

jdbc 场景的自动配置——DataSourceAutoConfiguration

这个部分配置了数据源等基本信息

mybatis-spring-boot-starter会导入数据库的场景 spring-boot-starter-jdbc,jdbc是操作数据库的场景

JDBC场景的几个自动配置如下,他们的自动配置使得Spring数据访问具有的底层能力:数据源、 JdbcTemplate 、事务

DataSourceAutoConfiguration

全类名:org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

  • 核心作用:自动创建数据库连接池(DataSource),是所有数据库操作的基础。实现了数据源的自动配置

    • 所有和数据源有关的配置都绑定在DataSourceProperties

      1
      2
      3
      4
      5
      6
      @ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
      @ConditionalOnMissingBean(
      type = {"io.r2dbc.spi.ConnectionFactory"}
      )
      @EnableConfigurationProperties({DataSourceProperties.class})
      @Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceCheckpointRestoreConfiguration.class})
  • 绑定配置:将 application.ymlspring.datasource 前缀的配置(如 urlusernamepassword),绑定到 DataSourceProperties 类。

  • 默认实现:默认使用 HikariDataSource(高性能连接池,Spring Boot 2.x+ 起默认),替代传统的 Tomcat JDBCDBCP

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    static class PooledDataSourceCondition extends AnyNestedCondition {
    PooledDataSourceCondition() {
    super(ConfigurationPhase.PARSE_CONFIGURATION);
    }

    @Conditional({PooledDataSourceAvailableCondition.class})
    static class PooledDataSourceAvailable {
    PooledDataSourceAvailable() {
    }
    }

    @ConditionalOnProperty(
    prefix = "spring.datasource",
    name = {"type"}
    )
    static class ExplicitType {
    ExplicitType() {
    }
    }
    • 连接池优先级:HikariCP > Tomcat JDBC > DBCP2 > Oracle UCP。
    • 默认选择 HikariCP:若类路径中同时存在多个连接池,Spring Boot 会优先使用 HikariCP(性能最优)。

JdbcTemplateAutoConfiguration

全类名:org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,这个是负责JdbcTemplate自动配置相关的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 确保数据源已创建
@AutoConfiguration(
after = {DataSourceAutoConfiguration.class}
)
// 仅当类路径中存在 DataSource 和 JdbcTemplate 时才生效,确保用户已引入 JDBC 相关依赖。
@ConditionalOnClass({DataSource.class, JdbcTemplate.class})
// 仅当容器中存在 唯一的 DataSource Bean 时才生效
@ConditionalOnSingleCandidate(DataSource.class)
// 启用 JdbcProperties 配置类,绑定 spring.jdbc 前缀的配置
@EnableConfigurationProperties({JdbcProperties.class})
@Import({DatabaseInitializationDependencyConfigurer.class, JdbcTemplateConfiguration.class, NamedParameterJdbcTemplateConfiguration.class})
public class JdbcTemplateAutoConfiguration {
public JdbcTemplateAutoConfiguration() {
}
}
  • 核心作用:自动创建 JdbcTemplate Bean,简化原生 JDBC 的繁琐操作(如手动获取连接、关闭资源)。

    • 它给容器中放入了JdbcTemplate组件,通过JdbcTemplateConfiguration.class引入,用于操作数据库

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      @Configuration(
      proxyBeanMethods = false
      )
      @ConditionalOnMissingBean({JdbcOperations.class})
      class JdbcTemplateConfiguration {
      JdbcTemplateConfiguration() {
      }

      @Bean
      @Primary
      JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties) {
      JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
      JdbcProperties.Template template = properties.getTemplate();
      jdbcTemplate.setFetchSize(template.getFetchSize());
      jdbcTemplate.setMaxRows(template.getMaxRows());
      if (template.getQueryTimeout() != null) {
      jdbcTemplate.setQueryTimeout((int)template.getQueryTimeout().getSeconds());
      }

      return jdbcTemplate;
      }
      }
  • 使用方式:直接 @Autowired 注入 JdbcTemplate,即可执行 SQL

DataSourceTransactionManagerAutoConfiguration

全类名:org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,这个是负责数据源事务管理器自动配置相关的

  • 核心作用:自动创建 DataSourceTransactionManager(事务管理器),作为 Spring 声明式事务的基础支撑,与 @Transactional 注解协同,实现方法执行失败时的自动回滚等事务管理功能 ,为基于 JDBC 操作的数据库事务提供统一的管理机制。

  • 使用场景:在 Service 层方法上添加 @Transactional,即可让方法执行失败时自动回滚:

  • 自动配置条件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @AutoConfiguration(
    before = {TransactionAutoConfiguration.class},
    after = {DataSourceAutoConfiguration.class, TransactionManagerCustomizationAutoConfiguration.class}
    )
    // 当类路径中存在 DataSource(数据源)、JdbcTemplate(JDBC 操作模板)、TransactionManager(事务管理器接口)时,该自动配置才会生效,确保相关基础类已引入 。
    @ConditionalOnClass({DataSource.class, JdbcTemplate.class, TransactionManager.class})
    @AutoConfigureOrder(Integer.MAX_VALUE)
    @EnableConfigurationProperties({DataSourceProperties.class})
    public class DataSourceTransactionManagerAutoConfiguration {
  • 内部配置类 JdbcTransactionManagerConfiguration

    • 条件控制:当容器中存在单一的 DataSource Bean 时才会生效

    • 创建事务管理器逻辑

      transactionManager 方法:创建 DataSourceTransactionManager Bean,先调用 createTransactionManager 方法构建基础事务管理器,再利用 ObjectProvider 对事务管理器进行自定义(若有自定义逻辑的话 )。

      createTransactionManager 方法就是进行统一异常转换

      DataSourceTransactionManager(基础的数据源事务管理器实现 ),最终都是构建出适配数据源的事务管理对象,用于管理数据库操作的事务。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      static class JdbcTransactionManagerConfiguration {
      JdbcTransactionManagerConfiguration() {
      }

      @Bean
      @ConditionalOnMissingBean({TransactionManager.class})
      DataSourceTransactionManager transactionManager(Environment environment, DataSource dataSource, ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
      DataSourceTransactionManager transactionManager = this.createTransactionManager(environment, dataSource);
      transactionManagerCustomizers.ifAvailable((customizers) -> {
      customizers.customize(transactionManager);
      });
      return transactionManager;
      }

      private DataSourceTransactionManager createTransactionManager(Environment environment, DataSource dataSource) {
      return (DataSourceTransactionManager)((Boolean)environment.getProperty("spring.dao.exceptiontranslation.enabled", Boolean.class, Boolean.TRUE) ? new JdbcTransactionManager(dataSource) : new DataSourceTransactionManager(dataSource));
      }
      }

XADataSourceAutoConfiguration(XA 数据源自动配置)

全类名:org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,基于XA⼆阶提交协议的分布式事务数据源

  • 核心作用:支持 分布式事务(跨多个数据库 / 数据源的事务),基于 XA 协议(两阶段提交)保证多个数据源操作的原子性。
  • 典型场景:微服务中,一个业务操作需要同时更新 订单库库存库,用 XA 事务保证两者要么都成功,要么都回滚。

JndiDataSourceAutoConfiguration(JNDI 数据源自动配置)

全类名:org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration

  • 核心作用:从 JNDI(Java Naming and Directory Interface)获取数据源,适用于传统 Java EE 应用服务器(如 Tomcat、WebLogic)的场景。

配置了MyBatis的整合流程——MyBatisAutoConfiguration

mybatis-spring-boot-starter 会导入 mybatis 的自动配置包mybatis-spring-boot-autoconfigure

之后就会默认加载两个自动配置类

  • org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration
  • org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

核心类:MyBatisAutoConfiguration 解析

全类名 org.mybatis.spring.boot.autoconfigure.MyBatisAutoConfiguration

核心作用

  • 自动整合 MyBatis 到 Spring Boot 体系,替代传统 XML 配置(如 mybatis-config.xmlSqlSessionFactoryBean 配置)。
  • 自动创建 SqlSessionFactorySqlSessionTemplate 等核心组件,让开发者直接注入 Mapper 接口即可使用。

自动配置的触发条件

  • 首先会进行依赖的导入

    • 当项目引入mybatis-spring-boot-starter时,会间接引入:
      • mybatis-spring-boot-autoconfigure:包含 MyBatis 自动配置类(如 MyBatisAutoConfiguration)。
      • mybatis-spring:实现 MyBatis 与 Spring 的整合(如 SqlSessionFactoryBean)。
  • 自动配置通过如下条件注解决定是否生效

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Configuration(
    proxyBeanMethods = false
    )
    // 存在 MyBatis 核心类时生效
    @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
    // 存在 DataSource Bean 时生效(依赖数据源),所以存在数据源才生效
    @ConditionalOnSingleCandidate(DataSource.class)
    // 绑定 application.yml 配置
    @EnableConfigurationProperties({MybatisProperties.class})
    // 在数据源配置后执行
    @AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
    public class MybatisAutoConfiguration implements InitializingBean {
    • 其中@ConditionalOnBean(DataSource.class):依赖 DataSourceAutoConfiguration 已创建的数据源。所以必须先创建数据源,并且数据源配置好之后才会生效,这和 Hibertnate 会自动创建数据表有一定的差别
    • @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})会放入 MyBatis 和核心组件,session工厂相关的两个组件,分别是
      • 给容器中放 SqlSessionFactory 组件。创建和数据库的⼀次会话
      • 给容器中 SqlSessionTemplate 组件。操作数据库

MyBatis的所有配置绑定在 MybatisProperties

可以发现public class MybatisAutoConfiguration implements InitializingBean中存在

1
@EnableConfigurationProperties({MybatisProperties.class})

所以,MybatisAutoConfiguration自动配置需要启用MybatisProperties.class,也就是说MyBatis的所有配置绑定在 MybatisProperties,我们来看看其中有什么内容

MybatisProperties 是 Spring Boot 整合 MyBatis 时的核心配置类,通过 @ConfigurationProperties(prefix = "mybatis") 绑定 application.ymlmybatis 前缀的配置。

1
2
3
@ConfigurationProperties(
prefix = "mybatis"
)

其中的一些基础配置项如表

配置项 类型 对应 application.yml 配置 作用描述
configLocation String mybatis.config-location 指定 MyBatis 全局配置文件路径(如 mybatis-config.xml),优先级高于 application.yml 配置
mapperLocations String[] mybatis.mapper-locations 扫描 Mapper XML 文件路径(如 classpath:mapper/*.xml),支持 Ant 风格路径
typeAliasesPackage String mybatis.type-aliases-package 扫描实体类包,自动注册类名作为别名(如 User 替代 com.example.User
typeAliasesSuperType Class<?> mybatis.type-aliases-super-type 仅注册指定父类的子类作为类型别名,缩小扫描范围
checkConfigLocation boolean mybatis.check-config-location 启动时检查配置文件是否存在,默认 false

对应的代码如下

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
public String getConfigLocation() {
return this.configLocation;
}

public void setConfigLocation(String configLocation) {
this.configLocation = configLocation;
}

public String[] getMapperLocations() {
return this.mapperLocations;
}

public void setMapperLocations(String[] mapperLocations) {
this.mapperLocations = mapperLocations;
}

public String getTypeHandlersPackage() {
return this.typeHandlersPackage;
}

public void setTypeHandlersPackage(String typeHandlersPackage) {
this.typeHandlersPackage = typeHandlersPackage;
}

public String getTypeAliasesPackage() {
return this.typeAliasesPackage;
}

public void setTypeAliasesPackage(String typeAliasesPackage) {
this.typeAliasesPackage = typeAliasesPackage;
}

public Class<?> getTypeAliasesSuperType() {
return this.typeAliasesSuperType;
}

public void setTypeAliasesSuperType(Class<?> typeAliasesSuperType) {
this.typeAliasesSuperType = typeAliasesSuperType;
}

public boolean isCheckConfigLocation() {
return this.checkConfigLocation;
}

public void setCheckConfigLocation(boolean checkConfigLocation) {
this.checkConfigLocation = checkConfigLocation;
}

类型处理器与语言驱动配置

配置项 类型 对应配置 作用描述
typeHandlersPackage String mybatis.type-handlers-package 扫描类型处理器(TypeHandler)包,自动注册自定义类型转换逻辑
defaultScriptingLanguageDriver Class<? extends LanguageDriver> mybatis.default-scripting-language-driver 指定默认脚本语言驱动(如 org.mybatis.scripting.xmltags.XMLLanguageDriver

源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public String getTypeHandlersPackage() {
return this.typeHandlersPackage;
}

public void setTypeHandlersPackage(String typeHandlersPackage) {
this.typeHandlersPackage = typeHandlersPackage;
}
public Class<? extends LanguageDriver> getDefaultScriptingLanguageDriver() {
return this.defaultScriptingLanguageDriver;
}

public void setDefaultScriptingLanguageDriver(Class<? extends LanguageDriver> defaultScriptingLanguageDriver) {
this.defaultScriptingLanguageDriver = defaultScriptingLanguageDriver;
}

执行器与事务配置

配置项 类型 对应配置 作用描述
executorType ExecutorType mybatis.executor-type 设置执行器类型:SIMPLE(默认)、REUSE(重用连接)、BATCH(批量执行)

而其中,核心配置项便是CoreConfiguration 内部类CoreConfiguration 封装了 MyBatis Configuration 类的核心配置,对应 mybatis.configuration 前缀的配置,是 MyBatis 行为的核心控制项。这个类其中有很多内容,只挑出重要的进行讲解

映射与驼峰命名配置
配置项 类型 对应配置 作用描述
mapUnderscoreToCamelCase Boolean mybatis.configuration.map-underscore-to-camel-case 开启下划线转驼峰映射(如 user_nameuserName
autoMappingBehavior AutoMappingBehavior mybatis.configuration.auto-mapping-behavior 自动映射行为:NONE/PARTIAL(默认)/FULL
autoMappingUnknownColumnBehavior AutoMappingUnknownColumnBehavior mybatis.configuration.auto-mapping-unknown-column-behavior 未知列映射策略:NONE(忽略)/WARNING(警告)/FAILING(报错)

示例

1
2
3
4
mybatis:
configuration:
map-underscore-to-camel-case: true # 开启驼峰映射
auto-mapping-behavior: FULL # 全量自动映射

2. 延迟加载与缓存配置

配置项 类型 对应配置 作用描述
lazyLoadingEnabled Boolean mybatis.configuration.lazy-loading-enabled 启用延迟加载(按需加载关联对象)
aggressiveLazyLoading Boolean mybatis.configuration.aggressive-lazy-loading 激进延迟加载(加载一个属性时加载所有关联属性)
cacheEnabled Boolean mybatis.configuration.cache-enabled 启用二级缓存(基于命名空间的缓存)
localCacheScope LocalCacheScope mybatis.configuration.local-cache-scope 本地缓存作用域:SESSION(默认)/STATEMENT

3. 执行与超时配置

配置项 类型 对应配置 作用描述
defaultStatementTimeout Integer mybatis.configuration.default-statement-timeout SQL 执行超时时间(秒)
defaultFetchSize Integer mybatis.configuration.default-fetch-size 结果集批量获取行数(优化大数据查询)
useGeneratedKeys Boolean mybatis.configuration.use-generated-keys 启用自增主键获取(如 MySQL 的 AUTO_INCREMENT

4. 日志与 VFS 配置

配置项 类型 对应配置 作用描述
logImpl Class<? extends Log> mybatis.configuration.log-impl 指定日志实现(如 org.apache.ibatis.logging.stdout.StdOutImpl
vfsImpl Class<? extends VFS> mybatis.configuration.vfs-impl

其中,MybatisProperties 提供 resolveMapperLocations() 方法解析 mapperLocations 配置,支持 Ant 风格路径(如 classpath*:mapper/**/*.xml),内部通过 PathMatchingResourcePatternResolver 实现资源扫描。

1
2
3
4
5
public Resource[] resolveMapperLocations() {
return Stream.of(Optional.ofNullable(mapperLocations).orElse(new String[0]))
.flatMap(location -> Stream.of(getResources(location)))
.toArray(Resource[]::new);
}

configLocation 指定的 XML 配置 > application.ymlmybatis.configuration 配置 > MyBatis 默认值。

自动配置流程

  • Spring Boot 启动时,MyBatisAutoConfiguration 读取 MybatisProperties 配置。
  • 通过 CoreConfiguration.applyTo(Configuration) 方法将配置应用到 MyBatis 的 Configuration 对象。

通过 MybatisProperties,Spring Boot 实现了 MyBatis 配置的全量绑定,开发者无需编写 XML 配置文件,仅通过 application.yml 即可完成 MyBatis 的所有核心配置,真正实现 “约定优于配置” 的开发体验。

每个Mapper接⼝的代理对象是怎么创建放到容器中——@MapperScan原理

MyBatis 中 Mapper 是接口,本身无法直接创建对象。@MapperScan 的作用是: 通过 动态代理 + Spring Bean 注册,让每个 Mapper 接口生成代理对象(能执行 SQL),并注入到 Spring 容器,最终实现 @Autowired UserMapper userMapper 直接用。

sequenceDiagram
    participant 启动类 as Spring Boot 启动类(@MapperScan)
    participant ImportRegistrar as MapperScannerRegistrar(@Import 引入)
    participant ScannerConfigurer as MapperScannerConfigurer(注册 BeanDefinition)
    participant ClassPathMapperScanner as ClassPathMapperScanner(扫描 Mapper)
    participant MapperFactoryBean as MapperFactoryBean(创建代理对象)
    
    启动类->>ImportRegistrar: 加载 @MapperScan,触发 Import
    ImportRegistrar->>ScannerConfigurer: 注册 MapperScannerConfigurer 的 BeanDefinition
    ScannerConfigurer->>ClassPathMapperScanner: 扫描指定包(如 com.example.mapper)
    ClassPathMapperScanner->>MapperFactoryBean: 为每个 Mapper 接口创建 BeanDefinition
    MapperFactoryBean->>Spring容器: 生成 Mapper 代理对象,注册到容器

MapperScan 接口的源码就是这样

@MapperScan 注解的触发入口如下,其中@Import(MapperScannerRegistrar.class) 引入逻辑处理类,替代手动配置 MapperScannerConfigurer是核心。

1
2
3
4
5
6
7
8
9
10
11
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan {
@AliasFor("basePackages")
String[] value() default {}; // 要扫描的 Mapper 包路径

@AliasFor("value")
String[] basePackages() default {};

而解析 @MapperScan 配置的类就是MapperScannerRegistrar,它的作用就是实现 ImportBeanDefinitionRegistrar 接口,动态注册 MapperScannerConfigurer 到 Spring 容器。

也就是将 @MapperScan 的配置(如扫描包路径)传递给 MapperScannerConfigurer,将 @MapperScan 注解的配置转换为 Spring 容器中的 Mapper Bean。

MapperScannerRegistrar源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ImportBeanDefinitionRegistrar:允许在 Spring 启动时动态注册 BeanDefinition(如 Mapper 接口的代理对象)
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
// 注入 Spring 的 ResourceLoader,用于加载类路径资源
private ResourceLoader resourceLoader;

public MapperScannerRegistrar() {
}

public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 1. 从启动类上获取 @MapperScan 注解的属性
AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(
importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));

if (mapperScanAttrs != null) {
// 2. 注册 MapperScannerConfigurer 的 BeanDefinition
this.registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}

其中,上述registerBeanDefinitions方法,做到了解析 @MapperScan 注解(如 @MapperScan(basePackages = "com.example.mapper")),并将配置传递给 MapperScannerConfigurer

配置 MapperScannerConfigurerBeanDefinition的源码如下

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, 
BeanDefinitionRegistry registry, String beanName) {
// 1. 创建 MapperScannerConfigurer 的 BeanDefinition
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);

// 2. 设置属性:处理占位符
builder.addPropertyValue("processPropertyPlaceHolders", annoAttrs.getBoolean("processPropertyPlaceHolders"));

// 3. 设置注解过滤:仅扫描带特定注解的接口
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
builder.addPropertyValue("annotationClass", annotationClass);
}

// 4. 设置标记接口:仅扫描实现特定接口的 Mapper
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
builder.addPropertyValue("markerInterface", markerInterface);
}

// 5. 设置 Bean 命名生成器
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
}

// 6. 设置 Mapper 工厂类(默认为 MapperFactoryBean)
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
}

// 7. 设置 SqlSessionTemplate 或 SqlSessionFactory 的引用
String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
if (StringUtils.hasText(sqlSessionTemplateRef)) {
builder.addPropertyValue("sqlSessionTemplateBeanName", sqlSessionTemplateRef);
}

String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
if (StringUtils.hasText(sqlSessionFactoryRef)) {
builder.addPropertyValue("sqlSessionFactoryBeanName", sqlSessionFactoryRef);
}

// 8. 计算扫描包路径(basePackages 或 basePackageClasses)
List<String> basePackages = new ArrayList<>();
basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages"))
.filter(StringUtils::hasText)
.collect(Collectors.toList()));

basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses"))
.map(ClassUtils::getPackageName)
.collect(Collectors.toList()));

// 如果未指定包路径,默认使用启动类所在的包
if (basePackages.isEmpty()) {
basePackages.add(getDefaultBasePackage(annoMeta));
}

// 9. 设置排除过滤器(excludeFilters)
AnnotationAttributes[] excludeFilterArray = annoAttrs.getAnnotationArray("excludeFilters");
if (excludeFilterArray.length > 0) {
// 处理正则、AspectJ 等特殊过滤器
List<TypeFilter> typeFilters = new ArrayList<>();
List<Map<String, String>> rawTypeFilters = new ArrayList<>();

// 解析过滤器配置...
builder.addPropertyValue("excludeFilters", typeFilters);
builder.addPropertyValue("rawExcludeFilters", rawTypeFilters);
}

// 10. 设置懒加载和作用域
String lazyInitialization = annoAttrs.getString("lazyInitialization");
if (StringUtils.hasText(lazyInitialization)) {
builder.addPropertyValue("lazyInitialization", lazyInitialization);
}

String defaultScope = annoAttrs.getString("defaultScope");
if (!"".equals(defaultScope)) {
builder.addPropertyValue("defaultScope", defaultScope);
}

// 11. 最终设置扫描包路径并注册 BeanDefinition
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
builder.setRole(2); // ROLE_SUPPORT:辅助角色的 Bean
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}

上述源码的核心功能就是将 @MapperScan 的各种配置(如 basePackagesannotationClassexcludeFilters)转换为 MapperScannerConfigurer 的属性,最终实现注册到 Spring 容器

下面的就是一些过滤器和重复注解处理,就不看了

总之,MapperScannerRegistrar 的核心流程就是

  1. 解析注解:从 @MapperScan 获取配置(如扫描包路径、过滤器)。
  2. 创建配置器:创建 MapperScannerConfigurer 的 BeanDefinition,并设置属性。
  3. 注册配置器:将 MapperScannerConfigurer 注册到 Spring 容器。
  4. 延迟扫描MapperScannerConfigurer 在 Spring 容器启动后,才会真正扫描 Mapper 接口并生成代理对象。

也即是说,MapperScan接口使用了@Import({MapperScannerRegistrar.class})导入了Mapper注册成了bean对象,其中,也做到了批量给容器中注册组件。解析指定的包路径里面的每⼀个类,为每⼀个Mapper接口类,创建Bean定义信息,注册到容器中。

还有一个类能扫描 Mapper 接口MapperScannerConfigurer,实现 BeanDefinitionRegistryPostProcessor 接口,在 Spring 启动早期扫描 Mapper 接口

其中比较核心的代码如下

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
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
this.processPropertyPlaceHolders();
}
// 1. 创建 ClassPathMapperScanner,设置扫描规则
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry, this.getEnvironment());
// 扫描上述`MybatisProperties`中的各种核心配置
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setExcludeFilters(this.excludeFilters = this.mergeExcludeFilters());
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(this.lazyInitialization)) {
scanner.setLazyInitialization(Boolean.parseBoolean(this.lazyInitialization));
}

if (StringUtils.hasText(this.defaultScope)) {
scanner.setDefaultScope(this.defaultScope);
}

scanner.registerFilters();
// 2. 扫描指定包下的 @Mapper 接口
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}

其中,ClassPathMapperScanner 是 MyBatis 扩展的扫描器,专门处理 Mapper 接口。

ClassPathMapperScanner就是生成 MapperBeanDefinition,它继承 ClassPathBeanDefinitionScanner重写 doScan 方法,为每个 Mapper 接口创建 BeanDefinition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 1. 扫描 basePackages 下的所有接口(@Mapper 或指定标记接口)
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
if (this.printWarnLogIfNotFoundMappers) {
LOGGER.warn(() -> {
return "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.";
});
}
} else {
this.processBeanDefinitions(beanDefinitions);
}

return beanDefinitions;
}
}

之后MapperFactoryBean的作用就是创建 Mapper 代理对象,继承 FactoryBean动态生成 Mapper 接口的代理对象

1
2
3
4
5
6
7
8
9
10
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
public T getObject() throws Exception {
// 从 SqlSession 获取 Mapper 代理对象(MyBatis 核心逻辑)
return this.getSqlSession().getMapper(this.mapperInterface);
}

public Class<T> getObjectType() {
return this.mapperInterface;
}

sqlSession.getMapper(mapperInterface) 会通过 JDK 动态代理,为 Mapper 接口生成代理对象,代理对象内部通过 SqlSession 执行 SQL。

所以说从 @MapperScan 到 Mapper 可用的完整流程总结如下:

  • 启动触发@MapperScan 通过 @Import 引入 MapperScannerRegistrar
  • 注册配置类MapperScannerRegistrar 向 Spring 容器注册 MapperScannerConfigurer
  • 扫描 MapperMapperScannerConfigurer 启动扫描,找到所有 Mapper 接口。
  • 替换 BeanClass:将 Mapper 接口的 BeanClass 替换为 MapperFactoryBean
  • 生成代理对象MapperFactoryBean 利用 MyBatis 的 SqlSession,为 Mapper 接口生成动态代理对象,并注册到 Spring 容器。

最终,你可以通过 @Autowired UserMapper userMapper 直接注入 Mapper 代理对象,执行 SQL 时由代理对象转发给 MyBatis 执行。

如何找自动配置类

如何分析哪个场景导⼊以后,开启了哪些自动配置类。

找: classpath:/META-INF/spring/org.springframework.boot.autoconfigure. AutoConfiguration.imports

文件中配置的所有值,就是要开启的⾃动配置类,但是每个 类可能有条件注解,基于条件注解判断哪个⾃动配置类⽣效了

快速定位⽣效的配置可以开启如下

1
2
3
#开启调试模式,详细打印开启了哪些⾃动配置
debug=true
# Positive(⽣效的⾃动配置) Negative(不⽣效的⾃动配置)

整合其他数据源

Druid 数据源

暂不⽀持 SpringBoot3

  • 导入 druid-starter
  • 写配置
  • 分析自动配置了哪些东⻄,怎么用
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
#数据源基本配置
spring.datasource.url=jdbc:mysql://192.168.200.100:3306/demo
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# 配置StatFilter监控
spring.datasource.druid.filter.stat.enabled=true
spring.datasource.druid.filter.stat.db-type=mysql
spring.datasource.druid.filter.stat.log-slow-sql=true
spring.datasource.druid.filter.stat.slow-sql-millis=2000
# 配置WallFilter防⽕墙
spring.datasource.druid.filter.wall.enabled=true
spring.datasource.druid.filter.wall.db-type=mysql
spring.datasource.druid.filter.wall.config.delete-allow=false
spring.datasource.druid.filter.wall.config.drop-table-allow=false
# 配置监控⻚,内置监控⻚⾯的⾸⻚是/druid/index.html
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=admin
spring.datasource.druid.stat-view-servlet.allow=*
# 其他Filter 配置不再演示
# ⽬前为以下Filter 提供了配置⽀持,请参考⽂档或者根据IDE提示(spring.datasource.druid.filter.*)进⾏配置。# StatFilter
# WallFilter
# ConfigFilter
# EncodingConvertFilter
# Slf4jLogFilter
# Log4jFilter
# Log4j2Filter
# CommonsLogFilter