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类的实例对象缓存的数据区域是互不影响的。一级缓存工作原理图:
二级缓存是 Mapper 级别的缓存,多个 SqlSession 实例对象可以共用二级缓存,二级缓存是跨 SqlSession 的。 二级缓存模式图如下:
我们都知道一级缓存是 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来执行方法。