MyBatis核心详解
本章导读
MyBatis 是 Java 持久层的主流框架之一,以灵活的 SQL 控制和强大的映射能力著称。本章深入剖析 MyBatis 的核心架构,从 SqlSession 生命周期到 Mapper 代理机制,从缓存原理到动态 SQL,帮助你理解框架设计思想,掌握高性能数据访问的实现技巧。
学习目标:
- 目标1:理解 MyBatis 核心组件的生命周期和职责划分
- 目标2:掌握 Mapper 接口绑定、动态 SQL 和结果映射的使用方法
- 目标3:理解一级缓存和二级缓存机制,能够正确使用和优化缓存
前置知识:JDBC 基础、SQL 语法、XML 基础
阅读时长:约 60 分钟
一、知识概述
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects)为数据库中的记录。
本文将深入讲解 MyBatis 的核心原理,包括 SqlSession 的生命周期、Mapper 代理机制、缓存机制等,帮助你理解 MyBatis 的工作原理,更好地使用和优化 MyBatis。
二、核心组件详解
2.1 MyBatis 整体架构
┌─────────────────────────────────────────────────────────────┐
│ 应用程序层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Mapper 接口 │ │ Service 层 │ │ Controller │ │
│ └──────┬──────┘ └──────┬──────┘ └─────────────┘ │
└─────────┼────────────────┼──────────────────────────────────┘
│ │
┌─────────┼────────────────┼──────────────────────────────────┐
│ ▼ ▼ MyBatis 框架层 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ SqlSession │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Configuration │ │ Executor │ │ │
│ │ └──────────────┘ └──────────────┘ │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ StatementHan │ │ ParameterHan │ │ │
│ │ │ dler │ │ dler │ │ │
│ │ └──────────────┘ └──────────────┘ │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ ResultSetHan │ │ TypeHandler │ │ │
│ │ │ dler │ │ │ │ │
│ │ └──────────────┘ └──────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 数据库层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ MySQL │ │ Oracle │ │ PostgreSQL │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
2.2 核心组件介绍
/**
* MyBatis 核心组件层次结构
*
* SqlSessionFactoryBuilder → SqlSessionFactory → SqlSession → Mapper
*
* 每个组件的职责:
* 1. SqlSessionFactoryBuilder: 解析配置,构建 SqlSessionFactory
* 2. SqlSessionFactory: 创建 SqlSession 的工厂(单例)
* 3. SqlSession: 执行 SQL 的会话(非线程安全,每次请求创建)
* 4. Mapper: 接口代理,执行具体 SQL
*/
public class MyBatisCoreComponents {
public static void main(String[] args) {
// 1. SqlSessionFactoryBuilder - 构建 SqlSessionFactory
// 生命周期:方法局部,用完即弃
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 2. SqlSessionFactory - 工厂单例
// 生命周期:应用级别,整个应用运行期间存在
// 最佳实践:通过单例模式或 Spring 容器管理
SqlSessionFactory sqlSessionFactory = builder.build(
Resources.getResourceAsStream("mybatis-config.xml")
);
// 3. SqlSession - 数据库会话
// 生命周期:请求/方法级别,非线程安全
// 最佳实践:每次数据库操作创建,用完关闭
try (SqlSession session = sqlSessionFactory.openSession()) {
// 4. Mapper 接口 - 执行 SQL
// 生命周期:随 SqlSession,每次获取新实例
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(1L);
System.out.println(user);
}
}
}
2.3 配置详解
<!-- mybatis-config.xml - MyBatis 核心配置文件 -->
<?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="username" value="root"/>
<property name="password" value="123456"/>
</properties>
<!-- 2. 设置 - 全局配置参数 -->
<settings>
<!-- 开启驼峰命名自动映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- 延迟加载全局开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 设置超时时间 -->
<setting name="defaultStatementTimeout" value="30"/>
<!-- 日志实现 -->
<setting name="logImpl" value="SLF4J"/>
</settings>
<!-- 3. 类型别名 - 为 Java 类型设置缩写 -->
<typeAliases>
<!-- 单个类别名 -->
<typeAlias type="com.example.entity.User" alias="User"/>
<!-- 包扫描别名(默认首字母小写) -->
<package name="com.example.entity"/>
</typeAliases>
<!-- 4. 类型处理器 - 自定义类型转换 -->
<typeHandlers>
<typeHandler javaType="java.util.Date"
handler="com.example.handler.DateTypeHandler"/>
</typeHandlers>
<!-- 5. 插件 - 拦截器 -->
<plugins>
<plugin interceptor="com.example.plugin.MyPlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
<!-- 6. 环境配置 -->
<environments default="development">
<environment id="development">
<!-- 事务管理器 -->
<transactionManager type="JDBC"/>
<!-- 数据源 -->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<!-- 连接池配置 -->
<property name="poolMaximumActiveConnections" value="20"/>
<property name="poolMaximumIdleConnections" value="10"/>
<property name="poolMaximumCheckoutTime" value="20000"/>
</dataSource>
</environment>
</environments>
<!-- 7. 数据库厂商标识 -->
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
</databaseIdProvider>
<!-- 8. 映射器配置 -->
<mappers>
<!-- 使用 XML 配置 -->
<mapper resource="mapper/UserMapper.xml"/>
<!-- 使用接口配置 -->
<mapper class="com.example.mapper.UserMapper"/>
<!-- 包扫描 -->
<package name="com.example.mapper"/>
</mappers>
</configuration>
三、SqlSession 源码解析
3.1 SqlSession 创建过程
/**
* SqlSession 创建流程源码分析
*/
public class SqlSessionCreationAnalysis {
/**
* SqlSessionFactory 接口定义
*/
public interface SqlSessionFactory {
// 创建默认 SqlSession(非自动提交)
SqlSession openSession();
// 创建指定自动提交的 SqlSession
SqlSession openSession(boolean autoCommit);
// 使用指定 Connection 创建 SqlSession
SqlSession openSession(Connection connection);
// 指定事务隔离级别
SqlSession openSession(TransactionIsolationLevel level);
// 指定执行器类型
SqlSession openSession(ExecutorType execType);
}
/**
* DefaultSqlSessionFactory.openSession() 实现
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
@Override
public SqlSession openSession() {
return openSessionFromDataSource(
configuration.getDefaultExecutorType(), // 执行器类型
configuration.getDefaultTransactionIsolationLevel(), // 事务隔离级别
false // 自动提交
);
}
private SqlSession openSessionFromDataSource(
ExecutorType execType,
TransactionIsolationLevel level,
boolean autoCommit) {
Transaction tx = null;
try {
// 1. 从 Configuration 获取 Environment(包含数据源和事务配置)
final Environment environment = configuration.getEnvironment();
// 2. 创建事务
TransactionFactory transactionFactory =
getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(
environment.getDataSource(), level, autoCommit);
// 3. 创建执行器(关键!)
Executor executor = configuration.newExecutor(tx, execType);
// 4. 创建 DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx);
throw ExceptionFactory.wrapException(
"Error opening session. Cause: " + e, e);
}
}
}
}
3.2 SqlSession 执行流程
/**
* SqlSession 执行 SQL 的完整流程
*/
public class SqlSessionExecutionFlow {
public static void main(String[] args) {
SqlSessionFactory factory = SqlSessionFactoryBuilder.build(...);
try (SqlSession session = factory.openSession()) {
// 查询操作
User user = session.selectOne(
"com.example.mapper.UserMapper.selectById", 1L);
// 插入操作
int rows = session.insert(
"com.example.mapper.UserMapper.insert", user);
// 更新操作
int rows = session.update(
"com.example.mapper.UserMapper.update", user);
// 删除操作
int rows = session.delete(
"com.example.mapper.UserMapper.deleteById", 1L);
// 提交事务(如果非自动提交)
session.commit();
}
}
/**
* DefaultSqlSession.selectOne 源码
*/
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
@Override
public <T> T selectOne(String statement, Object parameter) {
// 调用 selectList,取第一个结果
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException(
"Expected one result but found: " + list.size());
}
return null;
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter,
RowBounds rowBounds) {
try {
// 1. 从 Configuration 获取 MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
// 2. 委托给 Executor 执行
return executor.query(ms, wrapCollection(parameter),
rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException(
"Error querying database. Cause: " + e, e);
}
}
}
}
四、Mapper 代理机制
4.1 Mapper 接口绑定
/**
* Mapper 接口代理机制详解
*
* MyBatis 通过 JDK 动态代理为 Mapper 接口创建代理对象
* 代理对象拦截方法调用,将其转换为 SQL 执行
*/
public class MapperProxyMechanism {
/**
* Mapper 接口定义
*/
public interface UserMapper {
User selectById(Long id);
List<User> selectAll();
int insert(User user);
int update(User user);
int deleteById(Long id);
}
/**
* 对应的 XML 映射文件
*/
/*
<?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="com.example.mapper.UserMapper">
<select id="selectById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
<select id="selectAll" resultType="User">
SELECT * FROM user
</select>
<insert id="insert" parameterType="User">
INSERT INTO user(name, email, age)
VALUES(#{name}, #{email}, #{age})
</insert>
<update id="update" parameterType="User">
UPDATE user SET name=#{name}, email=#{email}, age=#{age}
WHERE id=#{id}
</update>
<delete id="deleteById">
DELETE FROM user WHERE id = #{id}
</delete>
</mapper>
*/
/**
* MapperProxy 核心源码分析
*/
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface,
Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 1. 如果是 Object 类的方法,直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 2. 默认方法处理(Java 8+)
if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
// 3. 获取 MapperMethod(缓存)
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 4. 执行 SQL
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method,
k -> new MapperMethod(mapperInterface, method,
sqlSession.getConfiguration()));
}
}
/**
* MapperMethod 执行逻辑
*/
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException(
"Unknown execution method for: " + command.getName());
}
return result;
}
}
}
4.2 Mapper 注解方式
/**
* 使用注解定义 Mapper
* 不需要 XML 文件,直接在接口上使用注解
*/
public interface UserMapperAnnotations {
/**
* @Select 注解 - 查询
*/
@Select("SELECT * FROM user WHERE id = #{id}")
@Results(id = "userResult", value = {
@Result(property = "id", column = "id", id = true),
@Result(property = "userName", column = "name"),
@Result(property = "email", column = "email"),
@Result(property = "createTime", column = "create_time")
})
User selectById(Long id);
/**
* @Insert 注解 - 插入
* @Options 配置主键生成策略
*/
@Insert("INSERT INTO user(name, email, age, create_time) " +
"VALUES(#{userName}, #{email}, #{age}, NOW())")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
/**
* @Update 注解 - 更新
*/
@Update("UPDATE user SET name=#{userName}, email=#{email}, " +
"age=#{age} WHERE id=#{id}")
int update(User user);
/**
* @Delete 注解 - 删除
*/
@Delete("DELETE FROM user WHERE id = #{id}")
int deleteById(Long id);
/**
* 动态 SQL - 使用 @SelectProvider
*/
@SelectProvider(type = UserSqlProvider.class, method = "selectByCondition")
List<User> selectByCondition(UserQuery query);
/**
* 一对多关联查询
*/
@Select("SELECT * FROM user WHERE id = #{id}")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "userName", column = "name"),
@Result(property = "orders", column = "id",
many = @Many(select = "com.example.mapper.OrderMapper.selectByUserId"))
})
User selectWithOrders(Long id);
/**
* 一对一关联查询
*/
@Select("SELECT * FROM user WHERE id = #{id}")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "userName", column = "name"),
@Result(property = "department", column = "dept_id",
one = @One(select = "com.example.mapper.DeptMapper.selectById"))
})
User selectWithDept(Long id);
}
/**
* SQL 提供者类
*/
public class UserSqlProvider {
public String selectByCondition(UserQuery query) {
return new SQL() {{
SELECT("*");
FROM("user");
if (query.getName() != null) {
WHERE("name LIKE CONCAT('%', #{name}, '%')");
}
if (query.getEmail() != null) {
WHERE("email = #{email}");
}
if (query.getMinAge() != null) {
WHERE("age >= #{minAge}");
}
if (query.getMaxAge() != null) {
WHERE("age <= #{maxAge}");
}
ORDER_BY("create_time DESC");
}}.toString();
}
}
五、缓存机制详解
5.1 一级缓存
/**
* MyBatis 一级缓存(本地缓存)
*
* 特点:
* 1. 默认开启,无法关闭
* 2. 作用域:SqlSession 级别
* 3. 生命周期:与 SqlSession 相同
* 4. 缓存 key:StatementId + 参数 + 分页参数 + SQL
*/
public class MyBatisLevelOneCache {
public static void main(String[] args) {
SqlSessionFactory factory = SqlSessionFactoryBuilder.build(...);
try (SqlSession session = factory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
// 第一次查询 - 从数据库获取
User user1 = mapper.selectById(1L);
System.out.println("第一次查询: " + user1);
// 第二次查询 - 从一级缓存获取
User user2 = mapper.selectById(1L);
System.out.println("第二次查询: " + user2);
// 验证是否同一个对象
System.out.println("是否同一对象: " + (user1 == user2)); // true
// 执行增删改操作会清空一级缓存
// mapper.update(user1);
// 手动清空缓存
// session.clearCache();
}
}
/**
* BaseExecutor 中的缓存实现
*/
public abstract class BaseExecutor implements Executor {
// 一级缓存 - PerpetualCache 是基于 HashMap 的简单实现
protected PerpetualCache localCache;
@Override
public <E> List<E> query(MappedStatement ms, Object parameter,
RowBounds rowBounds,
ResultHandler resultHandler) throws SQLException {
// 1. 获取绑定 SQL
BoundSql boundSql = ms.getBoundSql(parameter);
// 2. 创建缓存 key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 3. 执行查询(检查缓存)
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql) throws SQLException {
// 1. 检查一级缓存
List<E> list = localCache.getObject(key);
if (list != null) {
// 命中缓存
return list;
} else {
// 未命中,查询数据库
list = queryFromDatabase(ms, parameter, rowBounds,
resultHandler, key, boundSql);
}
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter,
RowBounds rowBounds,
ResultHandler resultHandler,
CacheKey key, BoundSql boundSql) {
List<E> list;
// 1. 占位符,防止递归查询
localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
try {
// 2. 执行查询
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 3. 移除占位符
localCache.removeObject(key);
}
// 4. 存入缓存
localCache.putObject(key, list);
return list;
}
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
// 增删改操作清空缓存
clearLocalCache();
return doUpdate(ms, parameter);
}
}
/**
* 缓存 Key 的构成
*/
public class CacheKey {
// 缓存 Key 的组成部分
private static final int DEFAULT_MULTIPLIER = 37;
private static final int DEFAULT_HASHCODE = 17;
private int multiplier;
private int hashcode;
private long checksum;
private int count;
private List<Object> updateList;
/**
* CacheKey = hashCode(StatementId + 参数 + RowBounds + SQL)
*/
public void update(Object object) {
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}
}
}
5.2 二级缓存
/**
* MyBatis 二级缓存
*
* 特点:
* 1. 默认关闭,需要配置开启
* 2. 作用域:Mapper 命名空间级别(跨 SqlSession)
* 3. 生命周期:与 SqlSessionFactory 相同
* 4. 需要实体类实现 Serializable 接口
*/
public class MyBatisLevelTwoCache {
/**
* 1. 配置文件开启二级缓存
*/
// mybatis-config.xml
// <settings>
// <setting name="cacheEnabled" value="true"/>
// </settings>
/**
* 2. Mapper XML 配置缓存
*/
/*
<mapper namespace="com.example.mapper.UserMapper">
<!-- 开启当前 Mapper 的二级缓存 -->
<cache/>
<!-- 详细配置 -->
<cache
eviction="LRU" <!-- 回收策略 -->
flushInterval="60000" <!-- 刷新间隔(毫秒) -->
size="1024" <!-- 缓存大小 -->
readOnly="true"/> <!-- 是否只读 -->
<!-- 使用第三方缓存 -->
<cache type="org.mybatis.caches.redis.RedisCache"/>
<!-- 针对特定语句配置 -->
<select id="selectById" resultType="User" useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
<!-- 刷新缓存 -->
<update id="update" flushCache="true">
UPDATE user SET name=#{name} WHERE id=#{id}
</update>
</mapper>
*/
/**
* 二级缓存使用示例
*/
public static void main(String[] args) {
SqlSessionFactory factory = SqlSessionFactoryBuilder.build(...);
// 第一个 SqlSession
try (SqlSession session1 = factory.openSession()) {
UserMapper mapper1 = session1.getMapper(UserMapper.class);
User user1 = mapper1.selectById(1L); // 查询数据库
session1.commit(); // 提交后才会写入二级缓存
}
// 第二个 SqlSession
try (SqlSession session2 = factory.openSession()) {
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user2 = mapper2.selectById(1L); // 从二级缓存获取
System.out.println("从二级缓存获取: " + user2);
}
}
/**
* 缓存回收策略
*/
public enum CacheEvictionPolicy {
/**
* LRU - 最近最少使用(默认)
* 移除最长时间不被使用的对象
*/
LRU,
/**
* FIFO - 先进先出
* 按对象进入缓存的顺序移除
*/
FIFO,
/**
* SOFT - 软引用
* 基于垃圾回收器状态和软引用规则移除
*/
SOFT,
/**
* WEAK - 弱引用
* 更积极地基于垃圾收集器状态和弱引用规则移除
*/
WEAK
}
/**
* 实体类需要实现序列化
*/
@Data
public static class User implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
private String email;
private Integer age;
private Date createTime;
}
}
5.3 缓存执行顺序
/**
* 一级缓存与二级缓存的执行顺序
*
* 查询顺序:二级缓存 → 一级缓存 → 数据库
* 更新时:清空一级缓存 → 清空二级缓存
*/
public class CacheExecutionOrder {
/**
* CachingExecutor 装饰器模式
* 在 BaseExecutor 外层包装二级缓存逻辑
*/
public class CachingExecutor implements Executor {
private final Executor delegate; // 实际的 BaseExecutor
private final TransactionalCacheManager tcm;
@Override
public <E> List<E> query(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql) throws SQLException {
// 1. 获取二级缓存
Cache cache = ms.getCache();
if (cache != null) {
// 2. 检查二级缓存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
// 3. 尝试从二级缓存获取
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 4. 未命中,调用 delegate(一级缓存逻辑)
list = delegate.<E>query(ms, parameter, rowBounds,
resultHandler, key, boundSql);
// 5. 存入二级缓存
tcm.putObject(cache, key, list);
}
return list;
}
}
// 无二级缓存,直接走一级缓存
return delegate.<E>query(ms, parameter, rowBounds,
resultHandler, key, boundSql);
}
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
// 更新时清空二级缓存
flushCacheIfRequired(ms);
return delegate.update(ms, parameter); // 然后清空一级缓存
}
}
/**
* 缓存流程图
*/
/*
┌─────────────────────────────────────────────────────────────┐
│ 查询请求 │
└─────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ CachingExecutor (二级缓存) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 检查二级缓存 │ │
│ │ 命中 → 直接返回 │ │
│ │ 未命中 → 继续 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
│ 未命中
▼
┌─────────────────────────────────────────────────────────────┐
│ BaseExecutor (一级缓存) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 检查一级缓存 │ │
│ │ 命中 → 直接返回 │ │
│ │ 未命中 → 查询数据库 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
│ 未命中
▼
┌─────────────────────────────────────────────────────────────┐
│ 数据库 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 执行 SQL,返回结果 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
*/
}
六、动态 SQL
6.1 动态 SQL 标签
<!-- UserMapper.xml - 动态 SQL 示例 -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 1. if 标签 - 条件判断 -->
<select id="selectByCondition" resultType="User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="email != null">
AND email = #{email}
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
</where>
ORDER BY create_time DESC
</select>
<!-- 2. choose-when-otherwise 标签 - 多选一 -->
<select id="selectByOneCondition" resultType="User">
SELECT * FROM user
<where>
<choose>
<when test="id != null">
id = #{id}
</when>
<when test="name != null">
name = #{name}
</when>
<when test="email != null">
email = #{email}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</where>
</select>
<!-- 3. set 标签 - 动态更新 -->
<update id="updateSelective">
UPDATE user
<set>
<if test="name != null">
name = #{name},
</if>
<if test="email != null">
email = #{email},
</if>
<if test="age != null">
age = #{age},
</if>
update_time = NOW()
</set>
WHERE id = #{id}
</update>
<!-- 4. foreach 标签 - 遍历集合 -->
<select id="selectByIds" resultType="User">
SELECT * FROM user
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- foreach 批量插入 -->
<insert id="batchInsert">
INSERT INTO user(name, email, age, create_time)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.email}, #{user.age}, NOW())
</foreach>
</insert>
<!-- 5. trim 标签 - 自定义截取 -->
<select id="selectByTrim" resultType="User">
SELECT * FROM user
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="name != null">
AND name = #{name}
</if>
<if test="email != null">
AND email = #{email}
</if>
</trim>
</select>
<!-- 6. bind 标签 - 变量绑定 -->
<select id="selectByBind" resultType="User">
<bind name="namePattern" value="'%' + name + '%'" />
SELECT * FROM user
WHERE name LIKE #{namePattern}
</select>
<!-- 7. sql 标签 - SQL 片段复用 -->
<sql id="userColumns">
id, name, email, age, create_time
</sql>
<select id="selectAll" resultType="User">
SELECT <include refid="userColumns"/>
FROM user
</select>
<!-- 8. include + property -->
<sql id="userColumnsWithAlias">
${alias}.id, ${alias}.name, ${alias}.email
</sql>
<select id="selectWithAlias" resultType="User">
SELECT <include refid="userColumnsWithAlias">
<property name="alias" value="u"/>
</include>
FROM user u
</select>
</mapper>
6.2 动态 SQL 原理
/**
* 动态 SQL 解析原理
*
* MyBatis 使用 OGNL 表达式解析动态 SQL
* XMLLanguageDriver 负责解析 SQL 节点
*/
public class DynamicSQLPrinciple {
/**
* SqlSource 接口
*/
public interface SqlSource {
BoundSql getBoundSql(Object parameterObject);
}
/**
* DynamicSqlSource - 动态 SQL
*/
public class DynamicSqlSource implements SqlSource {
private final Configuration configuration;
private final SqlNode rootSqlNode;
@Override
public BoundSql getBoundSql(Object parameterObject) {
// 1. 创建动态上下文
DynamicContext context = new DynamicContext(
configuration, parameterObject);
// 2. 应用 SqlNode(解析动态标签)
rootSqlNode.apply(context);
// 3. 创建 SqlSourceBuilder
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ?
Object.class : parameterObject.getClass();
// 4. 解析 #{} 占位符
SqlSource sqlSource = sqlSourceParser.parse(
context.getSql(), parameterType, context.getBindings());
// 5. 返回 BoundSql
return sqlSource.getBoundSql(parameterObject);
}
}
/**
* SqlNode 接口 - 各种动态标签的实现
*/
public interface SqlNode {
boolean apply(DynamicContext context);
}
/**
* IfSqlNode - if 标签实现
*/
public class IfSqlNode implements SqlNode {
private final ExpressionEvaluator evaluator;
private final String test;
private final SqlNode contents;
@Override
public boolean apply(DynamicContext context) {
// 使用 OGNL 表达式求值
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
}
/**
* WhereSqlNode - where 标签实现
*/
public class WhereSqlNode implements SqlNode {
private final SqlNode contents;
@Override
public boolean apply(DynamicContext context) {
// 1. 处理子节点
FilteredDynamicContext filteredContext =
new FilteredDynamicContext(context);
contents.apply(filteredContext);
// 2. 处理 SQL 字符串
String sql = filteredContext.getSql();
if (sql != null && !sql.isEmpty()) {
// 去除前缀的 AND/OR
sql = sql.trim();
if (sql.toUpperCase().startsWith("AND")) {
sql = sql.substring(3).trim();
} else if (sql.toUpperCase().startsWith("OR")) {
sql = sql.substring(2).trim();
}
context.appendSql("WHERE " + sql);
}
return true;
}
}
/**
* ForeachSqlNode - foreach 标签实现
*/
public class ForeachSqlNode implements SqlNode {
private final ExpressionEvaluator evaluator;
private final String collectionExpression;
private final String item;
private final String index;
private final String open;
private final String close;
private final String separator;
private final SqlNode contents;
@Override
public boolean apply(DynamicContext context) {
// 1. 获取集合
Iterable<?> iterable = evaluator.evaluateIterable(
collectionExpression, context.getBindings());
if (!iterable.iterator().hasNext()) {
return true;
}
// 2. 添加前缀
if (open != null) {
context.appendSql(open);
}
// 3. 遍历集合
boolean first = true;
for (Object o : iterable) {
// 添加分隔符
if (!first && separator != null) {
context.appendSql(separator);
}
first = false;
// 绑定变量
context.bind(item, o);
if (index != null) {
context.bind(index, o);
}
// 处理子节点
contents.apply(context);
}
// 4. 添加后缀
if (close != null) {
context.appendSql(close);
}
return true;
}
}
}
七、参数处理与结果映射
7.1 参数处理
/**
* MyBatis 参数处理机制
*
* #{ } - 预编译参数(PreparedStatement)
* ${ } - 字符串替换(直接拼接,有 SQL 注入风险)
*/
public class ParameterHandling {
/**
* 参数映射示例
*/
public interface UserMapper {
// 单个参数
User selectById(Long id);
// 多个参数 - 使用 @Param 注解
User selectByNameAndEmail(@Param("name") String name,
@Param("email") String email);
// 对象参数
int insert(User user);
// Map 参数
List<User> selectByMap(Map<String, Object> params);
// 集合参数
List<User> selectByIds(@Param("ids") List<Long> ids);
}
/*
<!-- 参数处理示例 -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 单个参数 -->
<select id="selectById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
<!-- 多个参数 -->
<select id="selectByNameAndEmail" resultType="User">
SELECT * FROM user
WHERE name = #{name} AND email = #{email}
</select>
<!-- 对象参数 -->
<insert id="insert" parameterType="User">
INSERT INTO user(name, email, age)
VALUES(#{name}, #{email}, #{age})
</insert>
<!-- Map 参数 -->
<select id="selectByMap" resultType="User">
SELECT * FROM user
WHERE name = #{name} AND age > #{minAge}
</select>
<!-- 集合参数 -->
<select id="selectByIds" resultType="User">
SELECT * FROM user WHERE id IN
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
<!-- ${ } 使用场景 - 动态表名、列名 -->
<select id="selectByTable" resultType="User">
SELECT * FROM ${tableName} WHERE id = #{id}
</select>
<select id="selectByColumn" resultType="User">
SELECT * FROM user ORDER BY ${orderColumn}
</select>
</mapper>
*/
/**
* ParameterHandler 源码
*/
public class DefaultParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject;
private final BoundSql boundSql;
@Override
public void setParameters(PreparedStatement ps) throws SQLException {
// 获取参数映射
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
// 1. 获取参数值
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(
parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject =
configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 2. 获取 TypeHandler
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
// 3. 设置参数
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
}
}
7.2 结果映射
/**
* MyBatis 结果集映射
*/
public class ResultMapping {
/**
* 简单映射
*/
/*
<!-- 自动映射(列名与属性名一致或驼峰转换) -->
<select id="selectAll" resultType="User">
SELECT id, name, email, age FROM user
</select>
<!-- resultMap 显式映射 -->
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/>
<result property="userName" column="name"/>
<result property="email" column="email"/>
<result property="age" column="age"/>
<result property="createTime" column="create_time"/>
</resultMap>
<select id="selectById" resultMap="userResultMap">
SELECT * FROM user WHERE id = #{id}
</select>
*/
/**
* 关联映射 - 一对一
*/
/*
<!-- 方式1:嵌套结果 -->
<resultMap id="userWithDeptResultMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="email" column="email"/>
<association property="department" javaType="Department">
<id property="id" column="dept_id"/>
<result property="name" column="dept_name"/>
</association>
</resultMap>
<select id="selectUserWithDept" resultMap="userWithDeptResultMap">
SELECT u.*, d.id as dept_id, d.name as dept_name
FROM user u
LEFT JOIN department d ON u.dept_id = d.id
WHERE u.id = #{id}
</select>
<!-- 方式2:嵌套查询(延迟加载) -->
<resultMap id="userWithDeptResultMap2" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<association property="department"
column="dept_id"
select="com.example.mapper.DeptMapper.selectById"
fetchType="lazy"/>
</resultMap>
<select id="selectById" resultMap="userWithDeptResultMap2">
SELECT * FROM user WHERE id = #{id}
</select>
*/
/**
* 关联映射 - 一对多
*/
/*
<!-- 方式1:嵌套结果 -->
<resultMap id="userWithOrdersResultMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<collection property="orders" ofType="Order">
<id property="id" column="order_id"/>
<result property="orderNo" column="order_no"/>
<result property="amount" column="amount"/>
</collection>
</resultMap>
<select id="selectUserWithOrders" resultMap="userWithOrdersResultMap">
SELECT u.*, o.id as order_id, o.order_no, o.amount
FROM user u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = #{id}
</select>
<!-- 方式2:嵌套查询 -->
<resultMap id="userWithOrdersResultMap2" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<collection property="orders"
column="id"
select="com.example.mapper.OrderMapper.selectByUserId"/>
</resultMap>
*/
/**
* 鉴别器映射
*/
/*
<resultMap id="vehicleResultMap" type="Vehicle">
<id property="id" column="id"/>
<result property="name" column="name"/>
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultType="Car">
<result property="doorCount" column="door_count"/>
</case>
<case value="2" resultType="Truck">
<result property="loadCapacity" column="load_capacity"/>
</case>
</discriminator>
</resultMap>
*/
/**
* ResultSetHandler 源码
*/
public class DefaultResultSetHandler implements ResultSetHandler {
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 获取 ResultMap
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
while (rsw != null && resultSetCount < resultMaps.size()) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// 处理结果集
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
resultSetCount++;
}
return collapseSingleResultList(multipleResults);
}
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap,
List<Object> multipleResults,
ResultMapping parentMapping) throws SQLException {
// 创建结果对象
Object rowValue = createResultObject(rsw, resultMap, null);
// 填充属性
applyAutomaticMappings(rsw, resultMap, rowValue);
applyPropertyMappings(rsw, resultMap, rowValue);
}
private Object createResultObject(ResultSetWrapper rsw,
ResultMap resultMap,
String columnPrefix) throws SQLException {
// 1. 使用 TypeHandler 创建
// 2. 使用构造函数创建
// 3. 使用默认构造函数创建
// 4. 使用 ObjectFactory 创建
}
}
}
八、实战应用场景
8.1 完整的 CRUD 示例
/**
* 完整的 MyBatis CRUD 示例
*/
public class MyBatisCrudExample {
/**
* 实体类
*/
@Data
public static class User {
private Long id;
private String name;
private String email;
private Integer age;
private Integer status;
private Date createTime;
private Date updateTime;
}
/**
* Mapper 接口
*/
public interface UserMapper {
// 基础 CRUD
User selectById(Long id);
List<User> selectAll();
int insert(User user);
int update(User user);
int deleteById(Long id);
// 条件查询
List<User> selectByCondition(UserQuery query);
long countByCondition(UserQuery query);
// 批量操作
int batchInsert(List<User> users);
int batchDelete(List<Long> ids);
int batchUpdate(List<User> users);
// 分页查询
List<User> selectByPage(@Param("offset") int offset,
@Param("limit") int limit);
}
/**
* 查询条件对象
*/
@Data
public static class UserQuery {
private String name;
private String email;
private Integer minAge;
private Integer maxAge;
private List<Integer> statusList;
private Date startTime;
private Date endTime;
}
/**
* XML 映射文件
*/
/*
<?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="com.example.mapper.UserMapper">
<resultMap id="BaseResultMap" type="User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="email" property="email"/>
<result column="age" property="age"/>
<result column="status" property="status"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<sql id="Base_Column_List">
id, name, email, age, status, create_time, update_time
</sql>
<!-- 单条查询 -->
<select id="selectById" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
WHERE id = #{id}
</select>
<!-- 查询全部 -->
<select id="selectAll" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
ORDER BY create_time DESC
</select>
<!-- 条件查询 -->
<select id="selectByCondition" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="email != null">
AND email = #{email}
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
<if test="statusList != null and statusList.size() > 0">
AND status IN
<foreach collection="statusList" item="status"
open="(" close=")" separator=",">
#{status}
</foreach>
</if>
<if test="startTime != null">
AND create_time >= #{startTime}
</if>
<if test="endTime != null">
AND create_time <= #{endTime}
</if>
</where>
ORDER BY create_time DESC
</select>
<!-- 统计 -->
<select id="countByCondition" resultType="long">
SELECT COUNT(1)
FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<!-- 其他条件同上 -->
</where>
</select>
<!-- 插入 -->
<insert id="insert" parameterType="User"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (name, email, age, status, create_time)
VALUES (#{name}, #{email}, #{age}, #{status}, NOW())
</insert>
<!-- 批量插入 -->
<insert id="batchInsert" parameterType="java.util.List"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (name, email, age, status, create_time)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.email}, #{user.age}, #{user.status}, NOW())
</foreach>
</insert>
<!-- 更新 -->
<update id="update" parameterType="User">
UPDATE user
<set>
<if test="name != null">name = #{name},</if>
<if test="email != null">email = #{email},</if>
<if test="age != null">age = #{age},</if>
<if test="status != null">status = #{status},</if>
update_time = NOW()
</set>
WHERE id = #{id}
</update>
<!-- 批量更新 - CASE WHEN 方式 -->
<update id="batchUpdate">
UPDATE user SET status =
<foreach collection="list" item="user" open="CASE id" close="END">
WHEN #{user.id} THEN #{user.status}
</foreach>
WHERE id IN
<foreach collection="list" item="user" open="(" close=")" separator=",">
#{user.id}
</foreach>
</update>
<!-- 删除 -->
<delete id="deleteById">
DELETE FROM user WHERE id = #{id}
</delete>
<!-- 批量删除 -->
<delete id="batchDelete">
DELETE FROM user WHERE id IN
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
<!-- 分页查询 -->
<select id="selectByPage" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
ORDER BY create_time DESC
LIMIT #{offset}, #{limit}
</select>
</mapper>
*/
/**
* 使用示例
*/
public static void main(String[] args) {
SqlSessionFactory factory = SqlSessionFactoryBuilder.build(...);
try (SqlSession session = factory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
// 1. 插入
User user = new User();
user.setName("张三");
user.setEmail("zhangsan@example.com");
user.setAge(25);
user.setStatus(1);
int rows = mapper.insert(user);
System.out.println("插入行数: " + rows + ", ID: " + user.getId());
// 2. 批量插入
List<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
User u = new User();
u.setName("用户" + i);
u.setEmail("user" + i + "@example.com");
u.setAge(20 + i);
u.setStatus(1);
users.add(u);
}
mapper.batchInsert(users);
// 3. 条件查询
UserQuery query = new UserQuery();
query.setName("张");
query.setMinAge(20);
query.setMaxAge(30);
query.setStatusList(Arrays.asList(1, 2));
List<User> result = mapper.selectByCondition(query);
// 4. 分页查询
int page = 1, pageSize = 10;
int offset = (page - 1) * pageSize;
List<User> pageData = mapper.selectByPage(offset, pageSize);
// 5. 更新
user.setAge(26);
mapper.update(user);
// 6. 批量更新状态
List<User> updateList = new ArrayList<>();
for (User u : users) {
u.setStatus(2);
updateList.add(u);
}
mapper.batchUpdate(updateList);
// 7. 删除
mapper.deleteById(user.getId());
// 提交事务
session.commit();
}
}
}
8.2 自定义类型处理器
/**
* 自定义 TypeHandler
*/
public class CustomTypeHandlers {
/**
* JSON 类型处理器 - 存储对象为 JSON 字符串
*/
@MappedTypes(UserInfo.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public static class JsonTypeHandler extends BaseTypeHandler<UserInfo> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
UserInfo parameter, JdbcType jdbcType)
throws SQLException {
try {
ps.setString(i, objectMapper.writeValueAsString(parameter));
} catch (JsonProcessingException e) {
throw new SQLException("Error converting to JSON", e);
}
}
@Override
public UserInfo getNullableResult(ResultSet rs, String columnName)
throws SQLException {
String json = rs.getString(columnName);
return parseJson(json);
}
@Override
public UserInfo getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
String json = rs.getString(columnIndex);
return parseJson(json);
}
@Override
public UserInfo getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
String json = cs.getString(columnIndex);
return parseJson(json);
}
private UserInfo parseJson(String json) throws SQLException {
if (json == null || json.isEmpty()) {
return null;
}
try {
return objectMapper.readValue(json, UserInfo.class);
} catch (JsonProcessingException e) {
throw new SQLException("Error parsing JSON", e);
}
}
}
/**
* 枚举类型处理器
*/
public static class UserStatus {
public enum Status {
ACTIVE(1), INACTIVE(0), DELETED(-1);
private final int code;
Status(int code) { this.code = code; }
public int getCode() { return code; }
public static Status fromCode(int code) {
for (Status s : values()) {
if (s.code == code) return s;
}
throw new IllegalArgumentException("Unknown code: " + code);
}
}
@MappedTypes(Status.class)
public static class StatusTypeHandler extends BaseTypeHandler<Status> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
Status parameter, JdbcType jdbcType)
throws SQLException {
ps.setInt(i, parameter.getCode());
}
@Override
public Status getNullableResult(ResultSet rs, String columnName)
throws SQLException {
int code = rs.getInt(columnName);
return rs.wasNull() ? null : Status.fromCode(code);
}
@Override
public Status getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
int code = rs.getInt(columnIndex);
return rs.wasNull() ? null : Status.fromCode(code);
}
@Override
public Status getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
int code = cs.getInt(columnIndex);
return cs.wasNull() ? null : Status.fromCode(code);
}
}
}
/**
* 注册 TypeHandler
*/
/*
<!-- mybatis-config.xml -->
<typeHandlers>
<typeHandler handler="com.example.handler.JsonTypeHandler"/>
<typeHandler handler="com.example.handler.StatusTypeHandler"/>
</typeHandlers>
<!-- 或在 Mapper 中指定 -->
<result property="userInfo" column="user_info"
typeHandler="com.example.handler.JsonTypeHandler"/>
*/
}
8.3 插件开发
/**
* MyBatis 插件(拦截器)开发
*
* 可拦截的四大对象:
* 1. Executor - 执行器
* 2. StatementHandler - 语句处理器
* 3. ParameterHandler - 参数处理器
* 4. ResultSetHandler - 结果集处理器
*/
public class MyBatisPlugins {
/**
* 分页插件
*/
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare",
args = {Connection.class, Integer.class})
})
public static class PagePlugin implements Interceptor {
private static final String PAGE_SQL = "SELECT * FROM (%s) temp LIMIT %d, %d";
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
// 获取原始 SQL
BoundSql boundSql = statementHandler.getBoundSql();
String originalSql = boundSql.getSql();
// 获取参数对象
Object parameterObject = boundSql.getParameterObject();
// 判断是否为分页查询
if (parameterObject instanceof Map) {
Map<?, ?> params = (Map<?, ?>) parameterObject;
if (params.containsKey("page") && params.containsKey("pageSize")) {
int page = (Integer) params.get("page");
int pageSize = (Integer) params.get("pageSize");
int offset = (page - 1) * pageSize;
// 重写 SQL
String pageSql = String.format(PAGE_SQL,
originalSql, offset, pageSize);
metaObject.setValue("delegate.boundSql.sql", pageSql);
}
}
return invocation.proceed();
}
}
/**
* SQL 执行时间统计插件
*/
@Intercepts({
@Signature(type = Executor.class, method = "update",
args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class})
})
public static class SqlTimePlugin implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(SqlTimePlugin.class);
private static final long SLOW_SQL_THRESHOLD = 1000; // 1秒
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
String statementId = ms.getId();
long startTime = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
long cost = System.currentTimeMillis() - startTime;
if (cost > SLOW_SQL_THRESHOLD) {
logger.warn("慢SQL: {} 执行耗时: {}ms", statementId, cost);
} else {
logger.debug("SQL: {} 执行耗时: {}ms", statementId, cost);
}
}
}
}
/**
* 数据权限插件
*/
@Intercepts({
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "update",
args = {MappedStatement.class, Object.class})
})
public static class DataPermissionPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
// 获取当前用户的数据权限
Long deptId = getCurrentUserDeptId();
// 重写 SQL 添加数据权限过滤
BoundSql boundSql = ms.getBoundSql(parameter);
String sql = boundSql.getSql();
// 如果是需要数据权限的表,添加条件
if (needDataPermission(ms.getId())) {
sql = addDataPermission(sql, deptId);
// 更新 SQL
MetaObject metaObject = SystemMetaObject.forObject(boundSql);
metaObject.setValue("sql", sql);
}
return invocation.proceed();
}
private String addDataPermission(String sql, Long deptId) {
// 简单实现:在 WHERE 后添加 dept_id 条件
// 实际应用中需要更复杂的 SQL 解析
if (sql.toLowerCase().contains("where")) {
return sql + " AND dept_id = " + deptId;
} else {
return sql + " WHERE dept_id = " + deptId;
}
}
}
/**
* 注册插件
*/
/*
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="com.example.plugin.PagePlugin"/>
<plugin interceptor="com.example.plugin.SqlTimePlugin"/>
<plugin interceptor="com.example.plugin.DataPermissionPlugin"/>
</plugins>
*/
}
九、总结与最佳实践
9.1 核心原理总结
┌─────────────────────────────────────────────────────────────────────────┐
│ MyBatis 核心架构总结 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 配置加载流程 │
│ XML/注解 → Configuration → MappedStatement → SqlSource │
│ │
│ 2. SQL 执行流程 │
│ SqlSession → Executor → StatementHandler → JDBC → ResultSet │
│ │
│ 3. 代理机制 │
│ Mapper 接口 → JDK 动态代理 → MapperProxy → SqlSession │
│ │
│ 4. 缓存机制 │
│ 二级缓存 → 一级缓存 → 数据库 │
│ │
│ 5. 插件机制 │
│ 责任链模式 → 拦截四大对象 → 增强功能 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
9.2 最佳实践
-
SqlSession 管理
- 使用 try-with-resources 确保关闭
- 与 Spring 集成时使用 SqlSessionTemplate
-
Mapper 编写
- 接口方法名与 XML id 保持一致
- 复杂查询使用 XML,简单查询可用注解
- 参数使用 @Param 注解明确命名
-
缓存使用
- 优先使用一级缓存
- 二级缓存适合读多写少、对一致性要求不高的场景
- 分布式环境建议使用 Redis 缓存
-
动态 SQL
- 使用
#{}而非${}防止 SQL 注入 - 复杂条件抽取为
<sql>片段复用 - 注意
<>需要转义
- 使用
-
性能优化
- 批量操作使用 foreach
- 避免 N+1 查询问题
- 合理设置 fetchSize
- 使用插件监控慢 SQL
-
类型处理
- 枚举类型自定义 TypeHandler
- JSON 类型使用专门处理器
- 注意时区问题
参考资料:
- MyBatis 官方文档:mybatis.org/mybatis-3/
- MyBatis 源码:github.com/mybatis/myb…
- 《MyBatis技术内幕》- 徐郡明
六、思考与练习
思考题
-
基础题:SqlSessionFactory、SqlSession、Mapper 接口三者的生命周期分别是什么?为什么 SqlSession 不是线程安全的?
-
进阶题:MyBatis 的一级缓存和二级缓存有什么区别?在什么情况下应该使用二级缓存?二级缓存可能出现什么问题?
-
实战题:如何解决 MyBatis 的 N+1 查询问题?嵌套查询和嵌套结果两种方式各有什么优缺点?
编程练习
练习:基于 MyBatis 实现一个完整的 CRUD 系统,要求:
- 实体类包含一对一、一对多关联关系
- 使用 resultMap 完成复杂结果映射
- 实现动态 SQL 查询(支持多条件组合)
- 实现批量插入、批量更新操作
- 自定义 TypeHandler 处理 JSON 类型字段
- 配置二级缓存并验证缓存效果
章节关联
- 前置章节:《会话管理详解》- 掌握 Web 安全后学习数据访问
- 后续章节:《MyBatis-Plus 详解》- 学习 MyBatis 增强工具
- 扩展阅读:
- MyBatis 插件开发指南:mybatis.org/mybatis-3/z…
- 动态 SQL 最佳实践:mybatis.org/mybatis-3/z…
📝 下一章预告
下一章将讲解 MyBatis-Plus 框架,它基于 MyBatis 提供了更强大的 CRUD 操作和代码生成功能,极大提升开发效率。
本章完