手打不易,如果转摘,请注明出处!
注明原文:https://zhangxiaofan.blog.csdn.net/article/details/129832925
目录
前言
版本
配置通用说明
项目结构
代码
启动类
实体类
基础使用——增删改查(@Cached、@CacheInvalidate、@CacheUpdate)
基础使用——@CreateCache注解和手动方式
基础使用——@CacheRefresh 详解
前言
最近有个项目用到jetcache,正好用到,加上spring-boot在使用jetcache的时候会有一些需要注意的坑,下面通过基础使用来给大家简单介绍。
下面有一些示例代码,都有注释,这些注释可以仔细阅读一下!
版本
本篇的jetcache、jedis等版本如下:
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.1.0</version>
</dependency>
完整的maven的pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring-boot-redis-jetcache-base</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>spring-boot-redis-jetcache-base</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<exclusions>
<exclusion>
<artifactId>aspectjweaver</artifactId>
<groupId>org.aspectj</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.25</version>
</dependency>
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>5.4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.0.RELEASE</version>
</plugin>
</plugins>
</build>
</project>
配置文件 application.yml 如下:
server:
servlet:
context-path: /
port: 8081
# 官方表示,不会支持 redisson 客户端, 仅支持 jedis 和 lettuce 链接:https://github.com/alibaba/jetcache/issues/634
jetcache:
statIntervalMinutes: 10 # 默认值0,统计间隔,0表示不统计
areaInCacheName: false # 默认值false, 是否将 areaName 作为远程缓存key前缀
# 本地
local:
# 默认分组配置,可以创建多个,对应@Cached和@CreateCache的 area 属性, 默认名就是 'default'
default:
type: caffeine # 可选 linkedhashmap,caffeine
keyConvertor: fastjson # 指定KEY的转换方式, 可选 fastjson2,fastjson,jackson
# 远程
remote:
default:
type: redis
keyConvertor: fastjson # 指定KEY的转换方式, 可选 fastjson2,fastjson,jackson
valueEncoder: java # 可选 java,kryo,kryo5
valueDecoder: java # 可选 java,kryo,kryo5
poolConfig:
minIdle: 5
maxIdle: 20
maxTotal: 50
host: 127.0.0.1
port: 6379
password: 123456
配置通用说明
参考官网:https://github.com/alibaba/jetcache/blob/master/docs/CN/Config.md
属性 | 默认值 | 说明 |
---|---|---|
jetcache.statIntervalMinutes | 0 | 统计间隔,0表示不统计 |
jetcache.areaInCacheName | true(2.6-) false(2.7+) | jetcache-anno把cacheName作为远程缓存key前缀,2.4.3以前的版本总是把areaName加在cacheName中,因此areaName也出现在key前缀中。2.4.4以后可以配置,为了保持远程key兼容默认值为true,但是新项目的话false更合理些,2.7默认值已改为false。 |
jetcache.hiddenPackages | 无 | @Cached和@CreateCache自动生成name的时候,为了不让name太长,hiddenPackages指定的包名前缀被截掉 |
jetcache.[local/remote].${area}.type | 无 | 缓存类型。tair、redis为当前支持的远程缓存;linkedhashmap、caffeine为当前支持的本地缓存类型 |
jetcache.[local/remote].${area}.keyConvertor | fastjson2 | key转换器的全局配置,2.6.5+已经支持的keyConvertor:fastjson2 /jackson ;2.6.5-只有一个已经实现的keyConvertor: fastjson 。仅当使用@CreateCache且缓存类型为LOCAL时可以指定为none ,此时通过equals方法来识别key。方法缓存必须指定keyConvertor |
jetcache.[local/remote].${area}.valueEncoder | java | 序列化器的全局配置。仅remote类型的缓存需要指定,2.7+可选java /kryo /kryo5 ;2.6-可选java /kryo |
jetcache.[local/remote].${area}.valueDecoder | java | 序列化器的全局配置。仅remote类型的缓存需要指定,2.7+可选java /kryo /kryo5 ;2.6-可选java /kryo |
jetcache.[local/remote].${area}.limit | 100 | 每个缓存实例的最大元素的全局配置,仅local类型的缓存需要指定。注意是每个缓存实例的限制,而不是全部,比如这里指定100,然后用@CreateCache创建了两个缓存实例(并且注解上没有设置localLimit属性),那么每个缓存实例的限制都是100 |
jetcache.[local/remote].${area}.expireAfterWriteInMillis | 无穷大 | 以毫秒为单位指定超时时间的全局配置(以前为defaultExpireInMillis) |
jetcache.remote.${area}.broadcastChannel | 无 | jetcahe2.7的两级缓存支持更新以后失效其他JVM中的local cache,但多个服务共用redis同一个channel可能会造成广播风暴,需要在这里指定channel,你可以决定多个不同的服务是否共用同一个channel。如果没有指定则不开启。 |
jetcache.local.${area}.expireAfterAccessInMillis | 0 | 需要jetcache2.2以上,以毫秒为单位,指定多长时间没有访问,就让缓存失效,当前只有本地缓存支持。0表示不使用这个功能。 |
项目结构
代码
启动类
import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
import com.alicp.jetcache.anno.config.EnableMethodCache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
// 开启 Cache
@EnableMethodCache(basePackages = "com.myjetcache")
// 如果不用@CreateCache注解可以删除 EnableCreateCacheAnnotation
@EnableCreateCacheAnnotation
@EnableScheduling
public class SpringJetCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringJetCacheApplication.class, args);
}
}
实体类
@Data
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private static final Random RANDOM = new Random();
/**
* 当对象只包含基本数据类型的时候,RADM工具可以直接展示数据. 非基本数据类型的字段, 默认不会展示.
*/
private Integer id;
private String name;
private Integer age;
public static Student getStudent(String id) {
Student student = new Student();
student.setId(Integer.parseInt(id));
student.setName(UUID.randomUUID().toString().substring(0, 3));
student.setAge(RANDOM.nextInt(9999));
return student;
}
}
基础使用——增删改查(@Cached、@CacheInvalidate、@CacheUpdate)
先定义接口
public interface JetCacheBaseService {
Student add(Student student);
void delete(Long id);
void update(Student student);
Student get(Long id);
Student get(Long id, boolean isUseCache);
}
写好实现类
需注意的点:
@Cached——[增]
注意点:
- 由于我们是在方法上使用,该注解缓存的是方法 return 的数据,因此方法返回类型不能是 void
- 缓存执行的是 com.alicp.jetcache.Cache#PUT() , 接口默认是异步存储
@CacheInvalidate——[删]
- 注意点:方法执行后,再删除
@CacheUpdate——[改]
- 注意点:方法执行后,再修改,作者说明: https://github.com/alibaba/jetcache/issues/115
@Cached——[查]
注意点:
- 先查缓存,无缓存则走方法
- 如果注解配合 condition 属性,那么 condition=true: 查缓存, 无缓存则走方法;condition=false: 不查缓存, 直接执行方法(查数据库)
@Slf4j
@Service
public class JetCacheBaseServiceImpl implements JetCacheBaseService {
private final Random random = new Random();
/**
* 增 @Cached
* 缓存执行的是 com.alicp.jetcache.Cache#PUT() , 接口默认是异步存储
*
* @param student spel表达式取值
* @return 待缓存的的数据(注意,这个返回值不能void)
*/
@Override
@Cached(area = "default", name = "my:jetcache:", key = "#student.id", cacheType = CacheType.BOTH,
expire = 3600, localExpire = 10, timeUnit = TimeUnit.SECONDS, cacheNullValue = false)
public Student add(Student student) {
log.info("student:{}", JSON.toJSONString(student));
return student;
}
/**
* 删 @CacheInvalidate
* 方法执行后,再删除,作者 huangli 表示也不会增加这个功能选项, 自主控制粒度即可
*/
@Override
@CacheInvalidate(name = "my:jetcache:", key = "#id")
public void delete(Long id) {
log.info("delete");
}
/**
* 改 @CacheUpdate
* 方法执行后,再修改. 作者说明:https://github.com/alibaba/jetcache/issues/115
*/
@Override
@CacheUpdate(name = "my:jetcache:", key = "#student.id", value = "#student")
public void update(Student student) {
log.info("update:{}", JSON.toJSONString(student));
}
/**
* 查
* 先查缓存,无缓存则走方法
*/
@Override
@Cached(name = "my:jetcache:", key = "#id")
public Student get(Long id) {
log.info("load from db");
// 当缓存不存在, 模拟从数据库查询
return loadFromDb(id);
}
/**
* 查
* condition=true: 查缓存, 无缓存则走方法
* condition=false: 不查缓存, 直接执行方法(查数据库)
*/
@Override
@Cached(name = "my:jetcache:", key = "#id", condition = "#isUseCache==true")
public Student get(Long id, boolean isUseCache) {
// 当 isUseCache 为 false, 或者 缓存不存在的时候, 插叙数据库
log.info("load from db, isUseCache:{}", isUseCache);
// 当缓存不存在, 模拟从数据库查询
return loadFromDb(id);
}
/**
* 模拟从数据库查询
*/
private Student loadFromDb(Long id) {
Student student = new Student();
student.setId(Math.toIntExact(id));
student.setName("load from db");
student.setAge(random.nextInt(100));
return student;
}
}
基础使用——@CreateCache注解和手动方式
由于 @CreateCache 以及标记为@Deprecated了,这里优先将手动方式
我们先看下属性说明:
@CreateCache属性表
参考官网Link:https://github.com/alibaba/jetcache/blob/master/docs/CN/CreateCache.md#createcache%E5%B1%9E%E6%80%A7%E8%A1%A8
属性 | 默认值 | 说明 |
---|---|---|
area | “default” | 如果需要连接多个缓存系统,可在配置多个cache area,这个属性指定要使用的那个area的name |
name | 未定义 | 指定缓存的名称,不是必须的,如果没有指定,会使用类名+方法名。name会被用于远程缓存的key前缀。另外在统计中,一个简短有意义的名字会提高可读性。如果两个@CreateCache 的name 和area 相同,它们会指向同一个Cache 实例 |
expire | 未定义 | 该Cache实例的默认超时时间定义,注解上没有定义的时候会使用全局配置,如果此时全局配置也没有定义,则取无穷大 |
timeUnit | TimeUnit.SECONDS | 指定expire的单位 |
cacheType | CacheType.REMOTE | 缓存的类型,包括CacheType.REMOTE、CacheType.LOCAL、CacheType.BOTH。如果定义为BOTH,会使用LOCAL和REMOTE组合成两级缓存 |
localLimit | 未定义 | 如果cacheType为CacheType.LOCAL或CacheType.BOTH,这个参数指定本地缓存的最大元素数量,以控制内存占用。注解上没有定义的时候会使用全局配置,如果此时全局配置也没有定义,则取100 |
serialPolicy | 未定义 | 如果cacheType为CacheType.REMOTE或CacheType.BOTH,指定远程缓存的序列化方式。JetCache内置的可选值为SerialPolicy.JAVA和SerialPolicy.KRYO。注解上没有定义的时候会使用全局配置,如果此时全局配置也没有定义,则取SerialPolicy.JAVA |
keyConvertor | 未定义 | 指定KEY的转换方式,用于将复杂的KEY类型转换为缓存实现可以接受的类型,JetCache内置的可选值为KeyConvertor.FASTJSON和KeyConvertor.NONE。NONE表示不转换,FASTJSON通过fastjson将复杂对象KEY转换成String。如果注解上没有定义,则使用全局配置。 |
先定义接口
public interface CreateCacheUseMethodService {
Cache<String, String> getStringMethodCache();
}
注意实现类有下面几个要注意的地方:
- refresh属性功能,必须要 QuickConfig 显示创建,不要用 cache.config().setRefreshPolicy() ,否则可能会出现无效的情况
- cache.config().setLoader() 加载器会遍历每一个key,并执行这个加载器;缓存没有key的时候是不会执行的,首次 set/get 都会创建 key
- 由于refresh 会用加载器刷新所有key,那么一定要设置 stopRefreshAfterLastAccess,表示多久不使用对应的key缓存则会停止刷新。
- 本地缓存的时间 < 远程缓存的时间,缓存refresh的时间可以接近远程缓存时间,也可以大于它
代码如下,可以仔细阅读注释:
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.CacheManager;
import com.alicp.jetcache.RefreshPolicy;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.template.QuickConfig;
import com.myjetcache.service.CreateCacheUseMethodService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.UUID;
import javax.annotation.PostConstruct;
@Slf4j
@Service
public class CreateCacheUseMethodServiceImpl implements CreateCacheUseMethodService {
@Autowired
private CacheManager cacheManager;
private Cache<String, String> stringMethodCache;
@PostConstruct
public void init() {
// 注意必须要refreshPolicy() ,才能创建 RefreshCache, 否则自动刷新功能无效
QuickConfig quickConfigStringCache = QuickConfig.newBuilder("myStringCache:use:method:") // 缓存的前缀
.cacheType(CacheType.BOTH) // local 和 remote 组合成两级缓存
.expire(Duration.ofSeconds(3600)) // 远程过期时间
.localExpire(Duration.ofSeconds(5)) // 本地过期时间, 应该小于远程过期时间, 只对CacheType.LOCAL和CacheType.BOTH有效
.localLimit(1000) // 本地缓存的最大元素数量, 默认:100
.cacheNullValue(false) // 是否缓存 NULL 值
.refreshPolicy(getRefreshPolicy()) // 这里必须显式创建, 不要使用 cache.config().setRefreshPolicy(), 否则无效
.build();
stringMethodCache = cacheManager.getOrCreateCache(quickConfigStringCache);
// 刷新执行的加载器, 会遍历刷新每一个key
stringMethodCache.config().setLoader(this::loadFromDb);
}
/**
* 创建刷新策略
* 等于注解:@CacheRefresh(refresh = 10, refreshLockTimeout = 10, stopRefreshAfterLastAccess = 3600, timeUnit = TimeUnit.SECONDS)
*/
private RefreshPolicy getRefreshPolicy() {
RefreshPolicy refreshPolicy = new RefreshPolicy();
// 刷新时间间隔
refreshPolicy.setRefreshMillis(10 * 1000L);
// 类型为 BOTH/REMOTE 的缓存刷新时,同时只会有一台服务器在刷新,这台服务器会在远程缓存放置一个分布式锁,此配置指定该锁的超时时间
// 不管有多少台服务器,同时只有一个服务器在刷新,这是通过 tryLock 实现的
refreshPolicy.setRefreshLockTimeoutMillis(10 * 1000);
// 指定多久未访问后停止自动刷新。 注意:不指定则会一直刷新
refreshPolicy.setStopRefreshAfterLastAccessMillis(3600 * 1000);
return refreshPolicy;
}
/**
* 刷新执行的加载器
* 每个key都有一个刷新任务, 因此必须设置 stopRefreshAfterLastAccess
* 注意: 没有key则不会定时执行数据库加载器, 首次 get/set 都相当于创建了key
*
* @param key 刷新的key
*/
public String loadFromDb(String key) {
// 模拟从数据库读取数据
String uuid = UUID.randomUUID().toString();
log.info("[Use Method] key:{},load cache form db:{}", key, uuid);
return uuid;
}
@Override
public Cache<String, String> getStringMethodCache() {
return stringMethodCache;
}
}
上面是手动创建,接下来我们看下如何使用注解 @CreateCache 创建(不建议使用该注解,因为被作者标记为过期 @Deprecated 了)
先定义一个接口
public interface CreateCacheUseAnnotationService {
Cache<String, String> getStringCache();
}
实现类如下,注解的属性跟上面手动方式的属性可以对应,可以看上面代码的注解。
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CacheRefresh;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.CreateCache;
import com.myjetcache.service.CreateCacheUseAnnotationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
@Slf4j
@Service
public class CreateCacheUseAnnotationServiceImpl implements CreateCacheUseAnnotationService {
/**
* 注解方式
* local 失效后, 会从 redis 查, redis 也没有则会执行数据库加载器
*/
@CreateCache(name = "myStringCache:use:annotation:", cacheType = CacheType.BOTH, expire = 3600, localExpire = 5,
localLimit = 1000)
@CacheRefresh(refresh = 20, refreshLockTimeout = 10, stopRefreshAfterLastAccess = 3600, timeUnit = TimeUnit.SECONDS)
private Cache<String, String> stringCache;
@PostConstruct
public void init() {
// 1.cache.get(),如果local和redis都没有缓存, 则会执行数据库加载器 loadFromDb()
// 2.定时刷新刷新的加载器, 会为每一个key创建刷新任务; 注意: 没有key则不会定时执行数据库加载器, 首次 get/set 都相当于创建了key
stringCache.config().setLoader(this::loadFromDb);
}
/**
* 刷新执行的加载器,jetcache会为每个key都会创建一个刷新任务, 因此必须设置 stopRefreshAfterLastAccess, 否则内存消耗太大
*
* @param key 刷新的key
*/
public String loadFromDb(String key) {
// 模拟从数据库读取数据
String uuid = UUID.randomUUID().toString();
log.info("[Use Annotation] key:{},load cache form db:{}", key, uuid);
return uuid;
}
@Override
public Cache<String, String> getStringCache() {
return stringCache;
}
}
基础使用——@CacheRefresh 详解
需要注意的点如下:
注意 CacheRefresh 会为每个key创建定时任务, 定时来执行这个方法
官方文档:
1.目的是为了防止缓存失效时造成的雪崩效应打爆数据库
2.对key比较少,实时性要求不高,加载开销非常大的缓存场景,适合使用自动刷新
CacheRefresh 刷新机制:
1.如果 CacheType.LOCAL ,那么多个节点会重复刷新。
2.如果 CacheType.REMOTE ,通过在远程缓存中的分布式锁'_#TS#',保证一个周期内只有一个节点执行了刷新操作.
3.如果 CacheType.BOTH ,即两级缓存,通过在远程缓存中的分布式锁,保证一个周期内只有一个节点执行了刷新操作.
注意:仅更新REMOTE,其节点的本地缓存不会更新.jetcache支持给远程和本地缓存设置不同的超时时间,所以可以把本地缓存的超时时间设置短一点.
CachePenetrationProtect 注解作用——当缓存访问【未命中】的情况下,对并发进行的加载行为进行保护;当前版本实现的是单JVM内的保护,即同一个JVM中同一个key只有一个线程去加载,其它线程等待结果
具体代码如下:
public interface RefreshService {
String getRefreshStringCache(String key);
Student getRefreshMapCache(Object obj);
}
实现类:
import com.alibaba.fastjson.JSON;
import com.alicp.jetcache.anno.CachePenetrationProtect;
import com.alicp.jetcache.anno.CacheRefresh;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.Cached;
import com.myjetcache.entity.Student;
import com.myjetcache.service.RefreshService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Service
public class RefreshServiceImpl implements RefreshService {
private final Random random = new Random();
private final AtomicInteger atomicInteger = new AtomicInteger(0);
/**
* 注意 CacheRefresh 会为每个key创建定时任务, 定时来执行这个方法
* 官方文档:
* 1.目的是为了防止缓存失效时造成的雪崩效应打爆数据库
* 2.对key比较少,实时性要求不高,加载开销非常大的缓存场景,适合使用自动刷新
* <p>
* CacheRefresh 刷新机制:
* 1.如果 CacheType.LOCAL ,那么多个节点会重复刷新。
* 2.如果 CacheType.REMOTE ,通过在远程缓存中的分布式锁'_#TS#',保证一个周期内只有一个节点执行了刷新操作.
* 3.如果 CacheType.BOTH ,即两级缓存,通过在远程缓存中的分布式锁,保证一个周期内只有一个节点执行了刷新操作.
* 注意:仅更新REMOTE,其节点的本地缓存不会更新.jetcache支持给远程和本地缓存设置不同的超时时间,所以可以把本地缓存的超时时间设置短一点.
* <p>
* CachePenetrationProtect 注解:
* 当缓存访问【未命中】的情况下,对并发进行的加载行为进行保护.
* 当前版本实现的是单JVM内的保护,即同一个JVM中同一个key只有一个线程去加载,其它线程等待结果
*
* @param key 缓存的 key
* @return 缓存的 value
*/
@Override
@Cached(name = "my:refresh:stringCache.", key = "#key", cacheNullValue = true, cacheType = CacheType.BOTH,
expire = 3600, localExpire = 60, timeUnit = TimeUnit.SECONDS)
@CacheRefresh(timeUnit = TimeUnit.SECONDS, refresh = 10, refreshLockTimeout = 10, stopRefreshAfterLastAccess = 3600)
@CachePenetrationProtect
public String getRefreshStringCache(String key) {
String value;
// 模拟查询数据库, 首次查询返回 null, 以后每次查询,如果缓存不存在,则返回 随机uuid
if (atomicInteger.get() == 0) {
atomicInteger.addAndGet(1);
value = null;
} else {
// 每隔10秒就会刷新新的uuid到 本地和redis
value = UUID.randomUUID().toString();
}
log.info("return value:{}", value);
return value;
}
/**
* Map结构的SpEL表达式参考下面的写法
* 入参是的 SpEL是 key
* return 是存储的 value
*/
@Override
@Cached(area = "default", name = "my:jetcache:", key = "#obj['id']", cacheType = CacheType.BOTH,
expire = 3600, localExpire = 60, timeUnit = TimeUnit.SECONDS, cacheNullValue = false)
@CacheRefresh(timeUnit = TimeUnit.SECONDS, refresh = 10, refreshLockTimeout = 10, stopRefreshAfterLastAccess = 3600)
@CachePenetrationProtect
public Student getRefreshMapCache(Object obj) {
log.info("obj:{}", JSON.toJSONString(obj));
Student student = Student.getStudent(String.valueOf(random.nextInt(100)));
log.info("after refresh:{}", JSON.toJSONString(student));
return student;
}
}
测试类:
@RestController
@Slf4j
public class RefreshController {
private final Random random = new Random();
@Autowired
private RefreshService refreshService;
@GetMapping("/refreshstringcache/get")
public String getStringCache() {
String stringCache = refreshService.getRefreshStringCache("myStringCache");
log.info(stringCache);
return stringCache;
}
@GetMapping("/getrefreshmapcache/get")
public Student getRefreshMapCache() {
Map<String, Object> body = new HashMap<>();
body.put("userId", "1");
body.put("id", "123");
return refreshService.getRefreshMapCache(body);
}
}
上面代码是Jetcache的简单自测,满足一般的业务开发,如果更深入的了解,请到官网学习:https://github.com/alibaba/jetcache/blob/master/docs/CN/Readme.md