An overview,

Recently I have been hearing that the level 1 cache is not available after Spring and MyBtis integration!

I wonder why level 1 cache is not available. Is this a Spring BUG? This aroused my great interest, because Spring, as an excellent project management framework, actually has bugs, I want to find out, to satisfy my curiosity!

Two, really did not go cache

To help me view the source code, I integrated MyBatis with Spring and wrote the following code:

AnnotationConfigApplicationContext annotationConfigApplicationContext;
@Before
public void init(a){
    annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
}
 @Test public void selectTest(a){  TestMapper bean = annotationConfigApplicationContext.getBean(TestMapper.class);  List<User> users = bean.selectUser("Saturday");  System.out.println(users);  List<User> users1 = bean.selectUser("Saturday");  System.out.println(users == users1); } Copy the code

In a reasonable way, the above code in the normal environment, is bound to go to the level 1 cache, because it meets the level 1 cache matching conditions, namely the same SqlSession, the same StatementId, the same parameter, the same paging condition, the same query statement, the same environment name six matching rules, so theoretically, Level 1 cache will definitely hit! But the actual log is as follows:

image-20200707132052562

He actually did not go to the cache, but to query the database twice, the level 1 cache gorgeous invalid, but this reason why?

Three, the cause of failure

Spring as a top-level project management framework, for such an obvious BUG, it is impossible for him not to find, if really found not, then github users can not not mention the BUG, so, I interrupt debugging debugging, look at the source code is how to operate!

Where to start? As we said earlier, 2, 3, 4, 5, and 6 rules must be the same, because I just copied the query twice, the code did not change, so the query statement, parameters, and other conditions must be the same, so the most likely condition is the first: When Spring integrates with MyBatis, it uses a different SqlSession for each query. If you want to verify that a SqlSession is not the same as a SqlSession, you can use the same SqlSession as an Executor. If you want to verify that a SqlSession is not the same as a SqlSession, you can use the same SqlSession as an Executor. As an example, I can do the same thing with the BaseExecutor#query method as follows:

image-20200707133723572

As I expected, the two queries did not go to an executor at all, so it must not be a SqlSession, this is the only reason for the drop, but why?

Four, the culprit

image-20200707133958314

As you can see from the breakpoint in the figure above, our Mapper proxy normally contains the DefaultSqlSession object, but by integrating Spring we find that our SqlSession object has been replaced. Instead of SqlSessionTemplate, we go into this class:

public class SqlSessionTemplate implements SqlSession.DisposableBean {... }Copy the code

Select (); Select (); Select (); Select (); Select ();

@Override
public <E> List<E> selectList(String statement, Object parameter) {
    return this.sqlSessionProxy.selectList(statement, parameter);
}
Copy the code

We find that the internal queries within this method seem to be handed over to another layer of agents who actually perform the queries, and we seem to be close to finding out why:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
                                 PersistenceExceptionTranslator exceptionTranslator) {
. Ignore unnecessary code...    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class }, new SqlSessionInterceptor());
 } Copy the code

Sure enough, when this object is initialized, it initializes this proxy object along with it, which is implemented using the JDK’s dynamic proxy, and those of you who are familiar with dynamic proxies may know that the essence of JDK dynamic proxies is a subclass of InvocationHandler, SqlSessionInterceptor is the same as the SqlSessionInterceptor, so let’s go inside and see what it does:

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       / / get the SqlSession
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
 SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);  try {  // Reflection calls the real processing method  Object result = method.invoke(sqlSession, args);  if(! isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {  // Submit data  sqlSession.commit(true);  }  // Return the queried data  return result;  } catch (Throwable t) {  //... Ignore unnecessary code  } finally {  if(sqlSession ! =null) {  // Close the SqlSession connection  closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);  }  }  }  } Copy the code

GetSqlSession (getSqlSession) ¶ getSqlSession (getSqlSession); getSqlSession (getSqlSession);

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {
   / /... Ignore unnecessary code....
 // Get the handler of the current SqlSession from ThreadLocal
    SqlSessionHolder holder = 
 (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);  // Obtain a Session from SqlSessionHolder if the transaction synchronization manager is active  SqlSession session = sessionHolder(executorType, holder);  if(session ! =null) {  return session;  }   // If the SqlSession obtained from SqlSessionHolder is empty, create a SqlSession  session = sessionFactory.openSession(executorType);  // If the transaction synchronization manager is active, set the SqlSession to the SqlSessionHolder and save it for the next time  registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);  return session;  } Copy the code

Spring invalidates MyBatis level 1 cache. Spring invalidates MyBatis level 1 cache. Spring invalidates MyBatis level 1 cache only when Spring invalidates a transaction. SqlSessionInterceptor#invoke (); / / closeSqlSession ();

public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
   / /... Ignore unnecessary code
    
    SqlSessionHolder holder = 
          (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
 // Check whether the transaction synchronization manager has a session  if((holder ! =null) && (holder.getSqlSession() == session)) {  holder.released();  } else {  // Close the Session if it does not exist  session.close();  }  } Copy the code

So, now that we’ve found the culprit that caused the level 1 cache invalidation, what can we do about it?

5. Solutions

Why is level 1 cache invalid because the two queries do not use the same thing, so let’s add the same thing and see what happens:

@Test
    public void selectTest(a){
        TestMapper bean = annotationConfigApplicationContext.getBean(TestMapper.class);
        // Add the transaction
        DataSourceTransactionManager dataSourceTransactionManager = 
 annotationConfigApplicationContext.getBean(DataSourceTransactionManager.class);  TransactionStatus transaction =  dataSourceTransactionManager.getTransaction(new DefaultTransactionDefinition());    List<User> users = bean.selectUser("Saturday");  System.out.println(users);  List<User> users1 = bean.selectUser("Saturday");  System.out.println(users == users1);  } Copy the code

Let’s look at the results at this point:

image-20200707141456766

As expected, level 1 cache was successfully used again.

The ancients said: hearing is empty, seeing is believing! Only real experience, just know what is true, what is false! This debugging source code, not only let me have an overall understanding of Spring integration MyBatis, but also let me have a more in-depth understanding of dynamic proxy, I will sort it out later, share it!


If there is an error in the understanding of the article, you are welcome to talk to the bosses. Welcome to follow the author’s public account, progress together, learn together!

This article was typeset using MDNICE

– END –