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


    前言

    我们都知道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能做到共享(没有被提交)。
    在这里插入图片描述

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

  • 相关阅读:
    rysnc 通过文件输入密码的设置方法
    浏览器IndexedDB模块损坏及解决办法
    卷积神经网络手写字符识别 - 深度学习 计算机竞赛
    计算机毕业设计Java银枫家政服务管理系统(系统+程序+mysql数据库+Lw文档)
    【Python 零基础入门】 Numpy
    Bootstrap警告和轮播插件详解【前端Bootstrap框架】
    代码报错:There‘s no Qt version assigned to project Project.vcxproj
    JAVA面试八股整理——基础部分
    【OpenCV 例程300篇】249. 图像的重映射(cv2.remap)
    vite+vue3+electron开发环境搭建
  • 原文地址:https://blog.csdn.net/Zong_0915/article/details/127125010