Mybatis一级缓存

一、原生 MyBatis 中

一级缓存作用域:同一个 SqlSession范围内

缓存行为:

java 复制代码
1、同一会话中相同查询直接从缓存返回结果
2、执行更新操作(INSERT/UPDATE/DELETE)会清空当前会话缓存
3、会话关闭时缓存自动销毁

二、MyBatis-Spring 中的变化

SqlSessionTemplate的特殊设计,在 MyBatis-Spring 中:

java 复制代码
// SqlSessionTemplate 的核心特性
public class SqlSessionTemplate implements SqlSession {
    // 通过代理实现会话管理
    private final SqlSession sqlSessionProxy;
    
    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
            SqlSessionFactory.class.getClassLoader(),
            new Class[] { SqlSession.class },
            new SqlSessionInterceptor()); 
            // 关键拦截器,重点在于这里SqlSessionInterceptor是一个InvocationHandler,主要在他的Invoke方法。
    }
}

invoke方法,如果是非事务的情况下,是直接关闭SqlSession。

java 复制代码
private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) {
        // 获取当前会话(事务内复用)
        SqlSession session = getSqlSession();
        try {
            Object result = method.invoke(session, args);
            // 非事务环境:立即关闭会话
            if (!isSqlSessionTransactional(session)) {
                session.close();
            }
            return result;
        } finally {
            // 事务结束时由事务管理器关闭
        }
    }
}

Mybatis-Spring的增删改查都会自动的去操作sqlSession,因为Invoke已经包装好了,而原生的Mybatis的是事务需要我们手动去操作,我们需要先拿到Session在拿到Mapper来做增删改操作,最后提交事务。

java 复制代码
SqlSession session = sqlSessionFactory.openSession();
try {
    UserMapper mapper = session.getMapper(UserMapper.class);
    
    // 执行操作(自动开启事务)
    mapper.insert(user);  这里去调用MapperProxy的invoke方法。
    
    // 需要显式提交
    session.commit();
} catch (Exception e) {
    // 显式回滚
    session.rollback();
} finally {
    // 显式关闭会话
    session.close();
}

结论

所以针对原生的Mybatis

无事务操作=》缓存有效

事务内操作 =》缓存有效

会话SqlSession的生命周期=》手动控制

缓存共享范围=》仅限于单个 SqlSession

MyBatis-Spring (SqlSessionTemplate)

无事务操作=》缓存无效(每次操作使用新会话)

事务内操作=》缓存有效(整个事务使用同一会话)

会话SqlSession的生命周期=》由 Spring事务管理器自动管理

缓存共享范围=》扩展至整个事务期间

举个例子:如果是Mybatis-Spring的框架下

情况 1:无事务环境(缓存无效)

java 复制代码
// 无事务方法
public User getUser(int id) {
    // 每次调用都会创建新会话
    User user1 = userMapper.findById(id); // 第一次查询数据库,因为这里通过代理对象执行操作,SqlSessionInterceptor的invoke在执行完就会自动关闭sqlSession,代理已经包装好了。
    User user2 = userMapper.findById(id); // 第二次查询数据库(未命中缓存)
    return user2;
}

原因:

SqlSessionInterceptor 在每次方法调用后都会关闭会话,相当于每次操作都是新的 SqlSession

情况 2:事务环境(缓存有效)

java 复制代码
@Transactional
public User getUserInTransaction(int id) {
    // 整个事务使用同一个会话
    User user1 = userMapper.findById(id); // 第一次查询数据库
    User user2 = userMapper.findById(id); // 从一级缓存获取
    return user2;
}

原因:

java 复制代码
事务开始时,SqlSessionInterceptor 创建会话并绑定到当前线程
事务内所有操作复用同一会话
事务结束后关闭会话

情况 3:更新操作清空缓存

java 复制代码
@Transactional
public void updateAndQuery() {
    User user1 = userMapper.findById(1); // 查询数据库
    userMapper.updateName(1, "Alice");   // 更新操作(清空缓存)
    User user2 = userMapper.findById(1); // 重新查询数据库
}

一级缓存使用建议

java 复制代码
事务粒度控制:
将需要缓存的操作放在同一事务中
避免过大的事务范围

性能敏感操作:

java 复制代码
@Transactional(readOnly = true) //表示当前事务是只读事务
public List<User> getActiveUsers() {
    // 多次查询相同数据可受益于缓存
}

更新后查询:

java 复制代码
@Transactional
public void updateUser(User user) {
    userMapper.update(user); // 清空缓存
    // 需要最新数据时直接查询
    User freshUser = userMapper.findById(user.getId());
}

思考:在一个方法内,谁会触发2次,Mapper的相同参数查询,这不是多余的查询吗?

相关推荐
num_killer7 小时前
小白的Langchain学习
java·python·学习·langchain
期待のcode8 小时前
Java虚拟机的运行模式
java·开发语言·jvm
程序员老徐8 小时前
Tomcat源码分析三(Tomcat请求源码分析)
java·tomcat
a程序小傲8 小时前
京东Java面试被问:动态规划的状态压缩和优化技巧
java·开发语言·mysql·算法·adb·postgresql·深度优先
仙俊红8 小时前
spring的IoC(控制反转)面试题
java·后端·spring
阿湯哥8 小时前
AgentScope Java 集成 Spring AI Alibaba Workflow 完整指南
java·人工智能·spring
小楼v8 小时前
说说常见的限流算法及如何使用Redisson实现多机限流
java·后端·redisson·限流算法
与遨游于天地9 小时前
NIO的三个组件解决三个问题
java·后端·nio
czlczl200209259 小时前
Guava Cache 原理与实战
java·后端·spring
yangminlei9 小时前
Spring 事务探秘:核心机制与应用场景解析
java·spring boot