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的相同参数查询,这不是多余的查询吗?

相关推荐
云原生指北1 小时前
GitHub Copilot SDK 入门:五分钟构建你的第一个 AI Agent
java
Leinwin6 小时前
OpenClaw 多 Agent 协作框架的并发限制与企业化规避方案痛点直击
java·运维·数据库
薛定谔的悦6 小时前
MQTT通信协议业务层实现的完整开发流程
java·后端·mqtt·struts
enjoy嚣士6 小时前
springboot之Exel工具类
java·spring boot·后端·easyexcel·excel工具类
罗超驿6 小时前
独立实现双向链表_LinkedList
java·数据结构·链表·linkedlist
盐水冰7 小时前
【烘焙坊项目】后端搭建(12) - 订单状态定时处理,来单提醒和顾客催单
java·后端·学习
凸头7 小时前
CompletableFuture 与 Future 对比与实战示例
java·开发语言
wuqingshun3141597 小时前
线程安全需要保证几个基本特征
java·开发语言·jvm
努力也学不会java8 小时前
【缓存算法】一篇文章带你彻底搞懂面试高频题LRU/LFU
java·数据结构·人工智能·算法·缓存·面试
攒了一袋星辰8 小时前
高并发强一致性顺序号生成系统 -- SequenceGenerator
java·数据库·mysql