• 实战讲解MyBatis缓存:一级缓存和二级缓存(图+文+源码)


    1 缘起

    回顾SpringBoot如何进行事务管理相关知识的时,
    发现使用Spring的注解@Transational即可实现事务管理,完成回滚操作,
    然而SpringBoot中使用MyBatis这个ORM框架操作数据库,实现CURD,
    这两者有什么关系呢?
    研究一番之后,发现,@Transactional注解于MyBatis是有关联的,
    并且不单单对事务起作用,同样对MyBatis一级缓存起作用,
    于是,开始研究MyBatis的缓存,分享如在,
    帮助读者轻松应对知识考核与交流。

    2 MyBatis缓存

    MyBatis提供两种缓存:一级缓存和二级缓存
    其中,生效条件:

    序号缓存级别生效条件
    1一级缓存基于同一个SqlSession
    2二级缓存基于同一个namespace

    2.1 一级缓存:L1 Cache

    MyBatis一级缓存结构如下图所示。由图可知,对于同一个SqlSession,
    一级缓存生效。
    MyBatis在SqlSession层次的数据查询顺序:L1级缓存->数据库
    第一次创建或者开启SqlSession时,一级缓存没有数据,
    缓存命中失败,查询请求会直接向数据发起,
    查到数据后,数据会保存到一级缓存,
    同一个SqlSession在相同查询条件下,会直接使用L1级缓存的数据,
    提高了查询性能,但是,会出现脏数据。分布式条件下或者直接操作数据库,其他SqlSession变更了数据,此时,查到的数据仍是L1级缓存的数据。

    在这里插入图片描述

    2.1.1 L1级缓存生效

    操作添加@Transactional事务注解,保证当前事务共用一套SqlSession,
    从而达到L1级缓存生效的条件。

        @Override
        @Transactional(rollbackFor = Exception.class)
        public BaseUserVO queryUserById(long id) {
            BaseUserVO baseUserVO = userDAO.queryUserById(id);
            BaseUserVO baseUserVO1 = userDAO.queryUserById(id);
            BaseUserVO baseUserVO2 = userDAO.queryUserById(id);
            return Optional.ofNullable(baseUserVO).orElse(new BaseUserVO());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    同一个事务中的SqlSession是公用的,所以,保证了SqlSession一致性,
    相同查询条件下,只要L1级缓存存在符合条件的数据,会直接使用。
    测试结果日志信息如下图所示,由图可知,第一次查询,L1级缓存命中失败,
    直接向数据库发起查询,查询语句如日志所示。由日志信息可知,SqlSession是同一个,地址为:37444756。
    在这里插入图片描述
    当前事务中查询语句执行结束后,SqlSession执行commit并取消注册,最后关闭SqlSession。
    执行过程日志如下图所示。
    在这里插入图片描述

    2.1.2 源码分析:为什么添加@Transactional注解使L1级缓存生效

    首先进入SqlSessionTemplate即SqlSession模板配置,用于构建SqlSession,交给Spring容器管理。源码如下图所示,
    需要关注的是SqlSessionInterceptor,SqlSession拦截器。
    位置:org.mybatis.spring.SqlSessionTemplate#SqlSessionTemplate(org.apache.ibatis.session.SqlSessionFactory, org.apache.ibatis.session.ExecutorType, org.springframework.dao.support.PersistenceExceptionTranslator)
    在这里插入图片描述
    SqlSession拦截器源码如下图所示,为MyBatis执行的CURD方法分配合适的SqlSession。在这个代理方法中,有两个部分需要关注:
    (1)getSqlSession:获取SqlSession,这就是从事务中获取SqlSession,同一个事务的SqlSession是一致的,保证L1级缓存生效;
    (2)sqlSession.commit:这一点是解释为什么不添加事务无法启用L1级缓存,因为,不启用事务,SqlSession就不是同一个,会触发SqlSession强制commit,细心的读者会发现,源码中(如下图)在sqlSession.commit上面有两行注释,解释了不使用事务无法使L1级缓存生效。
    位置:org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor

    在这里插入图片描述
    再来看一下getSqlSession,源码如下图所示。
    由图可知,从事务管理器中获取SqlSessionHolder,然后获取执行的SqlSession,
    如果在同一个事务中,则直接复用SqlSession,满足L1级缓存生效的条件。
    位置:org.mybatis.spring.SqlSessionUtils#getSqlSession(org.apache.ibatis.session.SqlSessionFactory, org.apache.ibatis.session.ExecutorType, org.springframework.dao.support.PersistenceExceptionTranslator)
    在这里插入图片描述

    2.2 二级缓存:L2 Cache

    二级缓存相对于一级缓存作用域更加宽泛,无需保证同一个SqlSession,只要是同一个namespace即可,即同一份映射文件:*.xml,
    L2级缓存查询过程示意图如下图图所示。
    由图可知,
    在这里插入图片描述

    2.2.1 L2级缓存生效

    在映射的xml文件中添加

    <cache>cache>
    
    • 1

    完整映射文件:

    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.monkey.standalone.modules.user.dao.UserDAO">
        <cache>cache>
        <select id="queryUserById" resultType="com.monkey.standalone.modules.user.vo.BaseUserVO">
            SELECT
                id id,
                user_id,
                username,
                sex
            FROM
                tb_user
            WHERE
                id = #{id};
        select>
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    执行查询:

        @Override
        public BaseUserVO queryUserById(long id) {
            BaseUserVO baseUserVO = userDAO.queryUserById(id);
            return Optional.ofNullable(baseUserVO).orElse(new BaseUserVO());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    查询结果如下图所示。
    由图可知,每次查询过程都会给出L2级缓存命中率,
    图中标注了计算方式,以及每次查询的过程,
    第一次查询时,L2级缓存没有数据,直接通过数据库查询,缓存命中率为0,
    第二次查询,在同一个namespace中,走L2级缓存,命中率50%,1/2,
    依次类推。
    在这里插入图片描述

    2.2.2 源码分析:为什么添加使L2级缓存生效

    L2级缓存是基于映射文件的namespace生效的,
    因此,首先进入映射文件元素配置,configurationElement,源码如下图所示,
    由图可知,通过builderAssistant构建当前namespace,同时,配置cache使L2级缓存生效:cacheElement,接下来进入该方法,探究如何使L2级缓存生效。
    位置:org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
    在这里插入图片描述
    L2级缓存生效的方法cacheElement源码如下图所示,
    由图可知,通过builderAssistant.useNewCache配置同一namespace使用L2级缓存。
    位置:org.apache.ibatis.builder.xml.XMLMapperBuilder#cacheElement
    在这里插入图片描述
    配置namespace使用L2级缓存源码如下图所示,
    由图可知,通过currentNamespace构建缓存,并添加到配置中心configuration。
    位置:org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache
    在这里插入图片描述
    接下来进入configuration一探究竟,
    confituration中的newExecutor源码如下图所示,构建执行器,
    由图可知,当缓存开启时,构建缓存执行器,每次查询先从缓存遍历数据,
    然后再看查询的策略。
    位置:org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
    在这里插入图片描述
    最终的查询方法源码如下图所示,
    由图可知,执行查询时,先从L2级缓存获取数据,如果命中数据,则直接返回,
    若未命中数据,则进一步通过L1级缓存或数据库获取数据。
    位置:org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
    在这里插入图片描述

    3 小结

    (1)MyBatis一级缓存生效的条件为同一个SqlSession;二级缓存生效的条件为同一个namespace;
    (2)一级缓存生效需要使用@Transactional注解,保证同一事务中的操作在同一个SqlSesion中,因为,MyBatis在设计时,不在同一事务中SqlSession会强制自动提交(commit),每次操作都会新建SqlSession,这样就无法保证所有操作在同一个SqlSession中,破坏了L1级缓存的生效条件;
    (3)二级缓存生效的范围是同一份映射文件,即同一个namespace,映射文件配置是,会读取cache属性,当存在cache属性时,开启二级缓存,新建缓存执行器,该执行器保证,每次查询数据时,都会先从二级缓存查询;
    (4)MyBatis使用缓存机制会出现脏数据问题,即分布式架构/体系中,不同的SqlSession或者namespace对数据变更后,其他的服务仍旧会从缓存中获取数据,导致数据更新不及时,出现脏数据。

  • 相关阅读:
    【AntDesign】封装全局异常处理-全局拦截器
    被锁总时间
    哪款半入耳式蓝牙耳机音质好?音质比较好的半入耳式蓝牙耳机推荐
    设计模式——中介者模式
    第4章 前馈神经网络
    60、回溯-单词搜索
    防抖和节流有什么区别,分别用于什么场景?
    C++数据结构:并查集
    Java函数式编程Lambda和Stream流
    提升系统性能之Future模式
  • 原文地址:https://blog.csdn.net/Xin_101/article/details/127982417