Spring整合Mybatis一级缓存失效?

一般来说,可以在5个方面进行缓存的设计:

  1. 最底层可以配置的是mysql自带的query cache,
  2. mybatis的一级缓存,默认情况下都处于开启状态,只能使用自带的PerpetualCache,无法配置第三方缓存
  3. mybatis的二级缓存,可以配置开关状态,默认使用自带的PerpetualCache,但功能比较弱,能够配置第三方缓存,
  4. service层的缓存配置,结合spring,可以灵活进行选择
  5. 针对实际业务情况,直接缓存部分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 , SqlSessionInterceptorSqlSessionTemplate 的内部类:

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调用数据库的过程如下:

  1. 创建 SqlSessionTemplate 对象

  2. 使用 SqlSessionTemplate 获取 sqlsession 并代理,执行查询

    • 没有使用事务:创建一个 sqlsession
    • 使用事务: template从threadlocal 获取到 sqlsession,没有获取到则创建一个 sqlsession ,并将 sqlsession 与当前线程绑定,放入 threadlocal 里面
  3. 查询结束

    • 没有使用事务:释放资源
    • 使用事务:线程结束释放资源

结论: 通过以上步骤后发现

  • 如果没有使用事务,在同一线程里面两次查询同一数据所使用的sqlsession是不相同的,所以,给人的印象就是结合spring后,mybatis的一级缓存失效了。
  • 如果使用了事务,在同一线程里面两次查询同一数据所使用的是同一个sqlsession,所以可以使用 mybatis 的一级缓存。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值