• Mybatis的一级缓存和二级缓存详解


    缓存原理图:

    一、一级缓存(本地缓存)

    sqlSession级别的缓存。(相当于一个方法内的缓存)

    每一次会话都对应自己的一级缓存,作用范围比较小,一旦会话关闭就查询不到了;

    一级缓存默认是一直开启的,是SqlSession级别的一个Map;
    与数据库同一次会话期间查询到的数据会放在本地缓存中。
    以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库;

    测试:

    取缓存中的数据:

    说明:当再次查询发现已经有数据了,就直接在缓存中返回之前查的数据,而不再访问数据库;

    二、一级缓存失效的四种情况:

    没有使用到当前一级缓存的情况,效果就是:还需要再向数据库发出查询

    1、sqlsession不同(会话不同)

    结果:

    说明:不同的会话的缓存不共享数据

    2、sqlsession相同,查询缓存中没有的数据

    @Test
    public void testFirstLevelCache() throws IOException{
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession openSession = sqlSessionFactory.openSession();
        try{
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            Employee emp01 = mapper.getEmpById(1);
            System.out.println(emp01);
            
            //sqlSession相同,查询条件不同
            Employee emp03 = mapper.getEmpById(3);
            System.out.println(emp03);
            System.out.println(emp01==emp03);
     
        }finally{
            openSession.close();
        }
    }

    结果:

    说明:当缓存中没有数据时,会重新查数据库

    3、sqlsession相同,但两次查询之间执行了增删改操作

    @Test
    public void testFirstLevelCache() throws IOException{
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession openSession = sqlSessionFactory.openSession();
        try{
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            Employee emp01 = mapper.getEmpById(1);
            System.out.println(emp01);
            
            //sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
            mapper.addEmp(new Employee(null, "testCache", "cache", "1"));
            System.out.println("数据添加成功");
            
            Employee emp02 = mapper.getEmpById(1);
            System.out.println(emp02);
            System.out.println(emp01==emp02);
            
        }finally{
            openSession.close();
        }
    }

    结果:

    说明:为了防止增删改对当前数据的影响,即使查的同一个对象,也会重新查数据库

    原因:每个增删改标签都有默认清空缓存配置:flushCache="true",不过这是默认的是一级和二级缓存都清空

    4、sqlsession相同,但手动清楚了一级缓存(缓存清空)

    清空缓存:openSession.clearCache();

    @Test
    public void testFirstLevelCache() throws IOException{
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession openSession = sqlSessionFactory.openSession();
        try{
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            Employee emp01 = mapper.getEmpById(1);
            System.out.println(emp01);
            
            //sqlSession相同,手动清除了一级缓存(缓存清空)
            openSession.clearCache();
            
            Employee emp02 = mapper.getEmpById(1);
            System.out.println(emp02);
            System.out.println(emp01==emp02);
            
        }finally{
            openSession.close();
        }
    }

    结果:

    说明:手动清空缓存后,需要重新查数据库

    三、二级缓存(全局缓存)

    基于namespace名称空间级别的缓存:一个namespace对应一个二级缓存

    即一个mapper.xml对应一个缓存:

    1、工作机制:

    *         1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    *         2、如果会话关闭;一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中的内容;
    *         3、sqlSession===EmployeeMapper==>Employee
    *                         DepartmentMapper===>Department
    *             不同namespace查出的数据会放在自己对应的缓存中(map)
    *             效果:数据会从二级缓存中获取
    *                 查出的数据都会被默认先放在一级缓存中。
    *                 只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中

    2、 使用:
    *             1)、开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>
    *             2)、去mapper.xml中配置使用二级缓存:
    *                 <cache></cache>
    *             3)、我们的POJO需要实现序列化接口

    1)在mybatis全局配置文件中开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>

    2)在mapper.xml中配置使用二级缓存

    直接加上: <cache><cache/>

    <?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.atguigu.mybatis.dao.EmployeeMapper">
        
         <cache><cache/>
     
         <!--public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName);  -->
         <select id="getEmpByLastNameLikeReturnMap" resultType="com.atguigu.mybatis.bean.Employee">
             select * from tbl_employee where last_name like #{lastName}
         </select>
    </mapper>

    或者 <cache>中配置一些参数:

    eviction:缓存的回收策略:
        • LRU – 最近最少使用的:移除最长时间不被使用的对象。
        • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
        • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
        • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
        • 默认的是 LRU。
    flushInterval:缓存刷新间隔
        缓存多长时间清空一次,默认不清空,设置一个毫秒值
    readOnly:是否只读:
        true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
                 mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
        false:非只读:mybatis觉得获取的数据可能会被修改。
                mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
    size:缓存存放多少元素;
    type="":指定自定义缓存的全类名;
            实现Cache接口即可;

    <?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.atguigu.mybatis.dao.EmployeeMapper">
     
         <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache> 
     
         <!--public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName);  -->
         <select id="getEmpByLastNameLikeReturnMap" resultType="com.atguigu.mybatis.bean.Employee">
             select * from tbl_employee where last_name like #{lastName}
         </select>
    </mapper>

    3)POJO需要实现序列化接口

    测试:

    注意:需要openSession.close();后,才能从二级缓存中查数据;

    @Test
    public void testSecondLevelCache() throws IOException{
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession openSession = sqlSessionFactory.openSession();
        SqlSession openSession2 = sqlSessionFactory.openSession();
        try{
     
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
            
            Employee emp01 = mapper.getEmpById(1);
            System.out.println(emp01);
            openSession.close();
            
            //第二次查询是从二级缓存中拿到的数据,并没有发送新的sql
            Employee emp02 = mapper2.getEmpById(1);
            System.out.println(emp02);
            openSession2.close();
            
        }finally{
            
        }
    }

    结果:

    说明:第二次查询是从二级缓存中拿到的数据,并没有发送新的sql;

    注意:

    如果openSession.close();在第二次查询之后才关闭,则第二次查询会从一级缓存中查,如果不是一个session,则查询不到数据:

    @Test
    public void testSecondLevelCache() throws IOException{
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession openSession = sqlSessionFactory.openSession();
        SqlSession openSession2 = sqlSessionFactory.openSession();
        try{
     
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
            
            Employee emp01 = mapper.getEmpById(1);
            System.out.println(emp01);
            
            Employee emp02 = mapper2.getEmpById(1);
            System.out.println(emp02);
            openSession.close();
            openSession2.close();
            
        }finally{
            
        }
    }

    结果:

    说明:第二次又重新发送了sql,因为从二级缓存中取数据时,会话没关闭所以二级缓存中没数据,所以又去一级缓存中查询,也没有数据则发送了sql查数据库;

    所以,只有会话关闭或提交后,一级缓存中的数据才会转移到二级缓存中,然后因为是同一个namespace所以可以获取到数据;

    关于Mybatis的一级缓存和二级缓存执行顺序具体可参考:Mybatis的一级缓存和二级缓存执行顺序

    四、和缓存有关的设置/属性

    1)、mybatis全局配置文件中配置全局缓存开启和清空

    1.1)控制二级缓存的开启和关闭

             <setting name="cacheEnabled" value="true"/>

             cacheEnabled=true:开启缓存;false:关闭缓存(二级缓存关闭)(一级缓存一直可用的)

    1.2)控制一级缓存的开启和关闭

             <setting name="localCacheScope" value="SESSION"/>

             localCacheScope:本地缓存作用域(一级缓存SESSION);

             当前会话的所有数据保存在会话缓存中;STATEMENT:可以禁用一级缓存;

    注意:一级缓存关闭后,二级缓存自然也无法使用;  

    2)、方法中sqlSession清除缓存测试

    sqlSession.clearCache();只是清除当前session的一级缓存;

    如果openSession清空了缓存,即执行了openSession.clearCache()方法:

    结果:

     说明:openSession清空缓存不影响二级缓存;只清空了一级缓存;因为在openSession.close()时,就将一级缓存保存至了二级缓存; 

    3)、mapper.xml中也可以配置一级和二级缓存开启和使用

    3.1)每个select标签都默认配置了useCache="true":
             如果useCache= false:则表示不使用缓存(一级缓存依然使用,二级缓存不使用)
    3.2)每个增删改标签默认配置了flushCache="true":(一级二级都会清除)


    增删改执行完成后就会清除缓存;

    测试:默认flushCache="true":一级缓存和二级缓存都会被清空;

    执行增加操作:

    结果:

    注意:查询标签<select>默认flushCache="false":如果flushCache=true;每次查询之后都会清空缓存;一级和二级缓存都无法使用;

    五、整合第三方缓存

    mybatis通过map实现的缓存,很不专业;此时可以通过整合第三方缓存来达到缓存的目的;

    做法:实现Cache.java接口中的方法即可:

    如实现putObject()方法,往缓存中写数据;实现getObject()方法,从缓存中获取数据;至于什么时候调用,由mybatis决定;

  • 相关阅读:
    PostgreSQL数据库逻辑复制实践
    CentOS7通过yum安装postgreSQL
    MongoDB动态建表方案(官方原生驱动)
    7大常用开源数据库利弊全对比
    错误:由于系统时间错误证书验证失败导致更新不成功
    deppin更新提示“由于没有公钥,无法验证下列签名”
    Debian 9 Vim无法使用鼠标右键复制 解决方法
    PHP版滑动时间窗口算法
    RabbitMQ PHP 代码示例
    创建或修改 docker 容器内部文件
  • 原文地址:https://www.cnblogs.com/huangjinyong/p/14543133.html
Copyright © 2020-2023  润新知