Mybatis - Spring整合后一级缓存失效了

这篇博客探讨了在Spring整合Mybatis时一级缓存失效的问题。作者通过分析源码发现,Spring使用SqlSessionTemplate并通过动态代理创建SqlSession,每次请求都会创建新的SqlSession并关闭,导致缓存无法共享。解决方案是在需要的地方添加事务注解,确保SqlSession在事务范围内,从而启用一级缓存。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

我们都知道Mybatis默认开启了一级缓存,二级缓存需要手动开启。我自己最近也在复习Mybatis相关的知识。但是在写的时候,发现一级缓存并没有生效,因此特此记录。

首先可以看这篇文章Mybatis - 预编译的运用和原理搭建一个项目。

一. 一级缓存的失效

我们对Controller层的代码做出更改,就是调用一次接口,我们会调用数据库两次:

@PostMapping("/hello")
public User hello(){
    userProcess.getUser("tom");
    return userProcess.getUser("tom");
}

调用一次接口之后,控制台输出:
在这里插入图片描述
可以发现,这里依旧是执行了两次SQL,可见一级缓存并没有生效,那是咋回事呐?

1.1 为什么一级缓存失效了?

首先我们应该知道的一点是,Mybatis的一级缓存是通过SqlSession来实现的。那么SqlSessionMybatis中是个什么样的存在呢?他是一个接口:

public interface SqlSession extends Closeable {}

对应的实现子类有:
在这里插入图片描述
同时Mybatis官网写到这么一句话:
在这里插入图片描述

翻译一下就是:

  • MyBatis中,使用sqlsessionFactory创建一个sqlsession。一旦有了会话,就可以使用它来执行Mapper的映射语句、提交或回滚连接,最后,当不再需要时,关闭会话。
  • 使用mybatis-spring,就不需要直接使用sqlsessionFactory,因为可以向bean注入一个线程安全的sqlsession它根据spring的事务配置自动提交、回滚和关闭会话。

Mybatis中如果使用了一级缓存,默认的实现是DefaultSqlSession。但是结合上面的文档来看,在Spring整合Mybatis的时候,就会有另一套逻辑。这里的具体实现是SqlSessionTemplate

也就是说,当Spring-Mybatis整合的时候,Spring对SqlSession的使用是通过SqlSessionTemplate来控制的。我们看下SqlSessionTemplate这个类的构造函数。

public class SqlSessionTemplate implements SqlSession, DisposableBean {
	private final SqlSessionFactory sqlSessionFactory;	
	private final ExecutorType executorType;
	private final SqlSession sqlSessionProxy;	
	private final PersistenceExceptionTranslator exceptionTranslator;
  
	public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
	   PersistenceExceptionTranslator exceptionTranslator) {
	
	 notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
	 notNull(executorType, "Property 'executorType' is required");
	
	 this.sqlSessionFactory = sqlSessionFactory;
	 this.executorType = executorType;
	 this.exceptionTranslator = exceptionTranslator;
	 // 关键在于这里
	 this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
	     new Class[] { SqlSession.class }, new SqlSessionInterceptor());
	}

我们可以看到SqlSession这个实例是通过JDK的动态代理来创建出的代理对象。 动态代理的相关文章如果想看的可以看这里:

既然他是一个代理对象了,那么我们需要关注的逻辑肯定是它代理的逻辑喽,那就得看SqlSessionInterceptor这个拦截器里面加了啥操作。

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 1.创建一个SqlSession
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        // 2.调用原始函数
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // 如果不是事务,关闭当前的SqlSession
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        // ...catch
      } finally {
        // 关闭SqlSession
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

1.2 一级缓存失效的原因总结

总结下就是:

  1. 创建一个SqlSession
  2. 如果当前的函数没有事务声明,则提交当前事务。
  3. 关闭当前的SqlSession

因此结合本文的案例来看就是:

  1. 程序执行了多次函数,调用相同的SQLMybatis默认开启了一级缓存,因此理论上来看,应该只有第一次是走了SQL。后续都是走缓存才对。但是实际上却每次都执行了SQL
  2. Mybatis的一级缓存是通过SqlSession来实现的,对应的实现是DefaultSqlSession。(package org.apache.ibatis.session.defaults;
  3. 因为Spring管理了Mybatis,因此这个时候SqlSession的实现被替换成了SqlSessionTemplate。(package org.mybatis.spring;)注意观察包名。
  4. SqlSessionTemplate中主要是通过JDK动态代理创建出了一个SqlSession代理对象。增强的逻辑如上。
  5. 因此每执行一次函数,哪怕SQL相同,Spring都会创建一个新的SqlSession,并且在执行完毕之后,还会将事务提交、关闭SqlSession。因此多次请求之间无法通过SqlSession来共享缓存。
  6. 从而造成了多次执行,多次调用SQL、缓存失效的现象。

二. 问题解决

从源码上分析来看,想要Spring-Mybatis项目中实现一级缓存,那么就得通过事务声明来完成。我们可以对Controller代码这么更改(这只是案例,实际项目中建议不要在Controller层添加事务声明,粒度太大!):

@PostMapping("/hello")
@Transactional
public User hello(){
    userProcess.getUser("tom");
    return userProcess.getUser("tom");
}

再次访问,可以见到第一次调用的时候,执行了SQL,后续执行了缓存。
在这里插入图片描述
并且从输出结果上来看,也吻合源码的操作,我们给对应的代码块添加了事务的声明,因此这里在判断的时候,就会返回true。因此不会讲当前的SqlSession提交。那么因此SqlSession能做到共享(没有被提交)。
在这里插入图片描述

到这里,一级缓存失效的问题也就解决啦。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

第七个香粽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值