欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring Boot2(二):使用Spring Boot2集成Mybatis缓存机制

程序员文章站 2023-10-18 22:38:21
前言 学习SpringBoot集成Mybatis的第二章,了解到Mybatis自带的缓存机制,在部署的时候踩过了一些坑。在此记录和分享一下Mybatis的缓存作用。 本文章的源码再文章末尾 什么是查询缓存 MyBatis有一级缓存和二级缓存。记录可以看下这篇博文: [聊聊MyBatis缓存机制]: ......

前言

学习springboot集成mybatis的第二章,了解到mybatis自带的缓存机制,在部署的时候踩过了一些坑。在此记录和分享一下mybatis的缓存作用。

本文章的源码再文章末尾

什么是查询缓存

mybatis有一级缓存和二级缓存。记录可以看下这篇博文:

一级缓存

首先看一下什么是一级缓存,一级缓存是指sqlsession。一级缓存的作用域是一个sqlsession。mybatis默认开启一级缓存。

在同一个sqlsession中,执行相同的查询sql,第一次会去查询数据库,并写到缓存中;第二次直接从缓存中获取。当执行sql查询前后发生增删改操作时,则sqlsession的缓存清空。

具体可以看这段代码:

@test
public void testlocalcachescope() throws exception {
        sqlsession sqlsession1 = factory.opensession(true); 
        sqlsession sqlsession2 = factory.opensession(true); 

        studentmapper studentmapper = sqlsession1.getmapper(studentmapper.class);
        studentmapper studentmapper2 = sqlsession2.getmapper(studentmapper.class);

        system.out.println("studentmapper读取数据: " + studentmapper.getstudentbyid(1));
        system.out.println("studentmapper读取数据: " + studentmapper.getstudentbyid(1));
        system.out.println("studentmapper2更新了" + studentmapper2.updatestudentname("小岑",1) + "个学生的数据");
        system.out.println("studentmapper读取数据: " + studentmapper.getstudentbyid(1));
        system.out.println("studentmapper2读取数据: " + studentmapper2.getstudentbyid(1));
}

开启两个sqlsession

Spring Boot2(二):使用Spring Boot2集成Mybatis缓存机制

从打印日志可以看出,前面两个说明sqlsession1的会话缓存生效了,第三个对sqlsession2会话执行了更新操作,这时候数据库发生数据变化,sqlsession2被清空。可是在执行第四个查询是,是查询的sqlsession1会话,由于sqlsession1没有被清空,所以还是查询的缓存的数据,是数据更新之前的,查询的是脏数据,一级缓存sqlsession是不共享的。证明了一级缓存只是在数据库会话内部共享的。

二级缓存

mybatis的二级缓存是指mapper映射文件。二级缓存的作用域是同一个namespace下的mapper映射文件内容,多个sqlsession共享,mybatis需要手动设置二级缓存。

在同一个namespace下的mapper文件中,执行相同的查询sql,第一次会查询数据库,并写道缓存中;第二次z直接从缓存中获取。当执行sql查询前后发生增删改操作时,则二级缓存清空。

上面说到二级缓存可以共享多个sqlsession。可以解决不同sqlsession回话中查询到脏数据的问题了。

springboot整合mybatis开启二级缓存

首先,mybatis默认是开启一级缓存的,即同一个sqlsession每次查询都会去缓存中查询,没有数据的话,再去数据库获取数据。但是,整合到springboot中后,一级缓存就会被关闭。为什么会出现这种原因呢,可以看下这篇文章:

好了,现在来创建项目,可以根据前一篇文章来创建项目,在这基础上修改

pom.xml新增mybatis缓存包caches

<dependency>
    <groupid>org.mybatis.caches</groupid>
    <artifactid>mybatis-ehcache</artifactid>
    <version>1.1.0</version>
</dependency>

sysuserdao.xml添加开启mybatis二级缓存

<cache />

加上这个标签,二级缓存就会开启,他的默认属性如下

  • 映射语句文件中的所有 select 语句将会被缓存。
  • 映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
  • 缓存会使用 least recently used(lru,最近最少使用的)算法来收回。
  • 根据时间表(比如 no flush interval,没有刷新间隔), 缓存不会以任何时间顺序来刷新。
  • 缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
  • 缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

    也可以自定义二级缓存的属性,例如:

<cache
  eviction="fifo"
  flushinterval="60000"
  size="512"
  readonly="true"/>

这个更高级的配置创建了一个 fifo 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会 导致冲突。

​ 可用的收回策略有:

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

默认的是 lru。

flushinterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒 形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。

​ size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的 可用内存资源数目。默认值是 1024。

​ readonly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓 存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存 会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是 false。

测试验证

编写controller接口

/**
 * 查询所有用户信息
 * @return
 */
@requestmapping("/getall")
private list<sysuserentity> getuser() {
    list<sysuserentity> userlist = sysuserservice.queryuserall();
    return userlist;
}

/**
 * 根据userid查询用户信息
 * @return
 */
@requestmapping("/getuser")
private list<sysuserentity> getuser(@requestparam(value = "userid", required = false) long userid) {
    list<sysuserentity> userlist = sysuserservice.queryuserinfo(userid);
    return userlist;
}

/**
 * 更新用户信息
 * @param user
 * @return
 */
@requestmapping("/updateuser")
private int updateuser(@requestbody sysuserentity user) {
    return sysuserservice.updateuserinfo(user);
}

通过postman发送接口请求进行测试:

  • 1、发送查询用户全部信息:http://localhost:8080/getall

  • 2、根据userid查询用户信息:http://localhost:8080/getuser?userid=1

  • 3、更新用户信息http://localhost:8080/updateuser

    更新用户信息接口发送报文:

{
    "userid":5,
    "email":"12321321",
    "mobile":"11111111111213"
}

通过日志可以看到,第一次发送1接口请求,对数据库进行了查询

Spring Boot2(二):使用Spring Boot2集成Mybatis缓存机制

可以看到,第二次和第三次查询没有查询数据库的sql打印,而是去数据库获取数据

此时发送3接口,进行更新操作,在发送1接口,查询改用户的数据

Spring Boot2(二):使用Spring Boot2集成Mybatis缓存机制

可以看到,当执行数据库更新操作后,再进行查询,此时缓存已经清空,需要从数据库中重新查询获取。

这就演示了springboot整合mybatis的缓存机制测试。

总结

1、缓存的对象必须实现序列化。因为二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,所以需要给缓存的对象执行序列化,才可以确保获取无误。

2、mybatis的二级缓存相比于一级缓存来说,实现了sqlsession之间的缓存数据的共享,做到namespace级别,粒度更细

3、在分布式环境下,由于默认的mybatis cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将mybatis的cache接口实现,有一定的开发成本,直接使用redis、memcached等分布式缓存可能成本更低,安全性也更高。

不过建议mybatis的缓存特性再生产环境下进行关闭,单纯作为一个

使用可能更加合适。

下篇文章计划写springboot整合mybatis,使用redis实现缓存基本配置。


关于作者:

个人博客:

github主页:

github博客:

掘金:

博客园:

知乎:

微博:

公众号:鸟不拉屎

Spring Boot2(二):使用Spring Boot2集成Mybatis缓存机制