Analysis of reasons why spring cannot use session cache after mybatis integration

  • 2020-06-07 04:31:38
  • OfStack

The session cache of mybatis is rarely used because spring is integrated with mybatis. The convention is that the local cache writes itself in map or introduces the third party local cache framework ehcache, Guava

So bring it out and tangle with it

Under the experiment (spring integration mybatis omitted, network 1 heap), first look at mybatis level session cache

Print the sql statement

configuration xml to join


<settings>
    <!--  Print query statement  -->
    <setting name="logImpl" value="STDOUT_LOGGING" />
  </settings>

The test source code is as follows:

dao class


/**
 *  test spring In the mybatis Why not use cache 
 *
 * @author  He Jinbin  2017.02.15
 */
@Component
public class TestDao {
  private Logger logger = Logger.getLogger(TestDao.class.getName());
  @Autowired
  private SqlSessionTemplate sqlSessionTemplate;
  @Autowired
  private SqlSessionFactory sqlSessionFactory;
  /**
   *  two SQL
   *
   * @param id
   * @return
   */
  public TestDto selectBySpring(String id) {
    TestDto testDto = (TestDto) sqlSessionTemplate.selectOne("com.hejb.TestDto.selectByPrimaryKey", id);
    testDto = (TestDto) sqlSessionTemplate.selectOne("com.hejb.TestDto.selectByPrimaryKey", id);
    return testDto;
  }
  /**
   * 1 time SQL
   *
   * @param id
   * @return
   */
  public TestDto selectByMybatis(String id) {
    SqlSession session = sqlSessionFactory.openSession();
    TestDto testDto = session.selectOne("com.hejb.TestDto.selectByPrimaryKey", id);
    testDto = session.selectOne("com.hejb.TestDto.selectByPrimaryKey", id);
    return testDto;
  }
}

Test service class


@Component
public class TestService {
  @Autowired
  private TestDao testDao;
  /**
   *  Not opening a transaction spring Mybatis The query 
   */
  public void testSpringCashe() {
    // I checked it twice SQL
    testDao.selectBySpring("1");
  }
  /**
   *  Transaction-Open spring Mybatis The query 
   */
  @Transactional
  public void testSpringCasheWithTran() {
    //spring After opening the transaction, the query 1 time SQL
    testDao.selectBySpring("1");
  }
  /**
   * mybatis The query 
   */
  public void testCash4Mybatise() {
    // The original ecological mybatis The query, 1 time SQL
    testDao.selectByMybatis("1");
  }
}

Output results:

The testSpringCashe() method executes SQL twice and the others once

Source tracking:

So let's look at sqlSession in mybatis

Tracking at the end of the call to org. apache. ibatis. executor. BaseExecutor query method


try {
   queryStack++;
   list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; // I'm going to take it from the cache 
   if (list != null) {
    handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); // Notice the inside key is CacheKey
   } else {
    list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
   }

How to retrieve cached data


private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
  if (ms.getStatementType() == StatementType.CALLABLE) {
   final Object cachedParameter = localOutputParameterCache.getObject(key);// from localOutputParameterCache Fetch the cache object 
   if (cachedParameter != null && parameter != null) {
    final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
    final MetaObject metaParameter = configuration.newMetaObject(parameter);
    for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
     if (parameterMapping.getMode() != ParameterMode.IN) {
      final String parameterName = parameterMapping.getProperty();
      final Object cachedValue = metaCachedParameter.getValue(parameterName);
      metaParameter.setValue(parameterName, cachedValue);
     }
    }
   }
  }
 }


One PerpetualCache is found from localOutputParameterCache, and PerpetualCache maintains map, which is the caching nature of session.

The focus can be on the following two tired logics

PerpetualCache, two parameters, id and map

CacheKey, key stored in map, which overrides the equas method, called when the cache is fetched.

The downside of this local map cache fetch, in my experience (I've implemented local cache with map before), is that the fetch is non-ES87en, and both objects are returned with 1 address

In spring, sqlSessionTemplate is generally used, as follows


<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:configuration.xml" />
    <property name="mapperLocations">
      <list>
        <value>classpath*:com/hejb/sqlmap/*.xml</value>
      </list>
    </property>
  </bean>
  <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg ref="sqlSessionFactory" />
  </bean>

session to perform SQL in SqlSessionTemplate is through sqlSessionProxy, the generation of sqlSessionProxy is assigned in the constructor, as follows:


this.sqlSessionProxy = (SqlSession) newProxyInstance(
    SqlSessionFactory.class.getClassLoader(),
    new Class[] { SqlSession.class },
    new SqlSessionInterceptor());

sqlSessionProxy generates a proxy class through the dynamic proxy method of JDK. The main logic in ES1010EN intercepts the methods executed before and after. The main logic in invoke covers the creation of sqlsesstion, common, and closes each execution

The code is as follows:


private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   //  It is created before each execution 1 A new one sqlSession
   SqlSession sqlSession = getSqlSession(
     SqlSessionTemplate.this.sqlSessionFactory,
     SqlSessionTemplate.this.executorType,
     SqlSessionTemplate.this.exceptionTranslator);
   try {
   //  Execution method 
    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);
    }
   }
  }
 }

The sqlSession cache is not used because it is created each time.

Why would you use it if you have a transaction open, so let's go to getSqlSession

As follows:


public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
  notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
  notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
  //  First of all, from the SqlSessionHolder out session
  SqlSession session = sessionHolder(executorType, holder);
  if (session != null) {
   return session;
  }
  if (LOGGER.isDebugEnabled()) {
   LOGGER.debug("Creating a new SqlSession");
  }
  session = sessionFactory.openSession(executorType);
  registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
  return session;
 }

There is an SqlSessionHolder maintained, which is associated with the transaction and session. If it exists, it will be fetched directly; otherwise, a new session will be created. Therefore, each session has the same one in the transaction, so it can use the cache


Related articles: