0%

MyBatis 整合 Spring 完整指南:从 XML 配置到 Spring Boot 实践

MyBatis 与 Spring 的整合是企业级开发的主流方案,核心是将 MyBatis 的核心组件(SqlSessionFactoryMapper 代理)交给 Spring IOC 容器管理,同时复用 Spring 的事务管理、依赖注入等能力。 Spring Boot 注解式整合(主流方案)生产级数据源配置事务深化工程规范常见问题排查,覆盖传统 Spring 与 Spring Boot 两种场景。

版本选型与依赖优化

2024 年稳定版本组合(支持 Spring 5 特性,兼容 JDK 8+):

传统 Spring(XML 配置)依赖

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
<dependencies>
<!-- MyBatis 核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.16</version>
</dependency>
<!-- MyBatis-Spring 整合包(2.x 支持 Spring 5) -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.1.6</version>
</dependency>

<!-- Spring 核心依赖(5.x 稳定版) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.31</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.31</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.31</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.31</version>
<scope>test</scope>
</dependency>

<!-- 生产级数据源(HikariCP,Spring 5 默认) -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>
<!-- MySQL 驱动(8.x 支持 MySQL 8.0+) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>

<!-- 测试依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>

Spring Boot(注解式)依赖

Spring Boot 提供 mybatis-spring-boot-starter,自动整合 MyBatis 与 Spring,无需手动配置 SqlSessionFactoryMapperScanner,是当前主流方案:

阅读全文 »

MyBatis 缓存机制深度剖析与实践指南

MyBatis 的缓存机制是提升数据库访问性能的核心手段,通过减少重复查询来降低数据库压力。深入解析一级缓存、二级缓存的工作原理、适用场景、配置技巧及最佳实践,帮助你在实际项目中合理运用缓存提升系统性能。

缓存机制整体架构

MyBatis 缓存分为一级缓存二级缓存,两者在作用范围、生命周期和使用方式上有显著区别,但协同工作形成完整的缓存体系:

维度 一级缓存(Local Cache) 二级缓存(Global Cache)
作用范围 SqlSession 级别(单次会话) Mapper 接口(namespace)级别
默认状态 自动开启,无法关闭 需手动开启(全局 + 映射文件配置)
数据存储 内存 HashMap(无持久化) 可配置(内存 / 第三方缓存如 Redis)
共享性 仅当前 SqlSession 可见 同 SqlSessionFactory 下所有 SqlSession 共享
失效触发 SqlSession 关闭 / 提交 / 增删改操作 同 namespace 下增删改操作 / 缓存过期

MyBatis 缓存查询流程如下:

  1. 发起查询时,先检查二级缓存(若开启);
  2. 二级缓存未命中,检查一级缓存
  3. 一级缓存未命中,执行数据库查询;
  4. 查询结果依次写入一级缓存和二级缓存(若开启)。

一级缓存:SqlSession 级别的本地缓存

一级缓存是 MyBatis 内置的基础缓存,默认开启且无需额外配置,其核心作用是在单次数据库会话中复用查询结果。

工作原理

  • 存储结构:每个 SqlSession 内部维护一个 HashMap 作为缓存容器,键为 CacheKey,值为查询结果。
  • CacheKey 生成逻辑:由以下因素共同决定(任意一项不同则 CacheKey 不同):
    • MappedStatement 的 ID(即 namespace + methodName);
    • 分页参数(offset/limit);
    • SQL 语句本身;
    • 传入的参数值;
    • 数据库环境 ID(environment.id)。
  • 生命周期:与 SqlSession 一致,随 SqlSession 关闭而销毁。

一级缓存命中与失效示例

示例 1:一级缓存命中(同 SqlSession 内重复查询)
阅读全文 »

MyBatis 动态 SQL 详解与实战

动态 SQL 是 MyBatis 的核心特性之一,它允许我们根据不同条件动态构建 SQL 语句,有效解决了传统 SQL 拼接带来的语法错误(如多余的 AND、逗号等问题)。深入解析动态 SQL 标签的使用场景、最佳实践及高级技巧。

动态 SQL 核心标签详解

