Mybatis 的 SqlSession 和一级缓存为什么失效?

SqlSession是什么

SqlSession是Mybatis 中定义的,用来表示与关系数据库的一次会话,会话定义了各种具体的操作,查询、数据更新(包含保存、更新、删除)操作。而这些操作都在与数据库建立会话的基础上进行的。SqlSession 可以看作是对Connection 更加高级的抽象,从其方法上更加可以看出他具有更加明显的操作特征。

SqlSession分类

mybatis的SqlSession有三种:DefaultSqlSession、SqlSessionManager、SqlSessionTemplate,前两者是 mybtais 默认情况下使用的,第三种主要用到 mybatis 和 spring 整合的时候。

SqlSession的创建

SqlSessionFactory.openSession() 的方法源码

// org/apache/ibatis/session/defaults/DefaultSqlSessionFactory.java
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        final Environment environment = configuration.getEnvironment();
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        final Executor executor = configuration.newExecutor(tx, execType);
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

通过源码我们知道每次 SqlSession(准确地说是 DefaultSqlSession )的创建都会有一个 Transaction 事务对象 的生成。也就是说:

  • 一个事务 Transaction 对象与一个 SqlSession 对象是一对一的关系

  • 同一个 SqlSession 不管执行多少次数据库操作,只要没有执行 close,那么,整个操作都是在同一个 Transaction 中执行的

但需要注意的是,我们整合Spring之后用到的其实都是 SqlSessionTemplate ,与这里的 DefaultSqlSession 不是同一个SqlSession对象,不懂的看上面的。

为什么和 Spring 整合后的 SqlSession 一级缓存偶尔会失效

一级缓存和二级缓存

一级缓存是SqlSession级别的缓存,在操作数据库时,每个SqlSession类的实例对象缓存的数据区域(Map)可以用于存储缓存数据,不同的SqlSession类的实例对象缓存的数据区域是互不影响的。一级缓存工作原理图:

image

二级缓存是 Mapper 级别的缓存,多个 SqlSession 实例对象可以共用二级缓存,二级缓存是跨 SqlSession 的。 二级缓存模式图如下:

image

我们都知道一级缓存是 SqlSession 级别的缓存,那么,一级缓存失效,肯定是因为 SqlSession 不一致,那么,我们进入到 getSqlSession 方法中:

// org/mybatis/spring/SqlSessionUtils.java
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    // ...
    // 从ThreadLocal变量里面获取到Spring的事务同步管理器
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    // 调用静态方法 sessionHoler 判断是否存在符合要求的sqlSession
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }
    // 如果 SqlSessionHolder 中获取的 SqlSession 为空,则新建一个 SqlSession
    session = sessionFactory.openSession(executorType);
    // 判断当前是否存在事务,将sqlSession 绑定到 sqlSessionHolder 中,并放到 threadLoacl 当中
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
    return session;
}

可以看出,Spring 只有在开启了事务之后,在同一个事务里的 SqlSession 会被缓存起来,同一个事务中,多次查询是可以命中缓存的。

总结

  • 同一事务中不管调用多少次 mapper 里的方法 ,最终,都是用的同一个 sqlSession,即一个事务中使用的是同一个sqlSession

  • 同一事务中,Mybatis 的一级缓存才会有效

  • 如果没有开启事务,调用一次 mapper 里的方法将会新建一个sqlSession来执行方法


posted @ 2024-02-05 11:33  LARRY1024  阅读(28)  评论(0编辑  收藏  举报