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