mybatis – PerpetualCache 一级缓存

2023年8月7日12:05:20

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

  • 作者:iiaythi
  • 原文链接:https://blog.csdn.net/u013887008/article/details/120459596
    更新时间:2023年8月7日12:05:20 ,共 6823 字。