mybatis06_mybatis缓存

发布时间 2023-03-23 15:52:40作者: Purearc

MyBatis缓存的概念

​ 它用来优化 SQL 数据库查询的,但是可能会产生脏数据。

​ 一级缓存是存在于 SqlSession 中的,而 SqlSession 就是操作数据库的一个会话对象。在 SqlSession 对象中实际使用了一个 HashMap 的数据结构用于存储缓存数据,不同的 SqlSession 之间的缓存数据互不影响。

​ 二级缓存是 mapper 级别的缓存,当多个 SqlSession 操作同一个 mapper.xml 配置中的 SQL 语句时,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession的。

image-20230323090215692

一、一级缓存

​ Mybatis默认开启。一级缓存的作用域在同一个 SqlSession 中,若同一个 SqlSession 执行两次相同的 Sql 语句,第一次执行完毕后会将数据放入缓存,第二次直接在缓存中取。当SqlSession结束,其中的缓存也消失。

image-20230323090532743

​ 我们随便找一个例子测试

​ 在这个案例中我们再写一个 findById 来测试一下,比较简单,这里就只给关键部分和结果解析。

    private static void testOneLeverCache() {
        SqlSession sqlSession = null;
        try {
            sqlSession = MybatisUtil.getSqlSession();
            EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
            Emp emp = empMapper.findByID(11L);
            System.out.println(emp);
            //在这儿开启事务提交
//            sqlSession.commit();
            System.out.println("__________");
            Emp emp2 = empMapper.findByID(11L);
            System.out.println(emp);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {sqlSession.close();}
        }
    }

⬇️执行了一次 sql 语句的 preparing

image-20230323092543287

​ 如果 SqlSession 执行 insert、update、delete 等操作 commit 提交后会清空一级缓存,这样做的目的是为了让缓存中存储的数据是最新的信息,避免脏读。

⬇️提交事务,一级缓存内容消失

image-20230323092828155

二、二级缓存

​ 在 MyBatis 中允许多个 SqlSession 对象共享一个缓存区域,这个缓存区域并一定在内存中,也可能是存储硬盘空间内,这个共享区域就是 MyBatis 的二级缓存。同样使用 HashMap 这种数据结构来存储二级缓 存中保存的数据。

image-20230323094132093

​ MyBatis 默认关闭二级缓存,需要在配置中手动开启

​ ①在 mybatis-config.xml 中设置二级缓存开启

<!--开启二级缓存-->
    <settings>
        <setting name="CacheEnabled" value="true"/>
    </settings>

​ ②在 mapper 文件中加入 cache 标签

<mapper namespace="com.ls.mapper.EmpMapper">
    <cache/>
</mapper>

​ 二级缓存的查询结果对应的 POJO 对象需要实现 Serializable 接口(如果存在父类、以及成员 pojo 都需要实现序列化接口)。下面是测试案例。

⬇️一定要实现序列化接口

image-20230323095424815

⬇️下面是测试代码,还是用上面那个

​ 为测试二级缓存,使用 close 关闭当前的 SqlSession 以绕过一级缓存,获取另一个 SqlSession 后执行相同的 sql 语句。

private static void testTwoLeverCache() {
        SqlSession sqlSession = null;
        SqlSession sqlSession2 = null;
        try {
            sqlSession = MybatisUtil.getSqlSession();
            EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
            Emp emp = empMapper.findByID(11L);
            System.out.println(emp);
            System.out.println("_____________________");
            sqlSession.close();
            //关闭sqlsession以清除一级缓存
            sqlSession2 = MybatisUtil.getSqlSession();
            EmpMapper empMapper2 = sqlSession2.getMapper(EmpMapper.class);
            Emp emp2 = empMapper2.findByID(11L);
            System.out.println(emp2);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {sqlSession.close();}
        }
    }

​ 注意不要使用已经关闭的 SqlSession,不然就会像下面这样。可以看一下这里的文章

​ PS:当让你可以直接sqlSession.commit() 来清空缓存,也能将一缓存到二缓,这是后来知道的。。。。。。

 Cause: org.apache.ibatis.executor.ExecutorException: Executor was closed.

⬇️结果

​ 缓存命中率为 0.5

image-20230323101154821

三、缓存查询的先后顺序

​ 首推这位大佬的文章,想测试的大佬都测了。

