SpringBoot默认情况下是整合了EhCache的,但是默认整合的EhCache的2.x版本,本文依然整合EhCache的3.x版本。
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.ehcachegroupId>
<artifactId>ehcacheartifactId>
<version>3.8.1version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
dependencies>
# 准备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;
}
@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注解是JSR规范中的,Spring支持这种注解。前面配置好关于CacheManager之后,就可以在Service层添加Cache注解,实现缓存使用,缓存更新,缓存清除。
这个是查询缓存的注解,可以加在方法上,也可以加在类上(不建议添加在类上,这样很多细粒度配置就无法实现,比如@Transactional),可以在执行当前方法前,根据注解查看方法的返回内容是否已经被缓存,如果已经缓存,不需要执行业务代码,直接返回数据。如果没有命中缓存,正常执行业务代码,在执行完毕后,会将返回结果作为缓存,存储起来。
直接在Service层的方法上添加@Cacheable,注意,必须填写@Cacheable中的value或者cacheName属性
默认情况下,每次查询会基于Key(默认是方法的参数)去查看是否命中缓存
key的声明方式有两种,一种是基于Spring的Expression Language去实现,另一种是基于编写类的方式动态的生成key
@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的生成策略
这种方式需要在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;
}
在执行方法前后,判断当前数据是否需要缓存,所以一般基础参数的判断。
都可以基于Spel编写条件表达式
在执行方法前,决定是否需要缓存
可以在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也可以编写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没有优先级之分,他的优先级在于,不缓存的优先级高于缓存。
缓存击穿问题。
当多个线程并发访问一个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注解,会根据key去重置指定的缓存。
细节点就在于对标上查询方法的key
@Override
@CachePut(cacheNames = "item",key = "#item.id")
public String write(Item item) {
// 写id为123的数据
System.out.println("123被改成456");
return "456";
}
@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实现@Cacheable,@CachePut以及@CacheEvict三个注解