Mybatis插件机制(拦截器)和缓存机制示例详解
作者:南部余额
Mybatis 插件机制
官网插件介绍:https://mybatis.org/mybatis-3/zh_CN/configuration.html#plugins
简介
MyBatis 插件(Interceptor)是 MyBatis 提供的扩展机制,允许用户在执行过程中的特定点拦截并自定义核心组件的行为。
Mybatis框架也有拦截器,Mybatis的拦截器出现在java和数据库之间- mapper代码——>>拦截器(1.接收要执行的sql语句。2.获取数据返回的结果)——>>数据库。
- Javaweb
filter(过滤器),提取servlet的重复工作。 - springmvc
HandlerInterceptor拦截器,提取handler的重复工作。 Mybatis的interceptor(插件),提取持久层的重复工作。例如:分页、逻辑删除。
核心概念
- 拦截器:实现特定接口的 Java 类
- 拦截点:MyBatis 允许拦截的特定方法
- 责任链模式:多个插件形成调用链
四大组件
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed):Mybatis的执行器,用于执行增删改查操作。Executor是MyBatis的核心组件,负责整个 SQL 执行流程的调度和管理。它协调StatementHandler、ParameterHandler、ResultSetHandler完成 SQL 执行,并负责事务管理、缓存管理和延迟加载等功能。ParameterHandler(getParameterObject, setParameters):处理SQL的参数对象。【参数处理器】ResultSetHandler(handleResultSets, handleOutputParameters):处理SQL的返回结果集。【结果返回处理器】StatementHandler(prepare, parameterize, batch, update, query):数据库的处理对象,用于执行SQL语句。【SQL语句处理器】
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
Executor接口:
Executor接口的实现类是:BaseExecutor它共有4个子类。BaseExecutor子类为SimpleExecutor:简单执行器(默认)BaseExecutor子类为CachingExecutor:带二级缓存的执行器。BaseExecutor子类为ReuseExecutor:重用 Statement 的执行器。BaseExecutor子类为BatchExecutor:批量操作执行器。
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
/**
* 执行更新操作(INSERT、UPDATE、DELETE)
* <p><b>执行流程:</b></p>
* <ol>
* <li>创建 StatementHandler</li>
* <li>调用 ParameterHandler 设置参数</li>
* <li>执行 SQL 更新</li>
* <li>清理相关缓存</li>
* </ol>
* @param ms MappedStatement 对象,包含 SQL 映射信息
* @param parameter 参数对象,可以是 Map 或 POJO
* @return 影响的行数
* @throws SQLException 如果数据库操作失败
*/
int update(MappedStatement ms, Object parameter) throws SQLException;
/**
* 执行查询操作(完整参数版本)
* 这是查询操作的底层实现,包含所有必要的参数。通常由另一个重载的 query 方法调用。
* @param <E> 返回结果的类型
* @param ms MappedStatement 对象
* @param parameter 参数对象
* @param rowBounds 分页参数,包含 offset 和 limit
* @param resultHandler 结果处理器,用于自定义结果处理
* @param cacheKey 缓存键,用于一级缓存
* @param boundSql 绑定的 SQL 信息
* @return 查询结果列表
* @throws SQLException 如果数据库操作失败
*
* @see #query(MappedStatement, Object, RowBounds, ResultHandler)
*/
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
CacheKey cacheKey, BoundSql boundSql) throws SQLException;
/**
* 执行查询操作(常用版本)
*
* <p><b>执行流程:</b></p>
* <ol>
* <li>检查一级缓存</li>
* <li>缓存未命中时查询数据库</li>
* <li>创建 StatementHandler 和 ResultSetHandler</li>
* <li>执行查询并映射结果</li>
* <li>将结果存入一级缓存</li>
* </ol>
* @param <E> 返回结果的类型
* @param ms MappedStatement 对象
* @param parameter 参数对象
* @param rowBounds 分页参数
* @param resultHandler 结果处理器,如果为 null 则使用默认处理器
* @return 查询结果列表
* @throws SQLException 如果数据库操作失败
*/
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
throws SQLException;
/**
* 执行查询操作,返回游标(用于大数据量流式处理)
* <p>与普通查询不同,游标查询不会一次性加载所有数据到内存,
* 而是逐条处理,适合处理大数据集。</p>
* @param <E> 返回结果的类型
* @param ms MappedStatement 对象
* @param parameter 参数对象
* @param rowBounds 分页参数
* @return 查询结果游标
* @throws SQLException 如果数据库操作失败
*/
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
/**
* 刷新批处理语句
*
* <p>执行所有缓存的批处理语句,通常在 BatchExecutor 中使用。
* 调用此方法会执行所有已添加但未执行的批处理操作。</p>
* @return 批处理结果列表
* @throws SQLException 如果数据库操作失败
* @see org.apache.ibatis.executor.BatchResult
*
* @example
* <pre>
* // 批处理操作示例
* for (User user : users) {
* sqlSession.insert("insertUser", user);
* }
* List<BatchResult> results = sqlSession.flushStatements();
* </pre>
*/
List<BatchResult> flushStatements() throws SQLException;
/**
* 提交事务
* <p>如果 required 为 true,则强制提交事务;
* 如果为 false,则根据事务状态决定是否提交。</p>
* @param required 是否强制提交
*/
void commit(boolean required) throws SQLException;
/**
* 回滚事务
* <p>如果 required 为 true,则强制回滚事务;
* 如果为 false,则根据事务状态决定是否回滚。</p>
* @param required 是否强制回滚
*/
void rollback(boolean required) throws SQLException;
/**
* 创建缓存键
*
* <p>根据 SQL 语句、参数和分页信息生成唯一的缓存键,
* 用于一级缓存的键值对存储。</p>
*
* @param ms MappedStatement 对象
* @param parameterObject 参数对象
* @param rowBounds 分页信息
* @param boundSql 绑定的 SQL
* @return 缓存键对象
*/
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
/**
* 检查指定缓存键是否已缓存
*
* <p>查询一级缓存中是否存在指定 MappedStatement 和缓存键对应的结果。</p>
*
* @param ms MappedStatement 对象
* @param key 缓存键
* @return 如果已缓存返回 true,否则返回 false
*/
boolean isCached(MappedStatement ms, CacheKey key);
/**
* 清理本地缓存(一级缓存)
*
* <p>清除当前 Executor 实例的所有一级缓存。
* 在更新操作后会自动调用,也可以手动调用。</p>
*/
void clearLocalCache();
/**
* 延迟加载
*
* <p>用于处理关联对象的延迟加载。当访问懒加载属性时,
* MyBatis 会调用此方法从数据库加载关联数据。</p>
*
* @param ms 关联对象的 MappedStatement
* @param resultObject 结果对象(主对象)
* @param property 要加载的属性名
* @param key 缓存键
* @param targetType 目标类型
*/
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
/**
* 获取事务对象
* @return 当前的事务对象
* @see org.apache.ibatis.transaction.Transaction
*/
Transaction getTransaction();
/**
* 关闭执行器
*
* <p>如果 forceRollback 为 true,则在关闭前强制回滚所有未提交的事务;
* 如果为 false,则根据事务状态决定。</p>
*
* @param forceRollback 是否强制回滚
*/
void close(boolean forceRollback);
/**
* 检查执行器是否已关闭
* @return 如果已关闭返回 true,否则返回 false
*/
boolean isClosed();
/**
* 设置执行器包装器
* <p>用于插件机制,允许在现有 Executor 外包装额外的逻辑。
* 这个方法通常由 MyBatis 内部使用。</p>
* @param executor 包装器执行器
*/
void setExecutorWrapper(Executor executor);
}
ResultSetHandler接口
- 接口的实现类
DefaultResultSetHandler,只有一个实现类。 ResultSetHandler负责将JDBC ResultSet转换为 Java 对象,是MyBatis ORM映射的核心组件。它处理结果集的遍历、对象创建、属性映射和关联关系处理。
主要职责:
- 将 ResultSet 映射为 Java 对象列表
- 处理游标结果的流式读取
- 处理存储过程的输出参数
- 支持复杂的结果映射(association、collection)
- 处理自动映射和自定义映射
/**
* MyBatis 结果集处理器核心接口
* <p><b>核心映射流程:</b></p>
* <ol>
* <li>遍历 ResultSet 的每一行</li>
* <li>根据 resultMap 创建目标对象实例</li>
* <li>将列值映射到对象属性</li>
* <li>处理嵌套查询和延迟加载</li>
* <li>返回映射后的对象集合</li>
* </ol>
*/
public interface ResultSetHandler {
/**
* 处理结果集并将其转换为 Java 对象列表,默认方法
*
* <p><b>执行流程:</b></p>
* <ol>
* <li>获取 ResultSet 元数据(列信息)</li>
* <li>遍历 ResultSet 的每一行数据</li>
* <li>根据 ResultMap 创建目标对象实例</li>
* <li>将列值设置到对象属性中(支持自动映射和显式映射)</li>
* <li>处理关联对象(association)和集合对象(collection)</li>
* <li>返回完整映射的对象列表</li>
* </ol>
*
* @param <E> 返回结果的类型
* @param stmt JDBC Statement 对象,包含执行后的 ResultSet
* @return 映射后的 Java 对象列表,如果没有结果返回空列表(非 null)
*/
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
/**
* 处理游标结果集,支持流式读取大数据集
*
* <p>与 {@link #handleResultSets(Statement)} 不同,此方法返回 Cursor 对象,
* 允许逐条处理结果,避免一次性加载所有数据到内存。</p>
*
* <p><b>适用场景:</b></p>
* <ul>
* <li>处理大量数据(避免内存溢出)</li>
* <li>需要逐条处理的业务逻辑</li>
* <li>数据导出或批量处理</li>
* </ul>
*
* @param <E> 返回结果的类型
* @param stmt JDBC Statement 对象
* @return 游标对象,用于迭代访问结果
*/
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
/**
* 处理存储过程的输出参数
* <p>专门用于处理 CallableStatement 中的输出参数(OUT/INOUT 参数)。
* 在执行存储过程后,从此方法中获取输出参数的值。</p>
* <p><b>处理流程:</b></p>
* <ol>
* <li>从 CallableStatement 中获取输出参数元数据</li>
* <li>根据 parameterMap 配置定位输出参数</li>
* <li>使用 TypeHandler 将 JDBC 类型转换为 Java 类型</li>
* <li>将输出参数值设置到参数对象中</li>
* </ol>
*
* @param cs 调用存储过程后的 CallableStatement 对象
*/
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
ParameterHandler接口
ParameterHandler负责将 Java 方法参数转换为JDBC参数,并设置到PreparedStatement中。它是MyBatis参数映射的核心组件,处理参数类型转换和参数设置。
主要职责:
- 获取原始参数对象
- 将
Java参数转换为JDBC参数 - 设置参数到
PreparedStatement - 处理参数类型映射
/**
* MyBatis 参数处理器核心接口
* <p><b>参数处理流程:</b></p>
* <ol>
* <li>获取 Mapper 方法的参数对象</li>
* <li>解析参数类型和值</li>
* <li>使用 TypeHandler 进行类型转换</li>
* <li>将参数设置到 PreparedStatement 的对应位置</li>
* </ol>
*/
public interface ParameterHandler {
/**
* 获取原始参数对象
*
* <p>返回 Mapper 方法调用时传入的原始参数对象。
* 参数可能是单个对象、Map、或者被 @Param 注解标注的多参数包装对象。</p>
*
* @return 原始参数对象,如果没有参数则返回 null
* @example
* // Mapper 方法定义:User selectUser(@Param("id") Long id, @Param("name") String name);
* // 调用时,getParameterObject() 返回的可能是:
* // 1. Map 对象:{id=1, name="张三", param1=1, param2="张三"}
* // 2. 或者经过包装的参数对象
*/
Object getParameterObject();
/**
* 设置参数到 PreparedStatement
*
* <p><b>核心执行流程:</b></p>
* <ol>
* <li>获取 BoundSql 中的参数映射信息</li>
* <li>遍历所有参数映射(ParameterMapping)</li>
* <li>根据参数名称从参数对象中获取值</li>
* <li>使用对应的 TypeHandler 将 Java 类型转换为 JDBC 类型</li>
* <li>调用 PreparedStatement.setXxx() 方法设置参数</li>
* </ol>
*
* @param ps JDBC PreparedStatement 对象
* @throws SQLException 如果数据库操作失败或参数设置错误
*/
void setParameters(PreparedStatement ps) throws SQLException;
}
StatementHandler接口
StatementHandler负责 SQL 语句的准备工作、参数设置和执行操作。它是连接Executor和JDBC Statement的桥梁,处理具体的 SQL 执行细节。
主要职责:
- 创建和管理
JDBC Statement对象 - 处理 SQL 语句的准备工作
- 协调
ParameterHandler进行参数设置 - 执行 SQL 查询和更新操作
- 处理批量操作
/**
* MyBatis 语句处理器核心接口
*/
public interface StatementHandler {
/**
* 准备 SQL 语句,创建 JDBC Statement 对象
*
* <p><b>执行流程:</b></p>
* <ol>
* <li>根据 SQL 类型创建对应的 Statement(PreparedStatement/CallableStatement)</li>
* <li>设置查询超时时间</li>
* <li>设置获取大小(fetch size)等优化参数</li>
* <li>返回准备好的 Statement 对象</li>
* </ol>
*
* @param connection 数据库连接
* @param transactionTimeout 事务超时时间(秒),可为 null
* @return 准备好的 JDBC Statement 对象
* @throws SQLException 如果数据库操作失败
*/
Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
/**
* 参数化 Statement,设置 SQL 参数
* <p>调用 ParameterHandler 将 Java 参数设置到 PreparedStatement 中。
* 这是参数绑定的核心步骤。</p>
*
* @param statement JDBC Statement 对象(通常是 PreparedStatement)
* @throws SQLException 如果参数设置失败
*/
void parameterize(Statement statement) throws SQLException;
/**
* 执行批量操作
*
* <p>将多个 SQL 语句添加到批处理中,但不立即执行。
* 实际的执行由 {@link org.apache.ibatis.executor.BatchExecutor} 控制。</p>
*
* @param statement JDBC Statement 对象
* @throws SQLException 如果批处理操作失败
*/
void batch(Statement statement) throws SQLException;
/**
* 执行更新操作(INSERT、UPDATE、DELETE)
*
* <p><b>执行流程:</b></p>
* <ol>
* <li>调用 parameterize() 方法设置参数</li>
* <li>执行 Statement.executeUpdate()</li>
* <li>返回影响的行数</li>
* </ol>
*
* @param statement JDBC Statement 对象
* @return 影响的行数
* @throws SQLException 如果数据库操作失败
*/
int update(Statement statement) throws SQLException;
/**
* 执行查询操作,返回结果列表
*
* <p><b>执行流程:</b></p>
* <ol>
* <li>调用 parameterize() 方法设置参数</li>
* <li>执行 Statement.executeQuery()</li>
* <li>调用 ResultSetHandler 处理结果集</li>
* <li>返回映射后的对象列表</li>
* </ol>
*
* @param <E> 返回结果的类型
* @param statement JDBC Statement 对象
* @param resultHandler 结果处理器
* @return 查询结果列表
* @throws SQLException 如果数据库操作失败
*/
<E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
/**
* 执行查询操作,返回游标(用于流式处理大数据集)
*
* <p>与 {@link #query(Statement, ResultHandler)} 不同,此方法返回 Cursor 对象,
* 允许逐条读取结果,避免一次性加载所有数据到内存。</p>
*
* @param <E> 返回结果的类型
* @param statement JDBC Statement 对象
* @return 查询结果游标
* @throws SQLException 如果数据库操作失败
*/
<E> Cursor<E> queryCursor(Statement statement) throws SQLException;
/**
* 获取绑定的 SQL 信息
* <p>BoundSql 包含最终要执行的 SQL 语句、参数映射信息和参数对象。</p>
* @return BoundSql 对象,包含 SQL 语句和参数信息
*/
BoundSql getBoundSql();
/**
* 获取参数处理器
* <p>ParameterHandler 负责将 Java 参数转换为 JDBC 参数。</p>
* @return ParameterHandler 对象
*/
ParameterHandler getParameterHandler();
}
Mybatis拦截器Interceptor接口
- 拦截器默认情况下会拦截所有符合条件的方法,因此需要谨慎使用,避免性能问题。
- 多个拦截器的执行顺序由配置顺序决定,先配置的先执行。
在拦截器中修改SQL语句或参数时,需要注意线程安全问题和性能影响。
1. 了解插件接口类
Mybatis拦截器接口,有三个方法,其中intercept方法为,核心方法。
public interface Interceptor {
// 核心拦截方法
Object intercept(Invocation invocation) throws Throwable;
// 生成代理对象,将该插件加入到过滤器链中。底层是代理技术。
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// 设置属性值,接收xml传递来的参数信息
default void setProperties(Properties properties) {
// NOP
}
}
2. 实现Mybatis拦截器接口
- 实现核心接口,并确保mybatis方法的正常执行。
public class TestMybatisInterceptor implements Interceptor {
/**
* 核心方法
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 核心方法,确保mybatis方法可以正常运行
Object proceed = invocation.proceed();
return proceed;
}
}
3. 了解@Intercepts()注解
- MyBatis的
@Intercepts注解用于标记一个类作为拦截器,并指定该拦截器要拦截的方法。 - 它通常与
@Signature注解一起使用,@Signature用于指定要拦截的接口、方法以及参数类型。- 接口:为四大组件
- 方法:为四大组件中的方法。
- 参数类型:四大组件中的方法类型(直接复制方法类型后跟
.class)。
3.1 基本结构
@Intercepts({
@Signature(type = 要拦截的类.class, method = "要拦截的方法名", args = {参数类型列表})
})
public class MyInterceptor implements Interceptor {
// 实现Interceptor接口的方法
}
3.2 可拦截的类-也就是四大组件
MyBatis允许拦截以下四个接口的方法:
Executor:执行器,负责SQL的执行和缓存管理。ParameterHandler:参数处理器,负责将用户传递的参数转换成JDBC类型的参数。ResultSetHandler:结果集处理器,负责将JDBC返回的ResultSet转换成对象。StatementHandler:语句处理器,负责SQL语句的生成和参数设置。
3.3 @Signature注解的详细说明
type:指定要拦截的接口类,必须是上述四个接口之一。method:指定要拦截的方法名,字符串类型。args:指定拦截方法的参数类型列表,通过参数类型列表可以唯一确定一个方法(因为方法可能重载)。
确定要拦截的组件类型。如果拦截对象为Executor执行器,则全局搜索Executor.class接口。method=该接口中的方法,args=该方法的参数类型。
/**
* 拦截mybatis执行器的方法query()【执行查询操作(常用版本)】
*/
@Intercepts(
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)
)
public class TestMybatisInterceptor implements Interceptor {
/**
* 核心拦截方法
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 核心方法,确保mybatis方法可以正常运行
Object proceed = invocation.proceed();
return proceed;
}
}
3.4 拦截器方法的参数说明
在intercept方法中,可以通过Invocation对象获取被拦截的方法的参数、目标对象和方法。
invocation.getTarget():获取被拦截的对象(例如Executor实例)。 invocation.getMethod():获取被拦截的方法(Method对象)。 invocation.getArgs():获取被拦截方法的参数数组。 invocation.proceed():执行原方法。
3.5 拦截Executor组件query方法
package com.maven.mybatis.plugins;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.lang.reflect.Method;
/**
* 拦截mybatis执行器的方法query()【执行查询操作(常用版本)】
*/
@Intercepts(
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)
)
public class TestMybatisInterceptor implements Interceptor {
/**
* 核心拦截方法,拦截所有的查询方法,并输出sql语句
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// CachingExecutor
Object target = invocation.getTarget();
// 拦截的那个方法:被@Signature注解修饰的methdo="query"方法。
// org.apache.ibatis.executor.Executor.query(MappedStatement,Object,RowBounds,ResultHandler)
Method method = invocation.getMethod();
// 方法的参数信息
Object[] args = invocation.getArgs();
// 获取SQL语句处理器
MappedStatement ms = (MappedStatement) args[0];
// args[1]:paramHandler,获取参数处理器
BoundSql boundSql = ms.getBoundSql(args[1]);
// 获取sql语句,xml中定义的sql语句,未进行参数替换
String sql = boundSql.getSql();
System.out.println("sql = " + sql);
// 核心方法,确保mybatis方法可以正常运行
Object proceed = invocation.proceed();
return proceed;
}
}
3.6 在mybatis-config.xml配置文件引入插件
- 标签引入顺序有严格的要求。必须在
environments环境配置标签之前。
<plugins>
<plugin interceptor="com.maven.mybatis.plugins.TestMybatisInterceptor">
</plugin>
</plugins>3.7 执行测试
@Test
public void user() throws Exception {
// 将配置文件转为输入流
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 创建SqlSessionFactoryBuilder,作用是根据配置文件,创建SqlSessionFactory工厂
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 根据配置文件,创建工程SqlSessionFactory,全局唯一
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
// 工厂创建SqlSession,建立一次会话
SqlSession sqlSession = sqlSessionFactory.openSession();
// 使用和指定语句的参数和返回值相匹配的接口,映射器实例
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1);
System.out.println("user = " + user);
sqlSession.close();
}
- 结果返回:
sql = select
id ,
user_name userName,
password
from user where id = ?
Mybatis 缓存机制
缓存机-官网简介:https://mybatis.org/mybatis-3/zh_CN/sqlmap-xml.html#cache
本地缓存-官网简介:https://mybatis.org/mybatis-3/zh_CN/java-api.html
缓存机制
Mybatis 使用到了两种缓存:本地缓存(一级缓存、local cache) 和 二级缓存(second level cache)。
本地缓存(一级缓存、事务缓存)
默认事务期间,会开启事务级别缓存。
- 同一个事务期间,前面查询的数据,后面如果再执行相同查询,会从一级缓存中获取数据,不会给数据库发送SQL。
概念:一级缓存(SqlSession级别)
每当一个新 sqlSession被创建,MyBatis 就会创建一个与之相关联的本地缓存。任何在 sqlSession执行过的查询结果都会被保存在本地缓存中,所以,当再次执行参数相同的相同查询时,就不需要实际查询数据库了。本地缓存将会在做出修改、事务提交或回滚,以及关闭 sqlSession时清空。
默认情况下,本地缓存数据的生命周期等同于整个 sqlSession的周期。由于缓存会被用来解决循环引用问题和加快重复嵌套查询的速度,所以无法将其完全禁用。但是你可以通过设置 localCacheScope=STATEMENT 来只在语句执行时使用缓存。
注意,如果 localCacheScope 被设置为 SESSION,对于某个对象,MyBatis 将返回在本地缓存中唯一对象的引用。对返回的对象(例如 list)做出的任何修改将会影响本地缓存的内容,进而将会影响到在本次 sqlSession中从缓存返回的值。因此,不要对 MyBatis 所返回的对象作出更改,以防后患。
二级缓存(Mapper级别)
二级缓存是基于 Mapper 的,它需要手动开启。二级缓存的作用范围更广,可以跨 SqlSession 使用。开启二级缓存后,相同的查询可以在不同的 SqlSession 中共享缓存数据。但是,二级缓存需要查询结果对象实现 Serializable (序列化)接口,因为缓存数据可能会被序列化到磁盘。
三级缓存(自定义缓存)
三级缓存是 MyBatis 提供的一个扩展点,允许开发者自定义缓存实现。在分布式系统中,一级和二级缓存可能会导致数据不一致的问题,因此可以通过集成第三方缓存,如 Redis 或 Ehcache,来实现分布式缓存。这种方式可以跨应用共享缓存数据,更适合现代的微服务架构。
一级缓存与二级缓存的区别?
MyBatis 的一级缓存和二级缓存是MyBatis中提供的两种缓存机制,它们的主要区别在于作用域和生命周期。下面我将从多个维度对它们进行详细的对比和解释。
一级缓存(Local Cache)
作用域:
一级缓存是
SqlSession级别的缓存,这意味着每个SqlSession都有自己独立的一级缓存。在同一个
SqlSession中,如果执行相同的查询,MyBatis会从一级缓存中直接返回结果,而不会再次访问数据库。
生命周期:
一级缓存的生命周期与SqlSession一致。当SqlSession被关闭时,该SqlSession的一级缓存也会被清空。
此外,当执行DML(insert、update、delete)操作时,为了防止脏数据,MyBatis会清空当前SqlSession的一级缓存。
配置:
- 一级缓存默认开启,且不能关闭。但是,你可以通过配置来调整其行为,例如设置本地缓存作用域(localCacheScope)。
工作机制:
在同一个SqlSession中,执行两次相同的SQL查询,第一次会从数据库中查询并放入缓存,第二次则直接从缓存中获取。
如果两次查询之间执行了DML操作,那么缓存会被清空,第二次查询会重新访问数据库。
二级缓存(Second Level Cache)
作用域:
二级缓存是
Mapper级别(或namespace级别)的缓存,这意味着多个SqlSession可以共享同一个Mapper的二级缓存。二级缓存是跨SqlSession的,即一个SqlSession执行查询后,会将结果存入二级缓存,其他SqlSession可以共享这些缓存数据。
生命周期:
二级缓存的生命周期与整个应用程序一致,即只要没有执行过DML操作,缓存数据会一直存在,直到被主动清除或缓存过期。
当执行DML操作时,MyBatis会清空对应Mapper的二级缓存,以确保数据的一致性。
配置:
- 二级缓存默认是关闭的,需要在每个Mapper.xml文件中通过
<cache>标签显式开启,或者通过<cache-ref>引用其他缓存的配置。
工作机制:
当在一个SqlSession中执行查询后,如果该Mapper配置了二级缓存,那么查询结果会被存入二级缓存。
另一个SqlSession执行相同的查询时,会先从二级缓存中查找,如果找到则直接返回,否则再去数据库查询。
缓存执行流程
执行查询操作
↓
检查二级缓存(如果开启)
↓ 命中 → 返回结果
↓ 未命中
检查一级缓存(当前SqlSession)
↓ 命中 → 返回结果(并存入二级缓存)
↓ 未命中
查询数据库
↓
结果存入一级缓存
↓
SqlSession关闭时 → 一级缓存数据刷入二级缓存
举例说明一级和二级缓存
一级缓存(SqlSession级别):
- 每个项目经理(
SqlSession)有自己的工作笔记(一级缓存),当他通过某个招聘渠道(Mapper)招募到员工(查询到数据)后,他会把员工的信息记录在自己的工作笔记中。 - 这样,当他在同一个项目(
同一个SqlSession)中需要再次联系这个员工时,他就不需要再去招聘(查询数据库)了,直接看笔记就行。 - 而且,这个笔记只有他自己能用,其他项目经理看不到。
二级缓存(Mapper级别):
- 公司有一个公共的人才库(二级缓存),每个项目经理都可以把通过招聘渠道(Mapper)招募到的 员工信息(查询到的数据) 共享到人才库中。
- 这样,其他项目经理在需要同样的员工时,就不需要再去招聘(查询数据库),可以直接从 人才库(二级缓存) 中获取。
- 但是,如果员工信息有变动(数据更新),那么人才库中的信息需要被更新(缓存失效),所有项目经理都会得到最新的信息。
什么时候会导致缓存失效?
缓存不命中与缓存失效一个意思。
失效几种情况:
- 查询的东西不一样。
- 两次查询之间,进行了一次增删改(由于增删改,会引发数据库的变化,mybatis认为,数据可能变化了,他就要再发一次查询)。
总结
到此这篇关于Mybatis插件机制(拦截器)和缓存机制的文章就介绍到这了,更多相关Mybatis拦截器和缓存机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
