mybatis - PerpetualCache 一级缓存
mybatis 使用 cache 顺序
DefaultSqlSession --> CacheExecutor(二级缓存) --> BaseExecutor(PerpetualCache一级缓存) SimpleExecutor
一级缓存生命周期
//DefaultSqlSession.java
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
}
// BaseExecutor.java
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
protected Transaction transaction;
protected Executor wrapper;
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
protected PerpetualCache localCache;
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
protected int queryStack;
private boolean closed;
}
可以整理出 PerpetualCache 的调用关系
所以 一级缓存的生命周期是和 SqlSession 对象绑定在一起的,如果 sqlSession 不一样,是不会走缓存的
使用缓存例子
@Test
//@Transactional
public void cacheTest1() {
AccountModel accountModel = accountDao.selectByPrimaryKey(1);
System.out.println(accountModel);
AccountModel accountModel1 = accountDao.selectByPrimaryKey(1);
System.out.println(accountModel1);
}
如果不加 @Transactional 注解,是不会使用一级缓存的,也就是创建了两个不同的 SqlSession 对象, 那 @Transaction 是如何保证获取到的是同一个 SqlSession 对象呢?
开启事务为什么可以保证获取到的是同一个 SqlSession 对象
首先,spring 是通过 SqlsessionTemplate
创建 SqlSession 代理对象操作 mybatis 中的 SqlSession 对象
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
其中 SqlSessionInterceptor
是创建 代理 sqlSession 的过程
SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取 SqlSession 关键
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
在 getSqlSession
来创建 sqlSession
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
// 从 threadLocal 中获取 SessionHolder
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 从 SessionHolder 获取 sqlSession
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
// 将 SqlSessionHolder 与当前线程绑定
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
这里分三个步骤:
- 从 threadLocal 中获取 SessionHolder
- 从 SqlSessionHolder 获取 sqlSession, 不为空则返回
- 如果 sqlSession 为空, 创建新的 sqlSession , 并将 SqlSessionHolder 与当前线程绑定
**这样 从 SqlSessionHolder 中获取的 SqlSession 就是同一个对象了 **
从 threadLocal 中获取 SessionHolder
TransactionSynchronizationManager.getResource(sessionFactory)
/**
* Retrieve a resource for the given key that is bound to the current thread.
*/
@Nullable
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doGetResource(actualKey);
return value;
}
//从 threadLocal 中获取 SqlSessionHolder
private static Object doGetResource(Object actualKey) {
// private static final ThreadLocal<Map<Object, Object>>
// resources = new NamedThreadLocal<>("Transactional resources");
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
从 SessionHolder 获取 sqlSession
SqlSession session = sessionHolder(executorType, holder)
private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {
SqlSession session = null;
// 如果 holder 不为空
// 并且 holder 开启了事务
if (holder != null && holder.isSynchronizedWithTransaction()) {
if (holder.getExecutorType() != executorType) {
throw new TransientDataAccessResourceException(
"Cannot change the ExecutorType when there is an existing transaction");
}
holder.requested();
session = holder.getSqlSession();
}
return session;
}
从 SqlSessionHolder 获取到 同一个 SqlSession 的条件是:
- holder 不为空
- holder 开启了事务
如果 sqlSession 为空, 创建新的 sqlSession , 并将 SqlSessionHolder 与当前线程绑定
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
// 用SqlSession 构建SqlSessionHolder
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
// 将SqlSessionHolder与 当前线程绑定
// 通过ThreadLocal 传递
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager
.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
} else {
if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
LOGGER.debug(() -> "SqlSession [" + session
+ "] was not registered DataSource is not transactional");
} else {
throw new TransientDataAccessResourceException(
"SqlSessionFactory must be using a SpringManagedTransactionFactory ...");
}
}
} else {
LOGGER.debug(() -> "SqlSession [" + session);
}
}
- 用SqlSession 构建SqlSessionHolder
- 将SqlSessionHolder与 当前线程绑定, 通过ThreadLocal 传递
再回到刚才的问题开启事务为什么可以保证获取到的是同一个 SqlSession 对象?
spring 当且仅当在开启事务的场景下,通过 ThreadLocal 传递 同一个 SqlSession 对象