mybatis – PerpetualCache 一级缓存

首页 / 新闻资讯 / 正文

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 的调用关系

mybatis - 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 对象