MyBatis笔记(五)——一级缓存,二级缓存

MyBatis笔记(五)——一级缓存,二级缓存


参考: https://www.bilibili.com/video/BV1NE411Q7Nx

视频原作者CSDN: https://blog.csdn.net/qq_33369905

这篇是自己整理了一下,以便自己复习。

1.前期准备

1.1 缓存简介

摘自 狂神说MyBatis07:缓存

1、什么是缓存 [ Cache ]?

  • 存在内存中的临时数据。

2、为什么使用缓存?

  • 减少和数据库的交互次数,减少系统开销,提高系统效率。

3、什么样的数据能使用缓存?

  • 经常查询并且不经常改变的数据。

合理利用缓存可提高系统运行效率!

1.2 MyBatis中的自带缓存简介

MyBatis系统中默认定义了两级缓存:一级缓存二级缓存

  • 一级缓存:默认开启 SqlSession(对一个会话中的数据进行缓存)
  • 二级缓存:手动开启和配置,基于某个xml映射文件的缓存

此外提供了缓存接口Cache,可自定义二级缓存。

1.3 数据表与实体类准备
-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(20) NOT NULL,
  `name` varchar(30) DEFAULT NULL,
  `pwd` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', '张三', '1123');
INSERT INTO `user` VALUES ('2', '李四', 'ksqo');
INSERT INTO `user` VALUES ('3', '王五', 'wwww');

User类

public class User {

    private int id;  //id
    private String name;   //姓名
    private String pwd;   //密码

    //推荐Lombok
    //构造,有参,无参
    //set/get
    //toString()
}

2.MyBatis中的一级缓存

2.1 一级缓存效果

添加log4j日志以便查看执行的SQL。

此不赘述,可查看:MyBatis笔记(三)——ResultMap结果集映射,日志,分页的多种实现方式

UserMapper接口类:

public interface UserMapper {
    
    //查询所有User
    List<User> selectUser();

    //按照id查询User
    User selectUserById(int id);
}

UserMapper.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.piao.dao.UserMapper">

    <select id="selectUser" resultType="com.piao.User">
      select * from user;
    </select>

    <select id="selectUserById" parameterType="int" resultType="com.piao.User">
        select * from user where id = #{id};
    </select>
</mapper>

测试:

    @Test
    public void testSelectUserById() {
        System.out.println("测试按照id查询用户selectUserById()");

        List<User> users = mapper.selectUser();
        for (User user : users) {
            System.out.println(user);
        }
        List<User> users2 = mapper.selectUser();
        for (User user : users) {
            System.out.println(user);
        }
        //比较两次 查询所有 的结果集
        System.out.println(users == users2);

        User user = mapper.selectUserById(2);
        System.out.println(user);
        User user2 = mapper.selectUserById(2);
        System.out.println(user);
        //比较两次 按照id查询 的结果集
        System.out.println(user == user2);
    }

输出:可看到第二次查询所有用户和第二次查询id为2的用户没有与数据库交互,没有执行SQL。

这便是MyBatis默认开启的一级缓存的效果,对于单条查询SQL语句的结果,sqlsession会保存该数据。

DEBUG [main] - ==>  Preparing: select * from user; 
DEBUG [main] - ==> Parameters: 
TRACE [main] - <==    Columns: id, name, pwd
TRACE [main] - <==        Row: 1, 张三, 1123
TRACE [main] - <==        Row: 2, 李四, ksqo
TRACE [main] - <==        Row: 3, 王五, wwww
DEBUG [main] - <==      Total: 3
User{id=1, name='张三', pwd='1123'}
User{id=2, name='李四', pwd='ksqo'}
User{id=3, name='王五', pwd='wwww'}
User{id=1, name='张三', pwd='1123'}
User{id=2, name='李四', pwd='ksqo'}
User{id=3, name='王五', pwd='wwww'}
true
DEBUG [main] - ==>  Preparing: select * from user where id = ?; 
DEBUG [main] - ==> Parameters: 2(Integer)
TRACE [main] - <==    Columns: id, name, pwd
TRACE [main] - <==        Row: 2, 李四, ksqo
DEBUG [main] - <==      Total: 1
User{id=2, name='李四', pwd='ksqo'}
User{id=2, name='李四', pwd='ksqo'}
true
2.2 MyBatis一级缓存失效的四种情况

