35-MyBatis核心详解

6 阅读12分钟

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 &lt;= #{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 &lt;= #{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 &lt;= #{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 最佳实践

  1. SqlSession 管理

    • 使用 try-with-resources 确保关闭
    • 与 Spring 集成时使用 SqlSessionTemplate
  2. Mapper 编写

    • 接口方法名与 XML id 保持一致
    • 复杂查询使用 XML,简单查询可用注解
    • 参数使用 @Param 注解明确命名
  3. 缓存使用

    • 优先使用一级缓存
    • 二级缓存适合读多写少、对一致性要求不高的场景
    • 分布式环境建议使用 Redis 缓存
  4. 动态 SQL

    • 使用 #{} 而非 ${} 防止 SQL 注入
    • 复杂条件抽取为 <sql> 片段复用
    • 注意 < > 需要转义
  5. 性能优化

    • 批量操作使用 foreach
    • 避免 N+1 查询问题
    • 合理设置 fetchSize
    • 使用插件监控慢 SQL
  6. 类型处理

    • 枚举类型自定义 TypeHandler
    • JSON 类型使用专门处理器
    • 注意时区问题

参考资料:


六、思考与练习

思考题

  1. 基础题:SqlSessionFactory、SqlSession、Mapper 接口三者的生命周期分别是什么?为什么 SqlSession 不是线程安全的?

  2. 进阶题:MyBatis 的一级缓存和二级缓存有什么区别?在什么情况下应该使用二级缓存?二级缓存可能出现什么问题?

  3. 实战题:如何解决 MyBatis 的 N+1 查询问题?嵌套查询和嵌套结果两种方式各有什么优缺点?

编程练习

练习:基于 MyBatis 实现一个完整的 CRUD 系统,要求:

  1. 实体类包含一对一、一对多关联关系
  2. 使用 resultMap 完成复杂结果映射
  3. 实现动态 SQL 查询(支持多条件组合)
  4. 实现批量插入、批量更新操作
  5. 自定义 TypeHandler 处理 JSON 类型字段
  6. 配置二级缓存并验证缓存效果

章节关联

  • 前置章节:《会话管理详解》- 掌握 Web 安全后学习数据访问
  • 后续章节:《MyBatis-Plus 详解》- 学习 MyBatis 增强工具
  • 扩展阅读

📝 下一章预告

下一章将讲解 MyBatis-Plus 框架,它基于 MyBatis 提供了更强大的 CRUD 操作和代码生成功能,极大提升开发效率。


本章完