@[TOC]
前言
SpringBoot默认情况下是整合了EhCache的,但是默认整合的EhCache的2.x版本,本文依然整合EhCache的3.x版本。
构建SpringBoot工程
导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
</dependencies>
准备EhCache的配置项
# 准备EhCache基础配置项
ehcache:
heap: 1000 # 堆内内存缓存个数
off-heap: 10 # 对外内存存储大小 MB
disk: 20 # 磁盘存储数据大小 MB
diskDir: D:/data/ # 磁盘存储路径
cacheNames: # 基于CacheManager构建多少个缓存
- user
- item
- card
引入配置文件中的配置项
@Component
@ConfigurationProperties(prefix = "ehcache")
public class EhCacheProps {
private int heap;
private int offheap;
private int disk;
private String diskDir;
private Set<String> cacheNames;
}
配置CachaManager
@Configuration
@EnableCaching
public class EhCacheConfig {
@Autowired
private EhCacheProps ehCacheProps;
@Bean
public CacheManager ehCacheManager(){
//1. 缓存名称
Set<String> cacheNames = ehCacheProps.getCacheNames();
//2. 设置内存存储位置和数量大小
ResourcePools resourcePools = ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(ehCacheProps.getHeap())
.offheap(ehCacheProps.getOffheap(), MemoryUnit.MB)
.disk(ehCacheProps.getDisk(),MemoryUnit.MB)
.build();
//3. 设置生存时间
ExpiryPolicy expiry = ExpiryPolicyBuilder.noExpiration();
//4. 设置CacheConfiguration
// baseObject是一个POJO类实现了序列化接口
CacheConfiguration cacheConfiguration = CacheConfigurationBuilder
.newCacheConfigurationBuilder(String.class, BaseObject.class, resourcePools)
.withExpiry(expiry)
.build();
//5. 设置磁盘存储的位置
CacheManagerBuilder<PersistentCacheManager> cacheManagerBuilder =
CacheManagerBuilder.newCacheManagerBuilder().with(CacheManagerBuilder.persistence(ehCacheProps.getDiskDir()));
//6. 缓存名称设置好。
for (String cacheName : cacheNames) {
cacheManagerBuilder.withCache(cacheName,cacheConfiguration);
}
//7. 构建
return cacheManagerBuilder.build();
}
}
Cache注解使用
Cache注解是JSR规范中的,Spring支持这种注解。前面配置好关于CacheManager之后,就可以在Service层添加Cache注解,实现缓存使用,缓存更新,缓存清除。
基本使用
这个是查询缓存的注解,可以加在方法上,也可以加在类上(不建议添加在类上,这样很多细粒度配置就无法实现,比如@Transactional),可以在执行当前方法前,根据注解查看方法的返回内容是否已经被缓存,如果已经缓存,不需要执行业务代码,直接返回数据。如果没有命中缓存,正常执行业务代码,在执行完毕后,会将返回结果作为缓存,存储起来。
直接在Service层的方法上添加@Cacheable,注意,必须填写@Cacheable中的value或者cacheName属性
默认情况下,每次查询会基于Key(默认是方法的参数)去查看是否命中缓存
- 如果命中缓存,直接返回
- 如果未命中缓存,正常执行业务代码,基于方法返回结果做缓存
key的声明方式
key的声明方式有两种,一种是基于Spring的Expression Language去实现,另一种是基于编写类的方式动态的生成key
Spel表达式语言实现
@Override
@Cacheable(cacheNames = {
"item"},key = "#id") // 123
public String echo(String id,String... args) {
System.out.println("查询数据库~");
// itemMapper.findById(id);
return id;
}
这种方式要基于Spel实现,但是Spel用的不多,单独为了这种操作熟悉Spel成本蛮高的,而且功能并不丰富,所以更推荐第二种方式,编写类的方式设置key的生成策略
KeyGenerator实现
这种方式需要在Spring容器中构建KeyGenerator实现类,基于注解配置进去即可
设置key的生成策略。
@Configuration
public class CacheKeyGenerator {
@Bean(name = "itemKeyGenerator")
public KeyGenerator itemKeyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName() + params[0];
}
};
}
}
设置bean name到keyGenerator中
@Override
@Cacheable(cacheNames = {
"item"},keyGenerator = "itemKeyGenerator")
public String echo(String id,String... args) {
System.out.println("查询数据库~");
// itemMapper.findById(id);
return id;
}
缓存条件
在执行方法前后,判断当前数据是否需要缓存,所以一般基础参数的判断。
- 条件为true代表缓存(condition)
- 条件为false代表缓存(unless)
都可以基于Spel编写条件表达式
condition
在执行方法前,决定是否需要缓存
可以在condition中编写Spel,只要条件为true,既代表当前数据可以缓存
@Override
@Cacheable(cacheNames = {
"item"},condition = "#id.equals(\"123\")")
public String echo(String id) {
System.out.println("查询数据库~");
// itemMapper.findById(id);
return id;
}
unless
执行方法之后,决定是否需要缓存
unless也可以编写Spel,条件为false时,代表数据可以缓存,如果为true,代表数据不需要缓存
@Override
@Cacheable(cacheNames = {
"item"},unless = "#result.equals(\"123\")")
public String echo(String id) {
System.out.println("查询数据库~");
// itemMapper.findById(id);
return id;
}
更多的其实还是在执行查询前,来判断数据是否需要缓存。如果真的需要做,也是避免诡异的操作。
比如Service在出现异常结果时,返回-1,那么这种-1,就不需要缓存。
condition&unless的优先级
condition和unless都是代表是都需要缓存数据。
如果同时设置condition和unless。
- condition,unless
- true,false:unless代表不缓存,那就不缓存
- true,true:都代表缓存,那就缓存
- false,false:都不让缓存, 那就不缓存
- false,true:condition代表不缓存数据,那就不缓存
condition和unless没有优先级之分,他的优先级在于,不缓存的优先级高于缓存。
sync
缓存击穿问题。
当多个线程并发访问一个Service方法时,发现当前方法没有缓存数据,此时会让一个线程去执行业务代码查询数据,扔到缓存中,后面线程再查询缓存
可以设置sync属性为true,代表当执行Service方法时,发现缓存没数据,那么就需要去竞争锁资源去执行业务代码,后续线程等待前置线程执行完,再去直接查询缓存即可
@Override
@Cacheable(cacheNames = {
"item"},sync = true)
public String echo(String id) {
System.out.println("查询数据库~");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return id;
}
@CachePut
@CachePut注解是在写数据之后,更新缓存的数据
在增删改的操作上追加@CachePut注解,会根据key去重置指定的缓存。
细节点就在于对标上查询方法的key
@Override
@CachePut(cacheNames = "item",key = "#item.id")
public String write(Item item) {
// 写id为123的数据
System.out.println("123被改成456");
return "456";
}
@CacheEvict
@CacheEvict是用来清除缓存的,可以根据注解里的cacheNames和key来清除指定缓存,也可以清除整个cacheNames中的全部缓存
清除指定缓存
@Override
@CacheEvict(value = "item")
public void clear(String id) {
System.out.println("清除缓存成功!");
}
清除全部缓存
@Override
@CacheEvict(value = "item",allEntries = true)
public void clearAll() {
System.out.println("清除item中的全部缓存~!");
}
如果执行清除缓存过程中,业务代码出现异常,会导致无法正常清除缓存,可以设置一个属性来保证在方法业务执行之前,就将缓存正常清除beforeInvocation设置为true
@Override
@CacheEvict(value = "item",allEntries = true,beforeInvocation = true)
public void clearAll() {
int i = 1 / 0;
System.out.println("方法执行前,清除item中的全部缓存~!");
}
@Caching
一个组合数据,可以基于Caching实现@Cacheable,@CachePut以及@CacheEvict三个注解