MyBatis一级缓存失效结果就是MyBatis会与数据库交互,刷新一级缓存。

  • sqlSession不同

每个sqlSession中保存的数据是不共享的,相互独立。因此使用不同的sqlSession对象去获取mapper来查询一摸一样的数据,仍然会与数据库交互。

  • sqlSession相同,查询条件不同

刚刚上述的运行结果正验证了这一点,一个查询所有,一个按照id=2查询,分别执行了SQL,后面一级缓存便更新成了按照id=2查询的结果。

  • sqlSession相同,两次查询之间执行了增删改操作
    //测试按照id查询用户selectUserById()
    @Test
    public void testSelectUserById() {
        System.out.println("测试按照id查询用户selectUserById()");
        User user = mapper.selectUserById(2);
        System.out.println(user);

        User user3 = new User(5, "赵六", "2333");
        mapper.insertUser(user3);
        session.commit();//提交事务  原生jdbc则需要开启事务执行sql后提交事务

        User user2 = mapper.selectUserById(2);
        System.out.println(user);
        //比较两次 按照id查询 的结果集
        System.out.println(user == user2);
    }

输出,重新查询了数据库

此时缓存已失效,因为增删改可能对数据产生影响,造成数据库与缓存不一致的可能。

DEBUG [main] - ==>  Preparing: select * from user where id = ?; 
DEBUG [main] - ==> Parameters: 2(Integer)
TRACE [main] - <==    Columns: id, name, pwd
TRACE [main] - <==        Row: 2, 李四, ksqo
DEBUG [main] - <==      Total: 1
User{id=2, name='李四', pwd='ksqo'}
DEBUG [main] - ==>  Preparing: insert into user(id,name,pwd) values (?,?,?); 
DEBUG [main] - ==> Parameters: 5(Integer), 赵六(String), 2333(String)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - ==>  Preparing: select * from user where id = ?; 
DEBUG [main] - ==> Parameters: 2(Integer)
TRACE [main] - <==    Columns: id, name, pwd
TRACE [main] - <==        Row: 2, 李四, ksqo
DEBUG [main] - <==      Total: 1
User{id=2, name='李四', pwd='ksqo'}
false
  • sqlSession相同,手动清除一级缓存
session.clearCache();//手动清除缓存
2.3 一级缓存流程图

图源百度

3.MyBatis中的二级缓存

由上述可看到MyBatis中一级缓存的局限性,条件太苛刻(很容易失效),性能提升十分有限。

MyBatis中的二级缓存(也称全局缓存) 基于namespace级别的缓存,一个名称空间,对应一个二级缓存(全局缓存);

3.1开启二级缓存(简要版)

要启用全局的二级缓存,只需要在你的 SQL 映射文件(***Mapper.xml)中添加:

    <!--当前mapper.xml中使用二级缓存-->
    <cache/>

然后在MyBatis-config.xml中的settings添加全局开启缓存

    <settings>
        <!-- 开启缓存(默认开启)-->
        <setting name="cacheEnabled" value="true"/>
    </settings>

官方文档有对应的说明(其实默认就是开启的):

注意这个设置是: 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。

也就是说,没有加cache元素的xml映射文件依旧是默认的一级缓存!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-82uQBNa1-1589514753471)(D:\博客markdown文件\CSDN\应用框架\MyBatis\MyBatis笔记(五)]——缓存.assets\image-20200515104647231.png)

3.2 二级缓存效果与工作机制

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

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

工作机制:

  • 在一级缓存的基础上如果会话关闭了,一级缓存的数据就会被保存到二级缓存中
  • 新的会话查询信息可以从二级缓存里获取内容**(如果一级缓存没有的话)**
  • 不同mapper之间的数据是独立的**(即二级缓存的作用域是namespace级别,存在各自的map中)**