​ 当一级缓存和二级缓存都支持时,MyBatis 会先从二级缓存中查询(别的 SqlSession 可能之前将所需要的数据存到了二级缓存中);如果二级缓存没有命中才会查询一级缓存,最后在去数据库中查询;当 sqlSession 关闭或者事务提交时,一级缓存的数据会写入二级缓存。

四、MyBatis二级缓存整合Ehcache

因为用不到所以简写了

​ MyBatis 的特长是 SQL 操作,而不是缓存管理,为了提高缓存的性能将 MyBatis 框架与第三方的缓存数据库框架整合即可,如 ehcache、redis、memcache 等。

​ ①引入依赖包

<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>

​ ②添加配置文件 ehcache.xml

<?xml version="1.0" encoding="UTF-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNameSpaceSchemaLocation="./ehcache.xsd">
<!--
path 属性指定缓存数据的存储路径,属性值可以指定一个盘符目录
例如:D:/temp 或者 java.io.temdir,这个值表示系统的缓存路径
为:C:/users/administrator/AppData/local/Temp
-->
<diskStore path="java.io.temdir"/>
<defaultCache maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="120"
maxElementsOnDisk="10000" diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU" />
</ehcache>

​ ③在mapper中指向ehcache

<cache type="org.mybatis.caches.ehcache.EhcacheCache" />

五、Mybatis延迟加载

https://www.cnblogs.com/purearc/p/17216578.html动态sql

1、延迟加载的概念

​ 延迟加载就是先查询主表的信息,主表信息查询完成后当需要的时候再按照主表相关数据完成关联信息的查询,即在 sql 查询中先查询一部分(主表信息),再查询另一部分(关联表信息)。

2、配置开启延迟加载

⬇️在配置文件中开启延迟加载

​ lazyLoadingEnabled:全局性设置懒加载。如果设为‘false’,则所有相关联属性都会被初始化加载。

​ aggressiveLazyLoading:当设置为‘true’的时候,懒加载的对象的任何懒属性会被全部加载。设置为 false 时,每个属性都按需加载

<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>

​ 真是长见识了,才知道 dtd 校验里面有节点的先后顺序。

image-20230323150933289

3、实现

​ 因为实现延迟加载和前面的 association 和 collection有关,所以用一对多关系那个模块的代码测试。

⬇️在mapper.xml 文件中配置

association 中的 select 值为要嵌套的 sql 子句,column 的值为 “要把主表的哪个列的值代入子 sql 语句” ,在这里我们把主表 id_card 中的属性列 person_id 代入了 elect id,person_name personName from person where id = ? 中。

<!--延迟加载-->
    <select id="list3" resultMap="IdCardMap2">
        select id_card.* from id_card
    </select>

    <resultMap id="IdCardMap2" type="com.ls.pojo.IdCardExtends">
        <id property="id" column="id"></id>
        <result property="cardNum" column="card_num"></result>
        <association property="person" javaType="com.ls.pojo.Person" select="queryPersonById" column="person_id">
        </association>
    </resultMap>
    <select id="queryPersonById" resultType="com.ls.pojo.Person">
        select id,person_name personName from person where id = #{personId}
    </select>

⬇️测试部分

​ 通过 MyBatis 实现延迟加载,其本质就是执行 SQL 查询时,如果调用 Mapper 接口中的 list3 方法 时,只会执行该接口对应的 SQL 语句,并返回一个 idCardExtends 对象。resultMap 中包含的 queryPersonById 对应的 SQL 并不会执行,只有当调用 idCardExtends 对象的 getPerson 方法时,MyBatis 才会去执行 queryPersonById 对应的 SQL。

 private static void testLazyLoad() {
        SqlSession sqlSession = null;
        try {
            sqlSession = MybatisUtil.getSqlSession();
            IdCardMapper idCardMapper = sqlSession.getMapper(IdCardMapper.class);
            List<IdCardExtends> personList =  idCardMapper.list3();
            for (IdCardExtends idCardExtends : personList) {
                System.out.println(idCardExtends.getCardNum());
//                System.out.println(idCardExtends.getCardNum()+":"+idCardExtends.getPerson());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {sqlSession.close();}
        }
    }

⬇️不调用getPerson

image-20230323153232751

⬇️调用getPerson

image-20230323153138706

六、下载

​ Mybatis 基础的部分暂时这些,之后的 Mybatis 源码的简单理解和 MybatisPlus、整合 Redis 、面试题等的理解会再开一部分。

​ 整个工程下载地址:click here