English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Because I have been using Spring integrated with MyBatis all the time, so I rarely use MyBatis session cache. Habits are to use local cache with map or introduce a third-party local cache framework such as ehcache, Guava
So I'm a bit confused
Let's experiment first (Spring integration with MyBatis is omitted, there are many on the Internet), let's first look at the session cache at the MyBatis level
Enable printing SQL statements
Add to configuration.xml
<settings> <!-- Print the query statement --> <setting name="logImpl" value="STDOUT_LOGGING" /> </settings>
The test source code is as follows:
DAO class
/** * Testing why MyBatis cache is not working in Spring * * @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; } /** * one 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; /** * Spring Mybatis query without transaction turned on */ public void testSpringCashe() { //Executed two SQL statements testDao.selectBySpring("1; } /** * Spring Mybatis query with transaction turned on */ @Transactional public void testSpringCasheWithTran() { //After spring transaction is turned on, query1the next SQL testDao.selectBySpring("1; } /** * mybatis query */ public void testCash4Mybatise() { //Native mybatis, queried1the next SQL testDao.selectByMybatis("1; } }
Output result:
The testSpringCashe() method executed two SQL statements, while others were executed once
Source code trace:
First look at the sqlSession in mybatis
Trace back to the call to the query method of org.apache.ibatis.executor.BaseExecutor
try { queryStack++; list = resultHandler == null63; (List<E>) localCache.getObject(key) : null; //First retrieve from the cache if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); //Note that the key is CacheKey } list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); }
Here is the code to retrieve the cached data
private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) { if (ms.getStatementType() == StatementType.CALLABLE) { final Object cachedParameter = localOutputParameterCache.getObject(key);//Retrieve the cached object from localOutputParameterCache 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); } } } } }
It is found that it is from localOutputParameterCache, which is a PerpetualCache, and PerpetualCache maintains a map, which is the essence of session caching.
Focus can be paid to the following two accumulation logic
PerpetualCache, two parameters, id and map
CacheKey, the key stored in the map, it has an overridden equas method, which is called when the cache is accessed.
The disadvantages of this local map cache object acquisition, based on my experience of falling into traps (I also used map to implement local caching before), is that the acquired objects are not cloned, and the two returned objects are the same address
In spring, it is usually used with sqlSessionTemplate, 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>
The sessions for executing SQL in SqlSessionTemplate are all 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 is a proxy class generated by the JDK dynamic proxy method, the main logic is intercepted before and after the execution of the method in the InvocationHandler, the main logic is in the invoke method, including the creation, common, and closing of sqlsession for each execution
The code is as follows:
private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // a new sqlSession is created before each execution SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { // execute 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; } Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { // Release the connection to avoid a deadlock if the translator is not 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; } if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
Since it is created every time, the cache of sqlSession is not used.
Why can it be used with a transaction that has been enabled, follow the getSqlSession method
The following is:
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, retrieve the session from the SqlSessionHolder 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; }
A SqlSessionHolder is maintained inside, which is associated with the transaction and session. If it exists, it will be directly retrieved, otherwise, a new session will be created. Therefore, in the transaction, each session is the same, so the cache can be used.
The following is the analysis of the reasons why session cache is not used after integrating Spring with MyBatis, which I introduced to everyone. I hope it will be helpful to everyone. If you have any questions, please leave a message, and I will reply to everyone in time. I am also very grateful for everyone's support of the Yell Tutorial website!