1. if 标签:条件判断

if 标签是动态 SQL 中最基础的条件判断标签,通过 test 属性(OGNL 表达式)决定是否拼接其内部的 SQL 片段。

基础用法
1
2
3
4
5
6
7
8
9
10
11
12
13
<select id="findUsers" resultType="User">
SELECT * FROM users
WHERE 1=1 <!-- 避免所有条件不满足时出现空 WHERE -->
<if test="username != null and username != ''">
AND username LIKE #{username}
</if>
<if test="age != null">
AND age > #{age}
</if>
<if test="status != null">
AND status = #{status}
</if>
</select>
注意事项
  • test 属性中使用 OGNL 表达式,判断字符串非空需同时检查 != null!= ''
  • 数值类型只需判断 != null(避免 != 0 误判合法的 0 值);
  • 多条件判断可用 &&(需转义为 &&)或 and 连接。

2. where 标签:智能处理条件前缀

where 标签解决了 if 标签可能导致的「多余 AND/OR」问题,它会自动去除条件片段开头的 ANDOR,并在有条件时添加 WHERE 关键字。

替代 1=1 的优雅方案
阅读全文 »

排序算法全解析:从原理到应用

排序是数据处理中的基础操作,根据数据是否全部在内存中,分为内排序(全在内存)和外排序(需内外存交换)。内排序是日常开发的重点,按策略可分为插入排序、交换排序、选择排序、归并排序等。本文详细解析各类内排序算法的原理、实现、性能及适用场景。

排序算法基础概念

内排序与外排序

  • 内排序:待排序数据全部在内存中,无需磁盘 IO,如插入排序、快速排序等。
  • 外排序:数据量超过内存容量,需分批加载到内存排序,再合并结果(如归并排序的外部实现)。

关键评价指标

  • 时间复杂度:算法执行时间随数据规模n的增长趋势(关注最坏、平均、最好情况)。
  • 空间复杂度:算法所需额外存储空间(如归并排序需 O (n) 辅助空间)。
  • 稳定性:排序后,相等元素的相对顺序是否保持不变(如[2, 2*]排序后仍为[2, 2*]则稳定)。

插入排序:逐步构建有序序列

插入排序的核心思想:将待排序元素逐个插入到已排序部分的正确位置,使已排序部分始终有序。

1. 直接插入排序

基本思想
  • 将数组分为 “已排序区” 和 “未排序区”,初始已排序区只有第一个元素。
  • 从第二个元素开始,依次将未排序区的元素插入到已排序区的合适位置(比它大的元素后移)。
示例步骤(以[67, 67, 14, 52, 29, 9, 90, 54, 87, 71]为例):
  1. 初始:[67](已排序),[67, 14, 52, 29, 9, 90, 54, 87, 71](未排序)。
  2. 插入 67:已排序区变为[67, 67]
  3. 插入 14:14 < 67,67 和 67 后移,已排序区变为[14, 67, 67]
  4. 依次插入剩余元素,最终得到有序数组。
代码实现
阅读全文 »

MyBatis 基础配置与执行流程全解析(从入门到实践)

MyBatis 作为 Java 生态中轻量级且灵活的持久层框架,其核心是通过配置文件解耦 SQL 与业务代码,同时简化 JDBC 冗余操作。配置细节优化、执行流程原理、工程化实践常见问题排查,帮助你彻底掌握 MyBatis 的基础使用与核心逻辑。

配置文件:从基础到优化

MyBatis 的配置体系分为「全局配置文件(mybatis-config.xml)」和「映射文件(XxxMapper.xml)」,两者分工明确 —— 全局配置负责框架级参数,映射文件负责业务 SQL 与实体映射。

全局配置文件(mybatis-config.xml)深度解读

(1)全局配置
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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 1. 引入外部属性文件(解耦数据库配置,支持多环境切换) -->
<properties resource="db.properties">
<!-- 可选:默认值(外部文件未定义时生效) -->
<property name="jdbc.username" value="default_root"/>
</properties>

