一般来说,可以在5个方面进行缓存的设计:
- 最底层可以配置的是mysql自带的query cache,
- mybatis的一级缓存,默认情况下都处于开启状态,只能使用自带的PerpetualCache,无法配置第三方缓存
- mybatis的二级缓存,可以配置开关状态,默认使用自带的PerpetualCache,但功能比较弱,能够配置第三方缓存,
- service层的缓存配置,结合spring,可以灵活进行选择
- 针对实际业务情况,直接缓存部分html页面,直接返回给客户端。
今天要分享的问题则是Mybatis整合spring一级缓存的原因。
案例
场景一: 未开启事务
@Service("countryService")
public class CountryService {
@Autowired
private CountryDao countryDao;
// @Transactional 未开启事物
public void noTranSactionMethod() throws JsonProcessingException {
CountryDo countryDo = countryDao.getById(1L);
CountryDo countryDo1 = countryDao.getById(1L);
}
}
结果:
[DEBUG] SqlSessionUtils Creating a new SqlSession
[DEBUG] SpringManagedTransaction JDBC Connection [com.mysql.jdbc.JDBC4Connection@14a54ef6] will not be managed by Spring
[DEBUG] getById ==> Preparing: SELECT * FROM country WHERE country_id = ?
[DEBUG] getById ==> Parameters: 1(Long)
[DEBUG] getById <== Total: 1
[DEBUG] SqlSessionUtils Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3359c978]
[DEBUG] SqlSessionUtils Creating a new SqlSession
[DEBUG] SqlSessionUtils SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2aa27288] was not registered for synchronization because synchronization is not active
[DEBUG] SpringManagedTransaction JDBC Connection [com.mysql.jdbc.JDBC4Connection@14a54ef6] will not be managed by Spring
[DEBUG] getById ==> Preparing: SELECT * FROM country WHERE country_id = ?
[DEBUG] getById ==> Parameters: 1(Long)
[DEBUG] getById <== Total: 1
[DEBUG] SqlSessionUtils Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2aa27288]
可以看到,两次查询,都创建了新的sqlSession,并向数据库查询,此时缓存并没有起效果
场景二: 开启事务
使用spring提供的 @Transactional
注解:
@Service("countryService")
public class CountryService {
@Autowired
private CountryDao countryDao;
@Transactional
public void noTranSactionMethod() throws JsonProcessingException {
CountryDo countryDo = countryDao.getById(1L);
CountryDo countryDo1 = countryDao.getById(1L);
}
}
结果:
[DEBUG] SqlSessionUtils Creating a new SqlSession
[DEBUG] SqlSessionUtils Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@109f5dd8]
[DEBUG] SpringManagedTransaction JDBC Connection [com.mysql.jdbc.JDBC4Connection@55caeb35] will be managed by Spring
[DEBUG] getById ==> Preparing: SELECT * FROM country WHERE country_id = ?
[DEBUG] getById ==> Parameters: 1(Long)
[DEBUG] getById <== Total: 1
[DEBUG] SqlSessionUtils Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@109f5dd8]
// 从当前事物中获取sqlSession
[DEBUG] SqlSessionUtils Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@109f5dd8] from current transaction
[DEBUG] SqlSessionUtils Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@109f5dd8]
可以看到,两次查询,只创建了一次sqlSession,说明一级缓存起作用了
源码
SqlSessionInterceptor
, SqlSessionInterceptor
是 SqlSessionTemplate
的内部类:
public class SqlSessionTemplate implements SqlSession, DisposableBean {
// ...omit..
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
//如果尚未开启事物(事物不是由spring来管理),则sqlSession直接提交
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
// 手动commit
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 {
//一般情况下,默认都是关闭sqlSession
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
}
再看getSqlSession
方法,这个方法是在SqlSessionUtils.java
中的:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
//获取holder
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
//从sessionHolder中获取SqlSession
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
//如果sqlSession不存在,则创建一个新的
session = sessionFactory.openSession(executorType);
//将sqlSession注册在sessionHolder中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
//在开启事物的情况下
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
//由spring来管理事物的情况下
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
}
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
//将sessionFactory绑定在sessionHolde相互绑定
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
} else {
if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
}
} else {
throw new TransientDataAccessResourceException(
"SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
}
}
}
从源码中我们可以看出spring通过mybatis调用数据库的过程如下:
-
创建
SqlSessionTemplate
对象 -
使用
SqlSessionTemplate
获取sqlsession
并代理,执行查询- 没有使用事务:创建一个
sqlsession
- 使用事务:
template从threadlocal
获取到sqlsession
,没有获取到则创建一个sqlsession
,并将sqlsession
与当前线程绑定,放入threadlocal
里面
- 没有使用事务:创建一个
-
查询结束
- 没有使用事务:释放资源
- 使用事务:线程结束释放资源
结论: 通过以上步骤后发现
- 如果没有使用事务,在同一线程里面两次查询同一数据所使用的sqlsession是不相同的,所以,给人的印象就是结合spring后,mybatis的一级缓存失效了。
- 如果使用了事务,在同一线程里面两次查询同一数据所使用的是同一个sqlsession,所以可以使用 mybatis 的一级缓存。