【快速学习系列】Mybatis缓存和使用SpringBoot开启MyBatis缓存+ehcache
Mybatis缓存
MyBatis一级缓存
Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。
tips:MyBatis一级缓存是默认开启的
示例
写了一个分页查询的功能
测试类
@Test
public void findUserArrays() {
SqlSession sqlSession = MyBatisUtil.createSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
//测试mybatis一级缓存
UserDao userDao1 = sqlSession.getMapper(UserDao.class);
List<User> userList = userDao.findUserArrays(new Integer[]{1, 2}, 1);
//调用两次方法但实际sql只执行一次(同sqlsession情况下且请求的方法和参数一致)
List<User> userList1 = userDao1.findUserArrays(new Integer[]{1, 2}, 1);
for (User user : userList) {
System.out.println(user.toString());
}
MyBatisUtil.closeSqlSession(sqlSession); //释放资源
}
结果
MyBatis二级缓存
MyBatis的二级缓存是**Application级别(也就是同一个SqlSessionFactory)**的缓存,它可以提高对数据库查询的效率,以提高应用的性能。
tips:MyBatis二级缓存是默认不开启的
开启二级缓存
SqlSessionFactory层面上的二级缓存默认是不开启的,二级缓存的开席需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。 也就是要求实现Serializable接口,配置方法很简单,只需要在映射XML文件配置就可以开启缓存了
步骤
1、在 MyBatis的配置文件 mybatis-config.xml中加入
<configuration>
<settings>
<!-- 开启mybatis二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
2、在mapper.xml中开启二级缓存,mapper.xml下的sql执行完成会存储到它的缓存区
<!--回收策略为先进先出,每隔60秒刷新一次,最多缓存512个引用对象,只读-->
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
<!--
参数说明:
eviction:
LRU 最近最少使用的:移除最长时间不被使用的对象
FIFO 先进先出:按对象进入缓存的顺序来移除它们
SOFT 软引用:移除基于垃圾回收器状态和软引用规则的对象
WEAK 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
flushInterval :刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新(mia毫秒单位)
size :引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024
readOnly :(只读)属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false
-->
select,insert,update标签中的缓存属性
useCache=false,禁用二级缓存
<!-- 刷新缓存(每次执行相应的sql语句时) -->
flushCache=true 刷新缓存 ,一般用于insert,update
tips:这里我们可以看到,当设置二级缓存时,策略是控制整个mapper文件的,其实这样是非常不灵活的,我们更希望能够控制每一个业务方法都能够有不同的缓存策略,这样使用起来也是更符合实际开发的需要,所以下面的就是相对来说更好的一种缓存办法
使用SpringBoot开启MyBatis缓存+ehcache
事实上MyBatis自带的缓存还是有着一定缺陷的,而我们使用第三方的ehcache缓存则会更灵活
步骤
导入jar包
添加maven依赖
pom.xml
<!--添加缓存-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
设置ehcache.xml配置
在项目的resources下新建一个名为ehcache.xml的配置文件,
然后把下面的代码粘过去即可
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--指定一个文件目录,当EhCache把数据写到硬盘上时,将把数据写到这个文件目录下
user.home : 用户主目录
user.dir : 用户当前工作目录
java.io.tmpdir : 默认临时文件路径
-->
<diskStore path="java.io.tmpdir/Tmp_EhCache"/>
<!--
name: 缓存名称
eternal: true表示对象永不过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false
timeToIdleSeconds: 设定允许对象处于空闲状态的最长时间,以秒为单位。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清空。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地处于空闲状态
timeToLiveSeconds: 设定对象允许存在于缓存中的最长时间,以秒为单位。当对象自从被存放到缓存中后,如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清除。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地存在于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds属性,才有意义
maxElementsInMemory: 内存中最大缓存对象数;maxElementsInMemory界限后,会把溢出的对象写到硬盘缓存中。注意:如果缓存的对象要写入到硬盘中的话,则该对象必须实现了Serializable接口才行
memoryStoreEvictionPolicy: 当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)
maxElementsOnDisk: 硬盘中最大缓存对象数,若是0表示无穷大
overflowToDisk: 是否保存到磁盘,当系统宕机时
diskPersistent: 是否缓存虚拟机重启期数据,是否持久化磁盘缓存,当这个属性的值为true时,系统在初始化时会在磁盘中查找文件名为cache名称,后缀名为index的文件,这个文件中存放了已经持久化在磁盘中的cache的index,找到后会把cache加载到内存,要想把cache真正持久化到磁盘,写程序时注意执行net.sf.ehcache.Cache.put(Element element)后要调用flush()方法
diskSpoolBufferSizeMB: 这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
diskExpiryThreadIntervalSeconds: 磁盘失效线程运行时间间隔,默认为120秒
clearOnFlush: 内存数量最大时是否清除
-->
<!--defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则默认缓存策略-->
<defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="true" diskPersistent="true" timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>
<!-- cache:自定义缓存策略,自己可以写多个然后针对不同的sql语句用于不同的情况中 -->
<cache
name="myCache"
eternal="false"
maxElementsInMemory="200"
overflowToDisk="false"
diskPersistent="true"
timeToIdleSeconds="0"
timeToLiveSeconds="300"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
上面标签可能会爆红,那个先不用管也没问题
使用@EnableCaching注解在启动类上开启缓存
@SpringBootApplication
@EnableCaching //开启缓存
public class XXXXApplication {
public static void main(String[] args) {
SpringApplication.run(XXXXApplication.class, args);
}
}
配置yml文件中缓存设置
首先需要开启MyBatis的二级缓存
# mybatis相关配置
mybatis:
mapper-locations: classpath:mappers/*.xml
type-aliases-package: com.r.springboot1.pojo # xml映射文件中实体类起别名
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 在控制台打印日志信息
cache-enabled: true # 开启MyBatis的二级缓存
然后在配置中来读取我们刚刚写的缓存文件ehcache.xml
spring:
cache:
ehcache:
config: classpath:ehcache.xml # 读取ehcache.xml缓存策略文件
Spring Cache注解使用
因为我们是使用注解来实现缓存,所以像原来在dao层映射文件中来配置缓存是没必要的,所以我们需要在Service的实现层来写缓存注解(注解可以是类上也可以是方法上)
@Cacheable
tips:如果使用时只写注解而不指定是哪个缓存策略的话,@Cacheable会自动执行默认策略😅
可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。
@Cacheable可以指定三个属性,value、key(使用key是为了让缓存数据之间能够有所区分,这样不会使其混乱)和condition (过滤条件)
不同配置方法示例
@Cacheable("myCache1") //Cache是发生在ehcache.xml中myCache1上的
public User find(Integer id) {
....
}
@Cacheable({"cache1", "cache2"}) //Cache是发生在ehcache.xml中cache1和cache2上的(可以同时设置多个)
public User find(Integer id) {
.....
}
//自定义策略是指我们可以通过Spring的EL表达式来指定我们的key
//#id指参数id作为key(按id区分同一个缓存策略中的不同缓存信息,使用唯一的id则不会使缓存混到一起出现问题)
@Cacheable(value="myCache1", key="#id")
public User find(Integer id) {
...
}
//#p0标识第一个参数作为key(p相当于param,#p0、#p1、#p2...依次代表第一个、第二个和第三个参数,以此类推)
@Cacheable(value="myCache1", key="#p0")
public User find(Integer id) {
.....
}
//#user.user_id表示对象user属性user_id作为key(可以用对象的属性作为key用来区分,当然id还是唯一的字段,使用起来不会使缓存混乱)
@Cacheable(value="myCache1", key="#user.user_id")
public User find(User user) {
.....
}
//也可以混着这样用
@Cacheable(value="myCache1", key="#p0.user_id")
public User find(User user) {
.....
}
Spring还为我们提供了一个root对象可以用来生成key
示例 | 描述 |
root.methodName | 当前方法名 |
root.method.name | 当前方法 |
root.target | 当前被调用的对象 |
root.targetClass | 当前被调用的对象的class |
root.args[0] | 当前方法参数组成的数组 |
root.caches[0].name | 当前被调用的方法使用的Cache |
condition使用
//表示只有当user的id为偶数时才会进行缓存
@Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
public User find(User user) { ... }
使用key的情景
key不是什么时候都适合使用
当要显示一些共享的缓存信息时,没有必要使用key(key适用于将缓存信息独立分离开进行缓存的情况)
在一些未知且经常变化的参数上,没有必要使用key(key适用于缓存一些有限的、且唯一的参数字段上,就比如id)
注意事项
使用的方法实体类必须经过序列化,否则会报错
报错显示所用的实体类为进行序列化,但同时也证明缓存的确是在运行着
给实体类序列化示例
implements Serializable即可
public class Provider implements Serializable {}
然后运行自己的方法后我们可以在控制台看到,虽然多次请求,但因为我们设置了缓存,会发现sql语句只运行了一次,这也说明我们的缓存设置成功
@CachePut
使用@CachePut时我们可以指定的属性跟@Cacheable是一样的
@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中
也就是说@Cacheable每次执行前都会先看一下之前是否有缓存,如果有就用原来存在的缓存,而@CachePut每次执行都直接刷新缓存,不会看之前是否存在缓存
@CacheEvict
清除缓存, 可以指定的属性有value、key、condition、allEntries、beforeInvocation
tips:一般用于增删改上,因为我们定义的缓存策略中可能存在当我们增删改时还没有过缓存时间,所以这时候就会显示还没有改的样子,所以我们为了能够改变后即刻刷新缓存,所以需要用到这个
使用示例
@CacheEvict(value="myCache",key="#p0.user_id")
public int updUser(SfUser user) throws Exception {
return sfUserMapper.updUser(user);
}
//allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素,这比一个一个清除元素更有效率(false时指执行方法时,我只清除对应id方法的这一条,true是执行方法时,所有的缓存都清掉,当再次执行有@Cacheable的方法时缓存再被再次加入)
@CacheEvict(value="users", allEntries=true)
public void delete(Integer id) {
System.out.println("delete user by id: " + id);
}
tips:如果使用@CacheEvict的时候不设置key,那么它会清除掉当前所有的缓存(当然清楚的是你设置的当前方法中配置的相应缓存策略,并不是说所有的完完全全的缓存全清除了😁)