• MyBatis的缓存机制(包含一级缓存和二级缓存等所有配置)


    Mybatis的缓存机制

    2.1 缓存介绍

    像大多数的持久化框架一样, Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。Mybatis 中缓存分为一级缓存,二级缓存

    1、默认情况下,只有一级缓存(session级别的缓存,也称为本地缓存)开启。

    2、二级缓存需要手动开启和配置(默认支持状态),他是基于Mapper级别的缓存。

    3、为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

    在这里插入图片描述

    从图中我们可以看出:一级缓存是基于SqlSessoion的缓存,一级缓存的内容不能跨sqlsession。由MyBatis自动维护。二级缓存是基于映射文件的缓存,缓存范围比一级缓存更大。不同的sqlsession可以访问二级缓存的内容。哪些数据放入二级缓存需要自己指定。

    2.2 一级缓存

    一级缓存,也叫本地缓存,作用域默认为当前的SqlSession也就是说不同的SqlSession的缓存是不同的。当 Session flush 或 close 后, 该Session中的一级缓存将被清空。一级缓存是MyBatis维护的,并且默认开启,且不能被关闭,但可以调用clearCache()来清空缓存,或者改变缓存的作用域

    2.2.1 一级缓存相关参数

    • localCacheScope
      • SESSION:会话级别缓存,默认值
      • STATEMENT:语句级别缓存,缓存只对当前执行的语句生效(相当于关闭一级缓存)

    在这里插入图片描述

    2.2.2 一级缓存测试

    • 实体类:
    package com.dfbz.entity;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.io.Serializable;
    
    /**
     * @author lscl
     * @version 1.0
     * @intro:
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Emp implements Serializable {
        private Integer id;
        private String name;
        private Integer age;
        private String addr;
        private Double salary;
        private Double deptId;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 在EmpDao中准备方法:
    package com.dfbz.dao;
    
    import com.dfbz.entity.Emp;
    
    /**
     * @author lscl
     * @version 1.0
     * @intro:
     */
    public interface EmpDao {
    
        Emp findById(Integer id);
    
        void save(Emp emp);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • EmpDao.xml:
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <!--namespace 名称空间,指定对哪个接口进行映射-->
    <mapper namespace="com.dfbz.dao.EmpDao">
    
        <!--
        flushCache:每次执行完SQL语句是否要刷新缓存
            true: 要刷新缓存(清空缓存)
            false: 不清空缓存
    -->
        <!--
            useCache: 是否要将SQL语句的结果集存入二级缓存
                true: 存入二级缓存(默认值)
                false: 不存
        -->
        <select id="findById" resultType="emp">
            select * from emp where id=#{id}
        </select>
    
        <!--
            增删改的flushCache可以设置不清空二级缓存
        -->
        <insert id="save" flushCache="false">
            insert into emp(id,name) values(null,#{name});
        </insert>
    </mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 测试类:
    /**
     * 测试一级缓存
     */
    @Test
    public void test1() {
    
        // 获取session(开启一级缓存)
        SqlSession session = factory.openSession();
    
        EmpDao mapper = session.getMapper(EmpDao.class);
    
        // 发送SQL查询数据,将数据存入一级缓存
        Emp emp = mapper.findById(1);
        System.out.println(emp);
    
        // 从一级缓存中获取数据(没有发送SQL)
        Emp emp2 = mapper.findById(1);
        System.out.println(emp2);
        System.out.println(emp == emp2);            // true
    
        // session关闭,一级缓存清空
        session.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    观察日志:

    在这里插入图片描述

    2.2.3 一级缓存失效清空

    一级缓存在如下情况下,会情况:

    • 1)将一级缓存的作用域设置为语句级别(localCacheScope设置为STATEMENT
    • 2)清空缓存(clearCache)
    • 3)执行任何增删改操作都会导致整个一级缓存(并不是清除影响的那行缓存,而是整个一级缓存)
    • 4)刷新缓存(flushCache)
    • 5)session关闭,一级缓存清空

    2.2.3.1 设置localCacheScope

    <!--日志配置-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    
        <!--
        更改一级缓存的作用域
            SESSION: 会话级别缓存,默认值
            STATEMENT: 语句级别缓存,缓存只对当前执行的语句生效(类似于关闭一级缓存)
    	-->
        <setting name="localCacheScope" value="STATEMENT"/>
    </settings>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 再次执行上述的测试代码:
    /**
     * 测试一级缓存
     */
    @Test
    public void test1() {
    
        // 获取session(开启一级缓存)
        SqlSession session = factory.openSession();
    
        EmpDao mapper = session.getMapper(EmpDao.class);
    
        // 发送SQL查询数据
        Emp emp = mapper.findById(1);
        System.out.println(emp);
    
        // 发送SQL查询数据
        Emp emp2 = mapper.findById(1);
        System.out.println(emp2);
        System.out.println(emp == emp2);            // false
    
        // session关闭
        session.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    执行程序,观察控制台:

    在这里插入图片描述

    2.2.3.2 clearCache

    一级缓存清空测试:

    @Test
    public void test2() throws Exception {          // 测试clearCache
    
        // 开启一级缓存
        SqlSession session = factory.openSession();
    
        EmpDao mapper = session.getMapper(EmpDao.class);
    
        // 首先从一级缓存里面查询,没查询到,然后去数据库查询,将查询的结果放入一级缓存
        Emp emp = mapper.findById(1);
        System.out.println(emp);
    
        session.clearCache();           // 清空一级缓存
    
        // 首先从一级缓存里面查询(没有),去数据库查询,将查询的结果放入一级缓存
        Emp emp2 = mapper.findById(1);
        System.out.println(emp2);
    
        System.out.println(emp == emp2);        // false
    
        // 关闭session(一级缓存清空)
        session.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    执行结果,查看控制台:

    在这里插入图片描述

    2.2.3.3 关闭session清空缓存

    一级缓存是基于session级别的,如果session关闭,那么一级缓存将会失效!

    • 测试代码:
    @Test
    public void test3() throws Exception {          // 测试session关闭清空一级缓存
    
        // 开启一级缓存
        SqlSession session = factory.openSession();
    
        EmpDao mapper = session.getMapper(EmpDao.class);
    
        // 将查询的结果放入一级缓存
        Emp emp = mapper.findById(1);
        System.out.println(emp);
    
        // session关闭一级缓存清空(session关闭了代表与数据库的会话一级结束了,不可以再发送任何的SQL语句了)
        session.close();
    
        // 重新获取一次会话
        SqlSession session2 = factory.openSession();
        EmpDao mapper2 = session2.getMapper(EmpDao.class);
    
        // 新的session的一级缓存中并没有数据,发送SQL去数据库查询
        Emp emp2 = mapper2.findById(1);
        System.out.println(emp2);
        System.out.println(emp == emp2);
    
        // session关闭,一级缓存清空
        session2.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    执行程序,查看控制台:

    在这里插入图片描述

    2.2.3.4 执行任何的增删改清空缓存

    在MyBatis中,执行任何的增删改都会导致一级缓存清空!

    • 测试代码:
    @Test
    public void test3() throws Exception {          // 测试session关闭清空一级缓存
    
        // 开启一级缓存
        SqlSession session = factory.openSession();
    
        EmpDao mapper = session.getMapper(EmpDao.class);
    
        // 将查询的结果放入一级缓存
        Emp emp = mapper.findById(1);
        System.out.println(emp);
    
        // session关闭一级缓存清空(session关闭了代表与数据库的会话一级结束了,不可以再发送任何的SQL语句了)
        session.close();
    
        // 重新获取一次会话
        SqlSession session2 = factory.openSession();
        EmpDao mapper2 = session2.getMapper(EmpDao.class);
    
        // 新的session的一级缓存中并没有数据,发送SQL去数据库查询
        Emp emp2 = mapper2.findById(1);
        System.out.println(emp2);
        System.out.println(emp == emp2);
    
        // session关闭,一级缓存清空
        session2.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述

    2.2.3.5 flushCache清空缓存

    flushCache属性用于控制执行完操作后是否要刷新缓存,对于增删改语句,flushCache的值默认是true,对于查询语句,flushCache的值默认是false,但是MyBatis不支持将任何的增删改语句设置为false

    • EmpDao.xml:
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.dfbz.dao.EmpDao">
    
        <!--
            flushCache="true":每次执行完这个查询语句都清空一级缓存
        -->
        <select id="findById" resultType="emp" flushCache="true">
            select * from emp where id=#{id}
        </select>
    
        <!--
            flushCache: 执行完本次SQL语句是否要刷新缓存
                insert/update/delete:默认为true,对于增删改的操作,MyBatis不支持将其设置为false
                select:默认为false
        -->
        <insert id="save" flushCache="false">
            insert into emp values(null,#{name},#{age},#{addr},#{salary},null)
        </insert>
    </mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    Tips:在MyBatis中,不支持将任何的增删改语句的flushCache属性设置为false

    • 测试代码:
    @Test
    public void test5() throws Exception {          // 测试flushCache清空缓存(对于增删改操作无法设置为false)
    
        // 开启一级缓存
        SqlSession session = factory.openSession();
    
        EmpDao mapper = session.getMapper(EmpDao.class);
    
        Emp emp = mapper.findById(1);
        System.out.println(emp);
    
        Emp emp2 = mapper.findById(1);
        System.out.println(emp2);
    
        System.out.println(emp == emp2);
    
        // 关闭session(一级缓存清空)
        session.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    执行程序,观察控制台:

    在这里插入图片描述

    需要注意的是MyBatis并不支持将任何的增删改语句的flushCache设置为false

    • 测试代码(此时save语句的flushCache为false):
    /**
     * 测试增删改的flushCache属性
     */
    @Test
    public void test6() {
    
        // 开启一级缓存
        SqlSession session = factory.openSession();
    
        EmpDao empDao = session.getMapper(EmpDao.class);
    
        // 将查询到的结果存入一级缓存缓存(发送一次SQL)
        Emp emp = empDao.findById(1);       // flushCache="true",因此会清空一级缓存
        System.out.println(emp);
    
        // 进行增删改操作(一级缓存清空,即使设置了flushCache=false也不管用)
        empDao.save(new Emp(null, "test", 20, null, null, null));
    
        // 重新发送SQL语句
        Emp emp2 = empDao.findById(1);
        System.out.println(emp2);
    
        // session关闭,一级缓存清空
        session.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述

    2.2.4 一级缓存的引用

    MyBatis将数据存入一级缓存时,是将对象的引用(内存地址)存入一级缓存;在获取一级缓存中的数据时,MyBatis将返回当初存入一级缓存的那个内存地址值,也就是说,一级缓存中的数据是同一个;这样一来就会出现内存地址值引用问题;

    • 测试代码:
    // 缓存的引用
    @Test
    public void test6() throws Exception {
    
        // 开启一级缓存
        SqlSession session = factory.openSession();
    
        EmpDao mapper = session.getMapper(EmpDao.class);
    
        // 去数据库查询,将查询结果存入一级缓存
        Emp emp = mapper.findById(1);
        System.out.println(emp);                // name=张三
    
        // 修改emp对象的name为abc(一级缓存中的emp也会修改)
        emp.setName("abc");
    
        // 从一级缓存中查询emp对象
        Emp emp2 = mapper.findById(1);
        System.out.println(emp2);               // name=abc
    
        System.out.println(emp == emp2);
    
        // 关闭session(一级缓存清空)
        session.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    2.2.5 PerpetualCache缓存类

    MyBatis 跟缓存相关的类都在cache 包里面,其中有一个Cache 接口,只有一个默认的实现类 PerpetualCache,改类是MyBatis的缓存实现类;包括一级缓存和二级缓存都是采用PerpetualCache类来实现的;

    在这里插入图片描述

    • 测试代码:
    @Test
    public void test1() throws Exception {
        // 开启一级缓存
        SqlSession session = factory.openSession();
    
        EmpDao mapper = session.getMapper(EmpDao.class);
    
        /*
            首先从一级缓存里面查询
                查询到了: 返回
                没查询到: 去数据库查询,之后将查询的结果放入一级缓存
         */
        Emp emp = mapper.findById(1);
        System.out.println(emp);
    
        /*
            从一级缓存里面查询,直接返回
         */
        Emp emp2 = mapper.findById(1);
        System.out.println(emp2);
    
        System.out.println(emp == emp2);        // true
    
        // 关闭session(一级缓存清空)
        session.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    一级缓存执行流程:

    在这里插入图片描述

    2.3 二级缓存

    我们刚刚学习完了一级缓存,一级缓存是session级别的缓存,不同的session一级缓存是不能共享的;

    二级缓存是mapper级别的缓存,多个session去操作同一个Mapper映射的SQL语句时,多个session可以共用二级缓存,二级缓存是跨SqlSession 的。

    当二级缓存和一级缓存同时存在时,先查询二级缓存,再查询一级缓存;

    在这里插入图片描述

    2.3.1 二级缓存相关配置

    • 全局配置:

    MyBatis默认是开启二级缓存的,我可以通过cacheEnabled参数来控制二级缓存的开关;此配置默认开启

    • 修改SqlMapConfig配置文件:
    <settings>
        <!--日志配置-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    
        <!--开启二级缓存,默认是开启状态,false为关闭二级缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • Mapper配置以及SQL语句配置:

    在SqlMapConfig.xml开启二级缓存后(此配置默认开启状态),还需要在对应的mapper.xml文件中激活缓存;否则二级缓存并不会生效;

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.dfbz.dao.EmpDao">
        <!--
        <cache />: 开启当前Mapper的二级缓存
            eviction: 当内存使用紧张时缓存的回收策略
                • LRU  – 最近最少使用的:移除最长时间不被使用的对象。
    		    • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    		    • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
    		    • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
            flushInterval:缓存刷新间隔,缓存多长时间清空一次,默认不清空,可以通过此参数设置一个毫秒值
            readOnly:是否只读
    		    true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
    				 mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
    		    false:非只读:mybatis觉得获取的数据可能会被修改。
    				mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
    		size:二级缓存中最大能够缓存存放多少元素
    		type:指定自定义缓存的全类名(实现Cache接口)
        -->
        <cache />
        
          <!--
            useCache: 当一级缓存关闭时,是否将本次SQL语句的结果集存入二级缓存
                true: 存入(默认值)
                false: 不存入
        -->
        <select id="findById" resultType="emp">
            select * from emp where id=#{id}
        </select>
    
        <insert id="save" >
            insert into emp(id,name) values(null,#{name});
        </insert>
    </mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    存入二级缓存中的对象必须实现序列化接口:

    在这里插入图片描述

    2.3.2 二级缓存测试

    2.3.2.1 二级缓存代码测试

    当二级缓存和一级缓存同时存在时,先查询二级缓存,再查询一级缓存;

    当session关闭时,将一级缓存中的数据写入二级缓存;

    @Test
    public void test1() throws Exception {          // 测试二级缓存
    
        // 开启一级缓存
        SqlSession session = factory.openSession();
    
        EmpDao mapper = session.getMapper(EmpDao.class);
    
    /*
        先从二级缓存里面查询-->再查询一级缓存-->再查询数据库(查询到了把结果放入一级缓存)
     */
        Emp emp = mapper.findById(1);
        System.out.println(emp);
    
        // 关闭一级缓存,把数据写入二级缓存(这个时候才会把数据写入二级缓存(序列化))
        session.close();
    //        session.clearCache();               // session关闭才会将一级缓存的内容写入二级缓存,clearCache并不会
    
        // 开启一级缓存
        SqlSession session2 = factory.openSession();
    
        EmpDao mapper2 = session2.getMapper(EmpDao.class);
    
        // 先查询二级缓存(有),反序列化
        Emp emp2 = mapper2.findById(1);
        System.out.println(emp2);
        System.out.println(emp == emp2);            // false ,两个对象的属性值一样,但是内存地址值不一样(反序列化出来的)
    
        session2.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    观察日志:

    在这里插入图片描述

    2.3.2.2 useCache

    useCache可以控制当前SQL语句的结果集是否要存入二级缓存;默认清空下为true

    <!--
        useCache: 当前SQL语句的结果集是否存入二级缓存(需要当前的Mapper.xml开启二级缓存useCache才会生效)
            true: 存
            false: 不存
    -->
    <select id="findById" resultType="emp" useCache="true">
        select * from emp where id=#{id}
    </select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Tips:

    • 1)useCache只能控制二级缓存,并不会影响一级缓存;
    • 2)useCache需要当前的mapper.xml开启二级缓存功能后才能使用;

    2.3.3 二级缓存失效情况

    二级缓存在如下情况下,会情况:

    • 1)执行任何增删改操作
    • 2)刷新缓存(flushCache)

    2.3.3.1 执行增删改

    执行任何的增删改操作,不仅会导致一级缓存清空,也会导致二级缓存清空!

    • 测试代码:
    /**
     * 测试任何增删改清空二级缓存
     */
    @Test
    public void test2() throws Exception {
    
        // 开启一级缓存
        SqlSession session = factory.openSession(true);
    
        EmpDao mapper = session.getMapper(EmpDao.class);
    
        // 先从二级缓存里面查询,再从一级缓存里面查询,再从数据库查询(发送SQL),将查询到的结果集存入一级缓存
        Emp emp = mapper.findById(1);
        System.out.println(emp);
    
        // 关闭一级缓存,把数据写入二级缓存(这个时候才会把数据写入二级缓存(序列化))
        session.close();
    
        // 开启一级缓存
        SqlSession session2 = factory.openSession(true);
    
        EmpDao mapper2 = session2.getMapper(EmpDao.class);
    
        Emp saveEmp = new Emp();
        saveEmp.setName("aaa");
    
        mapper2.save(saveEmp);                  // 执行任何的增删改清空所有缓存(一级缓存和二级缓存)
    
        // 查询二级缓存(没有),一级缓存(没有),发送SQL
        Emp emp2 = mapper2.findById(1);
        System.out.println(emp2);
    
        session2.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    执行程序,日志如下:

    Created connection 368342628.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@15f47664]
    ==>  Preparing: select * from dept where id=?			# 第一次发送SQL
    ==> Parameters: 1(Integer)
    <==    Columns: id, name, location
    <==        Row: 1, 研发部, 中国台湾
    <==      Total: 1
    研发部
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@15f47664]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@15f47664]		# 关闭session,将数据写入二级缓存
    Returned connection 368342628 to pool.
    Opening JDBC Connection
    Checked out connection 368342628 from pool.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@15f47664]
    ==>  Preparing: insert into dept values(null,?,?)			# 执行任何增删改清空一级/二级缓存
    ==> Parameters: test(String), test(String)
    <==    Updates: 1
    Cache Hit Ratio [com.dfbz.dao.DeptDao]: 0.5
    ==>  Preparing: select * from dept where id=?				# 再次发送SQL查询
    ==> Parameters: 1(Integer)
    <==    Columns: id, name, location
    <==        Row: 1, 研发部, 中国台湾
    <==      Total: 1
    false		# 返回false
    Rolling back JDBC Connection [com.mysql.jdbc.JDBC4Connection@15f47664]
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@15f47664]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@15f47664]
    Returned connection 368342628 to pool.
    Disconnected from the target VM, address: '127.0.0.1:58814', transport: 'socket'
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    2.3.3.2 flushCache

    flushCache不仅会清空一级缓存,而且还会清空二级缓存

    在这里插入图片描述

    Tips:flushCache控制二级缓存时可以设置任何的增删改查是否清空二级缓存

    • 测试代码:
    /*
        测试flushCache
     */
    @Test
    public void test4() throws Exception {
    
        // 开启一级缓存
        SqlSession session = factory.openSession();
    
        EmpDao mapper = session.getMapper(EmpDao.class);
    
        // 先从二级缓存里面查询,再从一级缓存里面查询,再从数据库查询(发送SQL),之后flushCache,一级缓存数据清空
        Emp emp = mapper.findById(1);
        System.out.println(emp);
    
        // 一级缓存关闭,将一级缓存的数据写入二级缓存(此时一级缓存并没有数据)
        session.close();
    
        // 获取session,开启新的一级缓存
        SqlSession session2 = factory.openSession();
    
        EmpDao mapper2 = session2.getMapper(EmpDao.class);
    
        // 此时二级缓存,一级缓存均没有数据,最终去数据库查询
        Emp emp2 = mapper2.findById(1);
        System.out.println(emp2);
    
        session2.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    执行程序,日志如下:

    Created connection 527829831.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    ==>  Preparing: select * from emp where id=?			# 发送SQL语句查询,并没有将结果存入一级缓存(flushCache)
    ==> Parameters: 1(Integer)
    <==    Columns: id, name, age, addr, salary, dept_id
    <==        Row: 1, 张三, 20, 广西来宾, 7600.00, 1
    <==      Total: 1
    Emp(id=1, name=张三, age=20, addr=广西来宾, salary=7600.0, deptId=null)
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    
    # sesison关闭,将一级缓存的数据写入二级缓存(此时一级缓存中并没有数据)
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]	
    Returned connection 527829831 to pool.
    Cache Hit Ratio [com.dfbz.dao.EmpDao]: 0.5
    Opening JDBC Connection									# 重新开启一个session
    Checked out connection 527829831 from pool.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    # 先查询二级缓存(没查询到),再擦好像一级缓存(没查询到),最终去查询数据库
    ==>  Preparing: select * from emp where id=?			
    ==> Parameters: 1(Integer)
    <==    Columns: id, name, age, addr, salary, dept_id
    <==        Row: 1, 张三, 20, 广西来宾, 7600.00, 1
    <==      Total: 1
    Emp(id=1, name=张三, age=20, addr=广西来宾, salary=7600.0, deptId=null)
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    Returned connection 527829831 to pool.
    Disconnected from the target VM, address: '127.0.0.1:52082', transport: 'socket'
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    flushCache不仅会清空一级缓存,而且也会清空二级缓存

    • 扩展一个方法:
    Emp findByName(String name);
    
    • 1
    • EmpDao.xml:
    <select id="findByName" resultType="emp" >
        select * from emp where name=#{name}
    </select>
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    • 测试代码:
    @Test
    public void test5() throws Exception {          
    
        // 开启一级缓存
        SqlSession session = factory.openSession();
    
        EmpDao mapper = session.getMapper(EmpDao.class);
    
        // 先从二级缓存里面查询,再从一级缓存里面查询,再从数据库查询(发送SQL),将查询到的结果集存入一级缓存
        Emp emp = mapper.findByName("小明");
        System.out.println(emp);
    
        // 一级缓存关闭,将一级缓存的数据写入二级缓存
        session.close();
    
        // 获取session,开启新的一级缓存
        SqlSession session2 = factory.openSession();
    
        EmpDao mapper2 = session2.getMapper(EmpDao.class);
    
        // flushCache清空一级缓存(不会清空二级缓存)
        Emp emp2 = mapper2.findById(1);
        System.out.println(emp2);
    
        // 先查询二级缓存(查询到了)
        Emp emp3 = mapper2.findByName("小明");
        System.out.println(emp3);
    
        session2.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    执行程序,日志如下:

    Created connection 527829831.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    
    # 将数据写入一级缓存
    ==>  Preparing: select * from emp where name=?
    ==> Parameters: 小明(String)
    <==    Columns: id, name, age, addr, salary, dept_id
    <==        Row: 3, 小明, 25, 广东云浮, 6600.00, 2
    <==      Total: 1
    Emp(id=3, name=小明, age=25, addr=广东云浮, salary=6600.0, deptId=null)
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    
    # 连接关闭,将数据写入二级缓存
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    Returned connection 527829831 to pool.
    Cache Hit Ratio [com.dfbz.dao.EmpDao]: 0.0
    Opening JDBC Connection
    Checked out connection 527829831 from pool.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    
    # 执行findById(flushCache清空一级缓存和二级缓存)
    ==>  Preparing: select * from emp where id=?
    ==> Parameters: 1(Integer)
    <==    Columns: id, name, age, addr, salary, dept_id
    <==        Row: 1, 张三, 20, 广西来宾, 7600.00, 1
    <==      Total: 1
    Emp(id=1, name=张三, age=20, addr=广西来宾, salary=7600.0, deptId=null)
    Cache Hit Ratio [com.dfbz.dao.EmpDao]: 0.3333333333333333
    
    # 再次发送SQL去数据库查询
    ==>  Preparing: select * from emp where name=?
    ==> Parameters: 小明(String)
    <==    Columns: id, name, age, addr, salary, dept_id
    <==        Row: 3, 小明, 25, 广东云浮, 6600.00, 2
    <==      Total: 1
    Emp(id=3, name=小明, age=25, addr=广东云浮, salary=6600.0, deptId=null)
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    Returned connection 527829831 to pool.
    Disconnected from the target VM, address: '127.0.0.1:52207', transport: 'socket'
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    前面我们学习一级缓存的时候,flushCache对于增删改语句是无法设置为false(设置了不生效),即执行任何增删改的时候一定会清空一级缓存,但flushCache却可以控制二级缓存的增删改;

    将insert语句的flushCache设置为false(不清空缓存):

    在这里插入图片描述

    • 测试代码:
    @Test
    public void test6() throws Exception {
    
        // 开启一级缓存
        SqlSession session = factory.openSession();
    
        EmpDao mapper = session.getMapper(EmpDao.class);
    
        // 先从二级缓存里面查询,再从一级缓存里面查询,再从数据库查询(发送SQL),将查询到的结果集存入一级缓存
        Emp emp = mapper.findByName("小明");
        System.out.println(emp);
    
        // 一级缓存关闭,将一级缓存的数据写入二级缓存
        session.close();
    
        // 获取session,开启新的一级缓存
        SqlSession session2 = factory.openSession();
    
        EmpDao mapper2 = session2.getMapper(EmpDao.class);
    
        // 执行新增(flushCache为false,并不会清空二级缓存)
        mapper2.save(new Emp(null,"xxx",null,null,null,null));
    
        // 先查询二级缓存(查询到了)
        Emp emp2 = mapper2.findByName("小明");
        System.out.println(emp2);
    
        session2.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    日志如下:

    Created connection 527829831.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    ==>  Preparing: select * from emp where name=?
    ==> Parameters: 小明(String)
    <==    Columns: id, name, age, addr, salary, dept_id
    <==        Row: 3, 小明, 25, 广东云浮, 6600.00, 2
    <==      Total: 1
    Emp(id=3, name=小明, age=25, addr=广东云浮, salary=6600.0, deptId=null)
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    Returned connection 527829831 to pool.
    Opening JDBC Connection
    Checked out connection 527829831 from pool.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    ==>  Preparing: insert into emp(id,name) values(null,?);
    ==> Parameters: xxx(String)
    <==    Updates: 1
    Cache Hit Ratio [com.dfbz.dao.EmpDao]: 0.5
    Emp(id=3, name=小明, age=25, addr=广东云浮, salary=6600.0, deptId=null)
    Rolling back JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    Returned connection 527829831 to pool.
    Disconnected from the target VM, address: '127.0.0.1:52373', transport: 'socket'
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    2.3.4 PerpetualCache的二级缓存

    PerpetualCache不仅是MyBaits的一级缓存类,同时MyBatis也将二级缓存的数据存储在PerpetualCache中;在MyBatis加载时(创建session工厂时)就已经创建好了二级缓存,只不过需要等到session关闭时,本次session的一级缓存的数据才会写入二级缓存;

    • 测试代码:
    package com.dfbz.mybatis;
    
    import com.dfbz.dao.EmpDao;
    import com.dfbz.entity.Emp;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    /**
     * @author lscl
     * @version 1.0
     * @intro:
     */
    public class Demo02_一级缓存和二级缓存 {
    
        public static void main(String[] args) throws Exception {
    
            // 创建Session工厂构建对象
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    
            // 通过构建对象来构建一个session工厂(创建二级缓存)
            SqlSessionFactory factory = builder.build(Resources.getResourceAsStream("SqlMapConfig.xml"));
    
            // 获取session(创建一级缓存)
            SqlSession session = factory.openSession();
    
            // 获取mapper类
            EmpDao mapper = session.getMapper(EmpDao.class);
    
            // 查询二级缓存,没查询到,再查询一级缓存,没查询到,最后发送SQL去数据库查询,然后将数据放入一级缓存
            Emp emp = mapper.findById(1);
            System.out.println(emp);
    
            // 查询二级缓存,没查询到,再查询一级缓存,查询到了,返回(没有发送SQL)
            Emp emp2 = mapper.findById(1);
            System.out.println(emp2);
    
            System.out.println(emp == emp2);        // true
    
            session.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    一级缓存和二级缓存查询流程:

    在这里插入图片描述

    2.4 MyBatis二级缓存相关配置

    2.4.1 二级缓存配置

    • <cache />:开启当前Mapper的二级缓存
      • eviction:当内存使用紧张时,缓存的回收策略缓存的回收策略
        • LRU:最近最少使用的:移除最长时间不被使用的对象。
        • FIFO:先进先出:按对象进入缓存的顺序来移除它们。
        • SOFT:软引用:移除基于垃圾回收器状态和软引用规则的对象。
        • WEAK:弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
      • flushInterval:缓存刷新间隔,缓存多长时间清空一次,默认不清空,可以通过此参数设置一个毫秒值
      • readOnly:是否只读
        • true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
        • false:非只读(默认值);mybatis觉得获取的数据可能会被修改。
      • size:二级缓存中最大能够缓存存放多少元素
      • type:指定自定义缓存的全类名(实现Cache接口)

    2.4.1.1 缓存大小

    修改配置:

    <cache size="1"/>
    
    • 1
    • 测试代码:
    @Test
    public void test1() throws Exception {
        // 开启一级缓存
        SqlSession session = factory.openSession();
    
        EmpDao mapper = session.getMapper(EmpDao.class);
    
        // 数据存入一级缓存
        Emp emp = mapper.findById(1);
        System.out.println(emp);
    
        // 数据存入一级缓存
        Emp emp2 = mapper.findById(2);
        System.out.println(emp2);
    
        System.out.println(emp == emp2);        // true
    
        // 关闭session,将数据写入二级缓存(此时一级缓存的大小为1)
        session.close();
    
        SqlSession session1 = factory.openSession();
    
        EmpDao mapper1 = session1.getMapper(EmpDao.class);
        Emp emp3 = mapper1.findById(2);         // 二级缓存不存在,一级缓存也不存在,发送SQL去查询
    
        session1.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    执行日志如下:

    Created connection 527829831.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    ==>  Preparing: select * from emp where id=?
    ==> Parameters: 1(Integer)
    <==    Columns: id, name, age, addr, salary, dept_id
    <==        Row: 1, 张三, 20, 广西来宾, 7600.00, 1
    <==      Total: 1
    Emp(id=1, name=张三, age=20, addr=广西来宾, salary=7600.0, deptId=null)
    Cache Hit Ratio [com.dfbz.dao.EmpDao]: 0.0
    ==>  Preparing: select * from emp where id=?
    ==> Parameters: 2(Integer)
    <==    Columns: id, name, age, addr, salary, dept_id
    <==        Row: 2, 李四, 22, 浙江绍兴, 6800.00, 4
    <==      Total: 1
    Emp(id=2, name=李四, age=22, addr=浙江绍兴, salary=6800.0, deptId=null)
    false
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    
    # 关闭session将数据写入二级缓存,只能存储一条
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    Returned connection 527829831 to pool.
    Cache Hit Ratio [com.dfbz.dao.EmpDao]: 0.0
    Opening JDBC Connection
    Checked out connection 527829831 from pool.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    
    # 再次查询二级缓存发现没有,然后查询一级缓存也没有,最终发送SQL去查询数据库
    ==>  Preparing: select * from emp where id=?
    ==> Parameters: 2(Integer)
    <==    Columns: id, name, age, addr, salary, dept_id
    <==        Row: 2, 李四, 22, 浙江绍兴, 6800.00, 4
    <==      Total: 1
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f760b47]
    Returned connection 527829831 to pool.
    Disconnected from the target VM, address: '127.0.0.1:51525', transport: 'socket'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    2.4.1.2 缓存只读

    之前我们学一级缓存的时候提到过,一级缓存里面存储的是对象的引用(内存地址),当对象内容修改了,一级缓存里面存储的那个对象内容也会随之修改,而二级缓存里面存储的是对象序列化之后的值,换句话来说就是类似与拷贝了一份新的数据到二级缓存;因此修改原有对象不会影响二级缓存里面的对象内容;

    • 测试代码:
    @Test
    public void test2() throws Exception {
        // 开启一级缓存
        SqlSession session = factory.openSession();
    
        EmpDao mapper = session.getMapper(EmpDao.class);
    
        // 数据存入一级缓存
        Emp emp = mapper.findById(1);
        System.out.println("------------emp1: " + emp);                 // name=张三
    
        // 关闭session,将一级缓存的数据序列化(拷贝)到二级缓存
        session.close();
    
        // 修改emp对象的属性值
        emp.setName("abc");
    
        SqlSession session1 = factory.openSession();
    
        EmpDao mapper1 = session1.getMapper(EmpDao.class);
    
        // 从二级缓存中反序列化出来
        Emp emp2 = mapper1.findById(1);
        System.out.println("------------emp2: " + emp2);                // name=张三
    
        // 因为是反序列化出来的对象,和原有对象不是同一个,返回false
        System.out.println(emp == emp2);                                
        session1.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    执行日志:

    在这里插入图片描述

    而这样带来的弊端就是每次session关闭的时候都需要将数据序列化一份,这不仅从时间角度上效率低了,从空间角度上效率也会变低,因为每次都在序列化(拷贝)新的对象;如果我们对数据的修改并不敏感,或者不会对数据进行修改,那么我们可以将二级缓存的readOnly设置为true;此时二级缓存里面存储的是对象的引用,并不会采用序列化技术,此时的对象也不需要实现序列化接口了;

    • 修改二级缓存配置:
    <cache readOnly="true"/>
    
    • 1
    • 测试代码:
    @Test
    public void test2() throws Exception {
        // 开启一级缓存
        SqlSession session = factory.openSession();
    
        EmpDao mapper = session.getMapper(EmpDao.class);
    
        // 数据存入一级缓存
        Emp emp = mapper.findById(1);
        System.out.println("------------emp1: " + emp);                 // name=张三
    
        // 关闭session,将一级缓存的数据序列化(拷贝)到二级缓存
        session.close();
    
        // 修改emp对象的属性值
        emp.setName("abc");
    
        SqlSession session1 = factory.openSession();
    
        EmpDao mapper1 = session1.getMapper(EmpDao.class);
    
        // 从二级缓存中反序列化出来
        Emp emp2 = mapper1.findById(1);
        System.out.println("------------emp2: " + emp2);                // name=张三
    
        // 因为是反序列化出来的对象,和原有对象不是同一个,返回false
        System.out.println(emp == emp2);
        session1.close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    执行日志:

    在这里插入图片描述

    2.5 Redis替换二级缓存

    查看Cache接口:

    package org.apache.ibatis.cache;
    
    import java.util.concurrent.locks.ReadWriteLock;
    
    public interface Cache {
        String getId();
    
        void putObject(Object var1, Object var2);
    
        Object getObject(Object var1);
    
        Object removeObject(Object var1);
    
        void clear();
    
        int getSize();
    
        default ReadWriteLock getReadWriteLock() {
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2.5.1 自定义缓存

    我们可以自定义一个类,重写Cache接口,让MyBatis存入二级缓存的数据交给我们来处理!

    • 1)引入相关依赖:
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.1</version>
    </dependency>
    
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.62</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 2)重写Cache接口:
    package com.dfbz.cache;
    
    import com.alibaba.fastjson.JSON;
    import com.dfbz.entity.Emp;
    import org.apache.ibatis.cache.Cache;
    import redis.clients.jedis.Jedis;
    
    import java.util.List;
    
    /**
     * @author lscl
     * @version 1.0
     * @intro:
     */
    public class MyCache implements Cache {
        private Jedis jedis = new Jedis();
    
        @Override
        public String getId() {
            return id;
        }
    
        /**
         * 添加缓存
         *
         * @param key: 缓存的key
         * @param val: 需要存储到缓存的值(是一个List类型)
         */
        @Override
        public void putObject(Object key, Object val) {
    
            jedis.set(key.toString(), JSON.toJSONString(val));
    
            System.out.println("putObject..............");
        }
    
        /**
         * 获取缓存中的内容
         *
         * @param key
         * @return
         */
        @Override
        public Object getObject(Object key) {
    
            System.out.println("getObject................");
    
            String jsonStr = jedis.get(key.toString());
    
            List<Emp> list = JSON.parseArray(jsonStr, Emp.class);
    
            return list;
        }
    
        /**
         * 删除缓存
         *
         * @param key
         * @return
         */
        @Override
        public Object removeObject(Object key) {
            System.out.println("removeObject................");
            return jedis.del(key.toString());
        }
    
        /**
         * 清空缓存
         */
        @Override
        public void clear() {
            System.out.println("clear................");
            jedis.flushDB();
        }
    
        /**
         * 获取缓存中的key数量
         *
         * @return
         */
        @Override
        public int getSize() {
            System.out.println("getSize................");
            return Integer.parseInt(jedis.dbSize() + "");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 3)在EmpDao.xml中配置:
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.dfbz.dao.EmpDao">
    
        <!--替换为我们自定义的缓存-->
        <cache type="com.dfbz.cache.MyCache"></cache>
    
        <select id="findById" resultType="Emp">
            select * from emp where id=#{id}
        </select>
    </mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    2.5.2 测试Redis替代二级缓存

    package com.dfbz.test;
    
    import com.dfbz.dao.EmpDao;
    import com.dfbz.entity.Emp;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    import org.junit.Before;
    import org.junit.Test;
    
    import java.io.IOException;
    
    public class Demo03_自定义缓存 {
    
        private SqlSessionFactoryBuilder builder;
    
        private SqlSessionFactory factory;
    
        @Before
        public void before() throws IOException {
            builder = new SqlSessionFactoryBuilder();
            factory = builder.build(Resources.getResourceAsStream("SqlMapConfig.xml"));
        }
    
        @Test
        public void test1() {
    
            // 开启一级缓存
            SqlSession session = factory.openSession();
    
            EmpDao mapper = session.getMapper(EmpDao.class);
    
            // 首先从二级缓存去查询,没查询到则去一级缓存查询,然后将结果存入一级缓存
            Emp emp = mapper.findById(1);
            System.out.println(emp);
    
            // 关闭session(将数据存入二级缓存)
            session.close();
    
            SqlSession session2 = factory.openSession();
    
            EmpDao mapper2 = session2.getMapper(EmpDao.class);
    
            Emp emp2 = mapper2.findById(1);
            System.out.println(emp2);
    
            session.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    在这里插入图片描述

    2.6 MyBatis缓存总结

    一级缓存和二级缓存的执行顺序为:二级缓存---->一级缓存---->数据库

    查询语句始终是由一级缓存发送的,一级缓存默认由MyBatis维护,我们只需了解使用即可

    二级缓存需要我们可以修改配置:

    1、在MyBatis中,二级缓存是默认开启状态,在SqlMapConfig.xml中设置二级缓存的支持情况

    <settings>
        <!--开启二级缓存支持 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
    • 1
    • 2
    • 3
    • 4

    2、在mapper文件中加入<cache>标签代表激活本mapper文件的二级缓存,并保证useCache不为false(默认为true)

    <cache></cache>
    <select id="findById" parameterType="int" resultType="user" useCache="true">
        select * from user where id=#{id}
    </select>
    
    • 1
    • 2
    • 3
    • 4

    3、MyBatis的二级缓存默认采用序列化/反序列化来保证对象的存取,所以所有的entity对象都应该实现serializable接口



    记得点赞~

  • 相关阅读:
    爱上源码,重学Spring MVC深入
    2022-08-27 AndroidR 插入USB设备自动授权不弹出权限对话框
    容器内的Linux诊断工具0x.tools
    hystrix 熔断器
    【附源码】计算机毕业设计SSM社区便捷管理系统
    EMCC 删除配置错误的数据库信息 以及修改度量METRICS
    Orin + Marvell 88q4364调试
    MyBatisPlus(七)——通用枚举、代码生成器、多数据源、MyBatisX
    windows mysql安装卸载,多版本mysql方案
    时序分解 | MATLAB实现RIME-VMD霜冰优化算法优化VMD变分模态分解信号分量可视化
  • 原文地址:https://blog.csdn.net/Bb15070047748/article/details/124954080