zoukankan      html  css  js  c++  java
  • Mybatis (六) 一级缓存和二级缓存

    一级缓存和二级缓存

    本文参考了下文:

    https://www.cnblogs.com/happyflyingpig/p/7739749.html

    一级缓存

    ​ Mybatis 对缓存提供了支持,在没有配置的情况下,Mybatis默认只开启一级缓存,一级缓存其实就是依托于SqlSession,即使用同一个SqlSession操作下,如果Sql语句和参数都没有变化,SqlSession第一次会发送SQL,再缓存没有失效的情况下,后面的查询,就会直接去二级缓存中查找,不再像数据库发送SQL查询。

    图片来源于上述链接博文:

    为什么要使用一级缓存,不用多说也知道个大概。但是还有几个问题我们要注意一下。

      1、一级缓存的生命周期有多长?

    ​ a、MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

      b、如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。

      c、如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。

      d、SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用

        2、怎么判断某两次查询是完全相同的查询?

    mybatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。

      2.1 传入的statementId

      2.2 查询时要求的结果集中的结果范围

      2.3 这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串(boundSql.getSql() )

      2.4 传递给java.sql.Statement要设置的参数值

    一级缓存验证

    我们依托于前面的示例,

    Role:

    @NoArgsConstructor
    @AllArgsConstructor
    @Getter
    @Setter
    @ToString
    public class Role{
    
        private Integer id;
        private String roleName;
        private String description;
        private Integer status;
        private Date createTime;
        private String createUser;
        private Date modifyTime;
        private String modifyUser;
    }
    

    RoleMapper:

    public interface RoleMapper {
        Role findById(Integer id);
    }
    

    下面是测试一级缓存的方法:

    public class AppTest {
    
        SqlSession sqlSession = null;
    
        @Before
        public void init(){
            String resource = "mybatis.configuration.xml";
            InputStream inputStream = AppTest.class.getClassLoader().getResourceAsStream(resource);
            sqlSession = new SqlSessionFactoryBuilder().build(inputStream).openSession(true);
        }
    
        @Test
        public void testFindRoleById(){
            RoleMapper mapper = sqlSession.getMapper(RoleMapper.class);
            Role role1 = mapper.findById(1);
            Role role2 = mapper.findById(1);
            System.out.println(role1);
            System.out.println(role2);
            sqlSession.close();
        }
    }
    

    测试结果:

    二级缓存

    Mybatis的二级缓存是SqlSessionFactory层面的,一级缓存是SqlSession层面的。

    默认情况下,只启用了本地的会话缓存,也就是一级缓存,基于SqlSession的 ,但是如果需要开启二级缓存的话,只需要在Mapper.xml文件中添加一行:

    <cache/>
    

    基本上就是这样。这个简单语句的效果如下:

    • 映射语句文件中的所有 select 语句的结果将会被缓存。
    • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
    • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
    • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
    • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
    • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

    二级缓存验证

    RoleMapper.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.ooyhao.mybatis4.mapper.RoleMapper">
    
        <!--开启二级缓存-->
        <cache />
    
        <resultMap id="base_map_4" type="role">
            <id column="id" jdbcType="INTEGER" property="id" />
            <result column="role_name" jdbcType="VARCHAR" property="roleName"/>
            <result column="description" jdbcType="VARCHAR" property="description"/>
            <result column="status" jdbcType="INTEGER" property="status"/>
            <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
            <result column="create_user" jdbcType="VARCHAR" property="createUser"/>
            <result column="modify_time" jdbcType="TIMESTAMP" property="modifyTime"/>
            <result column="modify_user" jdbcType="VARCHAR" property="modifyUser"/>
        </resultMap>
    
        <select id="findById" resultMap="base_map_4">
            select * from tb_role where id = #{id}
        </select>
    
    </mapper>
    

    如xml文件所述,我们仅在RoleMapper文件下添加<cache/> 用于开启当前namespace 的缓存。

    单元测试方法:

    (需要注意,我们需要将自动提交关闭,即openSession()的参数设置为false,或不填,否则无法进行缓存)

    @Test
    public void testFindRoleByIdWithCache(){
        //二级缓存是在SqlSessionFactory层面的。
        String resource = "mybatis.configuration.xml";
        InputStream stream = AppTest.class.getClassLoader().getResourceAsStream(resource);
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //创建SqlSessionFactory
        SqlSessionFactory build = builder.build(stream);
    
        SqlSession sqlSession1 = build.openSession();
        RoleMapper mapper = sqlSession1.getMapper(RoleMapper.class);
        Role role1 = mapper.findById(1);
        System.out.println(role1);
    
        RoleMapper mapper1 = sqlSession1.getMapper(RoleMapper.class);
        Role role = mapper1.findById(1);
        System.out.println(role);
        sqlSession1.commit();
    
        SqlSession sqlSession2 = build.openSession();//创建了一个新的session。
        RoleMapper roleMapper = sqlSession2.getMapper(RoleMapper.class);
        Role role2 = roleMapper.findById(1);
        System.out.println(role2);
        sqlSession2.commit();
    }
    

    此时测试结果为:(报未序列化异常)

    所以,我们需要为Role对象加上Serializable接口

    如果我们标注为缓存是只读的话,那么不序列化也不会保错:

    <cache readOnly="true" />
    

    测试结果:

    这是启用二级缓存之后的执行结果,可以发现查询了3次,但是实际仅仅只发送了一次SQL,我们可以看第二次的Cache Hit Ratio 是0.3333,即三分之一,我们总共查询了三次,在二级缓存中的命中的次数是一次,所以是0.333.

    我们像下面这样,增加一次查询:

    public class AppTest {
        @Test
        public void testFindRoleByIdWithCache(){
            //二级缓存是在SqlSessionFactory层面的。
            String resource = "mybatis.configuration.xml";
            InputStream stream = AppTest.class.getClassLoader().getResourceAsStream(resource);
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            //创建SqlSessionFactory
            SqlSessionFactory build = builder.build(stream);
    
            SqlSession sqlSession1 = build.openSession();
            RoleMapper mapper = sqlSession1.getMapper(RoleMapper.class);
            Role role1 = mapper.findById(1);
            System.out.println(role1);
    
            RoleMapper mapper1 = sqlSession1.getMapper(RoleMapper.class);
            Role role = mapper1.findById(1);
            System.out.println(role);
            sqlSession1.commit();
    
            SqlSession sqlSession2 = build.openSession();
            RoleMapper roleMapper = sqlSession2.getMapper(RoleMapper.class);
            Role role2 = roleMapper.findById(1);
            System.out.println(role2);
            sqlSession2.commit();
    
    
            SqlSession sqlSession3 = build.openSession();
            RoleMapper roleMapper3 = sqlSession3.getMapper(RoleMapper.class);
            Role role3 = roleMapper3.findById(1);
            System.out.println(role3);
            sqlSession3.commit();
        }
    }
    

    执行结果如下:

    二级缓存详述

    我们知道了如何操作二级缓存,下面我们对二级缓存进行更加详细的了解。

    <cache/>
    

    这个简单语句的效果如下:

    • 映射语句文件中的所有 select 语句的结果将会被缓存。
    • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
    • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
    • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
    • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
    • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

    这些属性可以通过 cache 元素的属性来修改。比如:

    <!--开启二级缓存-->
    <!--开启本mapper的namespace下的二级缓存-->
    <cache eviction="FIFO"
           flushInterval="60000"
           readOnly="true"
           size="512" />
    
    <resultMap id="base_map_4" type="role">
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="role_name" jdbcType="VARCHAR" property="roleName"/>
        <result column="description" jdbcType="VARCHAR" property="description"/>
        <result column="status" jdbcType="INTEGER" property="status"/>
        <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
        <result column="create_user" jdbcType="VARCHAR" property="createUser"/>
        <result column="modify_time" jdbcType="TIMESTAMP" property="modifyTime"/>
        <result column="modify_user" jdbcType="VARCHAR" property="modifyUser"/>
    </resultMap>
    
    <!--可以通过设置useCache来规定这个sql是否开启缓存,ture是开启,false是关闭-->
    <select id="findById" resultMap="base_map_4" useCache="true" >
      	select * from tb_role where id = #{id}
    </select>
    

    ​ 这个更高级的配置创建了一个FIFO(first in first out) 缓存,每隔60秒刷新,最多可以存储结果对象或列表的512个引用,而且返回的对象被认为是只读的,因此对他们进行修改可能会在不同线程中的调用者产生冲突。

    可用的清楚策略有:

    • LRU -最近最少使用:移除最长时间不被使用的对象。(默认)
    • FIFO -先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT - 软引用:基于垃圾回收器状态和软引用规则移除对象。
    • WEAK - 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

    flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

    size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

    readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

    提示: 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

    总结:

    ​ 至此,我们学习了Mybatis的基础知识了,当然,如果我们需要深入理解Mybatis,我们还有很多要学习,所以,学习Mybatis,我们才刚刚开始。后面,我们学习一下如何和Spring整合操作。

    源码地址:

    https://gitee.com/ooyhao/JavaRepo_Public/tree/master/Mybatis

    最后

    如果觉得不错的话,那就关注一下小编哦!一起交流,一起学习

    程序yuan
  • 相关阅读:
    哔哩哔哩小视频全栈爬取分析
    Python压缩指定文件及文件夹为zip
    python对字典及列表递归排序
    Python对list列表及子列表进行排序
    揭开yield关键字的神秘面纱
    python 发送email邮件带附件
    ElementNotVisibleException: Message: element not visible
    /usr/lib/python2.7/site-packages/requests/__init__.py:91: RequestsDependencyWarning: urllib3 (1.22) or chardet (2.2.1) doesn't match a supported version!
    selenium元素单击不稳定解决方法
    selenium自定义find_element
  • 原文地址:https://www.cnblogs.com/ooyhao/p/11562134.html
Copyright © 2011-2022 走看看