3.3 自定义设置二级缓存cache元素属性

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

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

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

可用的清除策略有:

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

默认的清除策略是 LRU。

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

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

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

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

3.4 具体的mapper映射xml文件编写举例

在MyBatis-config.xml中的settings添加全局开启缓存(其实默认就是开启的):

    <settings>
        <!-- 开启缓存(默认开启)-->
        <setting name="cacheEnabled" value="true"/>
    </settings>

UserMapper接口:

//操作数据库的接口
public interface UserMapper {

    //查询所有User
    List<User> selectUser();

    //按照id查询User
    User selectUserById(int id);
}

UserMapeer.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接口-->
<mapper namespace="com.piao.dao.UserMapper">

    <!--当前mapper.xml中使用二级缓存
    eviction="FIFO" 清除策略选择先进先出
    flushInterval="60000" 每隔60s刷新
    size="512" 最多可以存储结果对象或列表的 512 个引用,
    readOnly="true" 返回的对象被认为是只读的
    -->
    <cache eviction="FIFO"
            flushInterval="60000"
            size="512"
            readOnly="true"/>

    <!--    parameterType参数类型-->
    <!--    传参方式: #{id}  id为selectUserById方法中的int类型参数id selectUserById(int id);-->
    <select id="selectUserById" parameterType="int" resultType="com.piao.User">
        select * from user where id = #{id};
    </select>
</mapper>

测试

    //测试二级缓存
    @Test
    public void testSelectUserById() {
        SqlSession session = MybatisUtils.getSession();
        User5Mapper mapper = session.getMapper(User5Mapper.class);
        SqlSession session2 = MybatisUtils.getSession();
        User5Mapper mapper2 = session2.getMapper(User5Mapper.class);
        SqlSession session3 = MybatisUtils.getSession();
        User5Mapper mapper3 = session3.getMapper(User5Mapper.class);

        User user = mapper.selectUserById(2);
        System.out.println(user);
        session.close();//关闭第一个sqlsession 会将一级缓存提交到二级缓存

        User user2 = mapper2.selectUserById(2);
        System.out.println(user);
        //比较两次 按照id查询 的结果集
        System.out.println(user == user2);
        session2.close();

        User user3 = mapper3.selectUserById(2);
        System.out.println(user);
        //比较两次 按照id查询 的结果集
        System.out.println(user == user3);
        session3.close();
    }

测试输出:可看到第一个SsqlSession关闭后将一级缓存提交到二级缓存,后面两个sqlsession从二级缓存中拿到id=2的数据,三次查询出的结果集User都是同一个。

同时可看到日式的输出有Cache Hit Ratio 缓存命中率:

命中率=从缓存中读取数据的次数/所有访问数据次数(磁盘读取次数+缓存读取次数)

第一次是0,因为二级缓存里没有数据。第二次为0.5,因为第二次是从缓存拿到的数据,第三次是0.66666,说明也是从缓存中拿到的数据。就是只有第一次是从MySQL读取(磁盘读取),整个过程只执行一次SQL

DEBUG [main] - Cache Hit Ratio [com.piao.dao.User5Mapper]: 0.0
DEBUG [main] - ==>  Preparing: select * from user where id = ?; 
DEBUG [main] - ==> Parameters: 2(Integer)
TRACE [main] - <==    Columns: id, name, pwd
TRACE [main] - <==        Row: 2, 李四, ksqo
DEBUG [main] - <==      Total: 1
User{id=2, name='李四', pwd='ksqo'}
DEBUG [main] - Cache Hit Ratio [com.piao.dao.User5Mapper]: 0.5
User{id=2, name='李四', pwd='ksqo'}
true
DEBUG [main] - Cache Hit Ratio [com.piao.dao.User5Mapper]: 0.6666666666666666
User{id=2, name='李四', pwd='ksqo'}
true

结果也验证说明二级缓存是跨sqlsession的,作用域是namespace级别!

3.5 二级缓存流程图

图源百度

先走一级缓存,(如果开启二级缓存的话)再走二级缓存,(如果有第三方缓存)再走第三方缓存,没有就走数据库

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值