<!-- 2. 全局设置(控制MyBatis核心行为,必配优化项) -->
<settings>
<!-- 开启驼峰命名自动映射(数据库字段user_name → Java属性userName) -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启SQL日志打印(开发环境调试用,生产环境可关闭或改用SLF4J) -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- 开启二级缓存(提升查询性能,需配合映射文件<cache>使用) -->
<setting name="cacheEnabled" value="true"/>
<!-- 关闭激进延迟加载(按需加载关联数据,避免冗余查询) -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>

<!-- 3. 类型别名(简化映射文件中类名书写,避免全类名冗余) -->
<typeAliases>
<!-- 方式1:单个类指定别名(alias可自定义,不写则默认类名首字母小写) -->
<typeAlias type="com.zhanghe.study.mybatis.model.User" alias="User"/>
<!-- 方式2:批量扫描包(推荐!包下所有类默认别名为类名首字母小写,如User→user) -->
<package name="com.zhanghe.study.mybatis.model"/>
</typeAliases>

<!-- 4. 数据库环境配置(支持多环境,default指定默认环境) -->
<environments default="development">
<!-- 开发环境 -->
<environment id="development">
<!-- 事务管理器:JDBC(依赖数据库原生事务)/ MANAGED(交给容器如Spring管理) -->
<transactionManager type="JDBC"/>
<!-- 数据源:POOLED(连接池,生产推荐)/ UNPOOLED(无池化)/ JNDI(容器数据源) -->
<dataSource type="POOLED">
<!-- 引用外部properties文件中的配置(${key}) -->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 连接池优化参数 -->
<property name="poolMaximumActiveConnections" value="20"/> <!-- 最大活跃连接数 -->
<property name="poolMaximumIdleConnections" value="5"/> <!-- 最大空闲连接数 -->
</dataSource>
</environment>
<!-- 生产环境(可单独配置,切换时修改default值) -->
<environment id="production">
<transactionManager type="MANAGED"/>
<dataSource type="POOLED">
<property name="driver" value="${prod.jdbc.driver}"/>
<property name="url" value="${prod.jdbc.url}"/>
<property name="username" value="${prod.jdbc.username}"/>
<property name="password" value="${prod.jdbc.password}"/>
</dataSource>
</environment>
</environments>

<!-- 5. 映射文件扫描(告诉MyBatis去哪里找SQL映射) -->
<mappers>
<!-- 方式1:单个映射文件(resource路径基于classpath,如src/main/resources/mapper/UserMapper.xml) -->
<mapper resource="mapper/UserMapper.xml"/>
<!-- 方式2:批量扫描Mapper接口(推荐!需满足「接口与映射文件同包同名」) -->
<!-- <package name="com.zhanghe.study.mybatis.mapper"/> -->
</mappers>
</configuration>
(2)外部属性文件(db.properties

将数据库配置单独抽取到 src/main/resources/db.properties,便于维护和环境切换:

1
2
3
4
5
6
7
8
9
10
11
# 开发环境
jdbc.driver=com.mysql.cj.jdbc.Driver # MySQL 8.0+需用cj驱动,5.x用com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
jdbc.username=root
jdbc.password=123456

# 生产环境
prod.jdbc.driver=com.mysql.cj.jdbc.Driver
prod.jdbc.url=jdbc:mysql://192.168.1.100:3306/prod_db?useSSL=true&serverTimezone=UTC
prod.jdbc.username=prod_user
prod.jdbc.password=prod_123456

映射文件(UserMapper.xml

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
<?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">
<!-- namespace必须与Mapper接口全类名一致(绑定接口) -->
<mapper namespace="com.zhanghe.study.mybatis.mapper.UserMapper">
<!-- 可选:开启当前Mapper的二级缓存(需全局settings中cacheEnabled=true) -->
<cache eviction="LRU" flushInterval="60000" size="1024" readOnly="true"/>

<!-- 结果映射(ResultMap):解决「表字段与实体属性名不一致」或「复杂关联查询」 -->
<!-- 若开启驼峰映射(mapUnderscoreToCamelCase=true),简单场景可省略ResultMap -->
<resultMap id="UserResultMap" type="User"> <!-- type用别名(全局配置中定义) -->
<id column="user_id" property="userId"/> <!-- 主键映射 -->
<result column="user_name" property="userName"/> <!-- 普通字段映射 -->
<result column="user_age" property="userAge"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
</resultMap>

<!-- 1. 基础查询:根据ID查用户(使用ResultMap) -->
<select id="selectUser" parameterType="Integer" resultMap="UserResultMap">
SELECT user_id, user_name, user_age, create_time
FROM users
WHERE user_id = #{id} <!-- #{id}:预编译占位符,防SQL注入 -->
</select>

<!-- 2. 多参数查询:按姓名和年龄查用户(用@Param注解) -->
<select id="selectUserByNameAndAge" resultMap="UserResultMap">
SELECT * FROM users
WHERE user_name = #{name} AND user_age = #{age}
<!-- 接口方法需加@Param:User selectUserByNameAndAge(@Param("name") String name, @Param("age") Integer age) -->
</select>

<!-- 3. 插入:新增用户并返回自增主键 -->
<insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="userId">
<!-- useGeneratedKeys=true:开启自增主键返回;keyProperty:主键对应实体属性名 -->
INSERT INTO users (user_name, user_age, create_time)
VALUES (#{userName}, #{userAge}, #{createTime})
</insert>
</mapper>
对应的 Mapper 接口(UserMapper.java

接口方法需与映射文件的 id、参数类型、返回值类型完全匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.zhanghe.study.mybatis.mapper;

import com.zhanghe.study.mybatis.model.User;
import org.apache.ibatis.annotations.Param;

public interface UserMapper {
// 根据ID查用户(对应映射文件selectUser)
User selectUser(Integer userId);

// 多参数查询(需@Param注解绑定参数名)
User selectUserByNameAndAge(@Param("name") String userName, @Param("age") Integer userAge);

// 新增用户(返回自增主键注入到User的userId属性)
int insertUser(User user);
}

执行流程:从配置加载到 SQL 执行

MyBatis 的执行流程可拆解为「配置加载→核心对象创建→SQL 执行→资源释放」四步,每一步都有明确的职责和生命周期管理要求。

核心流程拆解

(1)步骤 1:加载配置,创建 SqlSessionFactory

SqlSessionFactory 是 MyBatis 的 “工厂”,负责创建 SqlSession,其生命周期是全局唯一(应用启动时创建一次,避免重复创建导致连接泄漏)。

1
2
3
4
5
6
7
8
9
10
11
public static SqlSessionFactory createFactory() {
try {
// 1. 读取全局配置文件(Resources是MyBatis提供的工具类,简化classpath资源加载)
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 通过SqlSessionFactoryBuilder构建SqlSessionFactory
// (SqlSessionFactoryBuilder用完即丢,无需长期持有)
return new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
throw new RuntimeException("MyBatis配置文件加载失败!", e);
}
}

关键原理
SqlSessionFactoryBuilder 会解析 mybatis-config.xml 和所有 Mapper.xml,将配置信息封装到 Configuration 对象中,最终基于 Configuration 创建 SqlSessionFactory(默认实现是 DefaultSqlSessionFactory)。

(2)步骤 2:创建 SqlSession

SqlSession 相当于 JDBC 的 Connection,代表一次数据库会话,生命周期是单次业务请求(线程不安全,需及时关闭)。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 方式1:自动提交事务(openSession(true))
SqlSession session = sqlSessionFactory.openSession(true);

// 方式2:手动提交事务(默认,适合多SQL操作需事务一致性场景)
SqlSession session = sqlSessionFactory.openSession();
try {
// 执行SQL
session.commit(); // 手动提交
} catch (Exception e) {
session.rollback(); // 异常回滚
} finally {
session.close(); // 关闭会话
}

核心注意事项

  • SqlSession 线程不安全,不可跨线程共享;
  • 必须在 finally 块中关闭 SqlSession,避免数据库连接泄漏;
  • 手动提交模式下,未调用 commit() 会导致事务未提交,数据不生效。
(3)步骤 3:执行 SQL(新旧版本对比)

MyBatis 提供两种执行 SQL 的方式,新版本的「Mapper 接口代理」是主流(类型安全、可读性强)。

执行方式 代码示例 优缺点对比
老版本(SqlSession 直接调用) User user = session.selectOne("com.zhanghe.study.mybatis.mapper.UserMapper.selectUser", 2); 优点:无需定义接口; 缺点:字符串硬编码(易写错)、无类型校验、可读性差。
新版本(Mapper 接口代理) UserMapper mapper = session.getMapper(UserMapper.class); User user = mapper.selectUser(2); 优点:类型安全(编译期校验)、代码简洁、可维护性强; 缺点:需定义接口。

原理补充
MyBatis 会为 Mapper 接口动态生成代理对象(基于 JDK 动态代理),调用接口方法时,代理对象会解析 namespace + methodName 找到对应的 SQL,执行后返回结果。

(4)步骤 4:资源释放

无论 SQL 执行成功或失败,都需关闭 SqlSession,释放数据库连接(归还到连接池):

1
2
3
4
5
6
7
8
9
10
11
SqlSession session = null;
try {
session = sqlSessionFactory.openSession(true);
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUser(2);
System.out.println(user);
} finally {
if (session != null) {
session.close(); // 关闭SqlSession,释放连接
}
}

工程化优化:工具类封装

为避免重复代码,推荐封装 MyBatisUtil 工具类,统一管理 SqlSessionFactorySqlSession

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
package com.zhanghe.study.mybatis.util;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class MyBatisUtil {
// 静态变量:确保SqlSessionFactory全局唯一
private static SqlSessionFactory sqlSessionFactory;

// 静态代码块:应用启动时初始化SqlSessionFactory
static {
try {
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
throw new ExceptionInInitializerError("MyBatis初始化失败!", e);
}
}

// 获取SqlSession(自动提交事务,简化单SQL操作)
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession(true);
}

// 关闭SqlSession
public static void closeSqlSession(SqlSession session) {
if (session != null) {
session.close();
}
}
}

工具类使用示例

1
2
3
4
5
6
7
8
9
10
// 业务代码中使用工具类
SqlSession session = null;
try {
session = MyBatisUtil.getSqlSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUser(2);
System.out.println(user);
} finally {
MyBatisUtil.closeSqlSession(session);
}

常见问题与解决方案

1. 映射文件找不到(Invalid bound statement (not found)

  • 原因 1mybatis-config.xml<mappers> 配置路径错误(如 resource 路径不是 classpath 下的路径);
  • 原因 2:Mapper 接口与映射文件不同包同名(如接口在 com.mapper,映射文件需在 resources/com/mapper 下且名为 UserMapper.xml);
  • 解决方案:使用 <package name="com.zhanghe.study.mybatis.mapper"/> 批量扫描 Mapper 接口,确保接口与映射文件同包同名。

2. 驼峰命名不生效(数据库字段 user_name → 实体属性 userNamenull

  • 原因:未开启全局设置 mapUnderscoreToCamelCase

  • 解决方案:在mybatis-config.xml的<settings>中添加:

    1
    <setting name="mapUnderscoreToCamelCase" value="true"/>

3. SQL 注入风险(使用 ${} 占位符)

  • 原因${} 是字符串直接拼接(如 ${id} → 替换为参数值,无预编译),#{} 是预编译占位符(推荐);
  • 解决方案:除动态表名 / 排序字段(如 ORDER BY ${field})外,全部使用 #{}

4. SqlSession 未关闭导致连接泄漏

  • 原因:未在 finally 块中关闭 SqlSession,异常时资源未释放;
  • 解决方案:使用工具类的 closeSqlSession 方法,在 finally 中强制关闭。

总结

MyBatis 的基础使用核心是 “配置 + 接口 + 映射”:

  1. 配置文件:全局配置管理数据库和框架参数,映射文件定义 SQL 和实体映射;
  2. 执行流程SqlSessionFactory(全局唯一)→ SqlSession(单次会话)→ Mapper 代理(执行 SQL);
  3. 最佳实践:封装工具类管理核心对象、开启驼峰映射简化配置、使用接口代理避免硬编码