文章目录
- ①. Spring Cache概述
- ②. 触发缓存入口 - @Cacheable
- ③. 更新缓存 - CachePut
- ④. 删除缓存 - CacheEvict
- ⑤. 组合操作- Caching
- ⑥. 共享缓存配置 - CacheConfig
- ⑦. 从0搭建缓存项目
①. Spring Cache概述
- ①. 如何找到Spring Cache的官方文档
-
②.Spring 从 3.1开始定义了org.springframework.cache.Cache和 org.springframework.cache.Cache Manager接口来统一不同的缓存技术,并支持使用 JCache(JSR-107)注解简化我们开发
-
③. JSR-107定义了5个核心接口来实现缓存操作,分别是CachingProvider, CacheManager, Cache, Entry和Expiry
-
④. 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取
-
⑤. SpringCache常用注解详解
注解 | 解释 |
---|---|
@Cacheable | 触发将数据保存到缓存的操作 |
@CacheEvict | 触发将数据从缓存删除的操作 |
@CachePut | 不影响方法执行更新缓存 双写模式 |
@Caching | 组合以上多个操作 |
@CacheConfig | 在类级别共享缓存的相同配置 |
②. 触发缓存入口 - @Cacheable
- ①. @Cacheable 注解提供了一些参数,用于配置缓存的行为。下面是一些常用的参数:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";
String unless() default "";
boolean sync() default false;
}
- ②. value/cacheNames属性
- 这两个属性代表的意义相同,根据@AliasFor注解就能看出来了。
- 这两个属性都是用来指定缓存组件的名称,即将方法的返回结果放在哪个缓存中,属性定义为数组,可以指定多个缓存
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
@Cacheable(cacheNames ={ CacheEnum.RedisCacheNameExpGroup.AN_MINUTE,CacheEnum.RedisCacheNameExpGroup.FIVE_MINUTES})
//这两种配置等价
@Cacheable(value = "user") //@Cacheable(cacheNames = "user")
User getUser(Integer id);
@Cacheable(cacheNames = CacheEnum.RedisCacheNameExpGroup.AN_MINUTE, cacheManager =
CacheEnum.CacheManager.REDIS_MANAGER,
key = CacheEnum.RedisCacheKeys.PATROL_BOARD + "+#params.areaCode" + "+#params.staffCode")
- ③. key属性:可以通过key属性来指定缓存数据所使用的的key,默认使用的是方法调用传过来的参数作为key
@Cacheable(value = "user",key = "#root.method.name")
User getUser(Integer id);
- ④. cacheManager属性:用来指定缓存管理器。针对不同的缓存技术,需要实现不同的 cacheManager,Spring 也为我们定义了如下的一些cacheManger实现
- ⑤. unless属性,意为"除非"的意思。即只有unless指定的条件为true时,方法的返回值才不会被缓存。可以在获取到结果后进行判断
@Cacheable(value = "user",unless = "#result==null || #result.size()==0")//当方法返回值为 null 时,就不缓存
User getUser(Integer id);
@Cacheable(value = "user",unless = "#a0 == 1")//如果第一个参数的值是1,结果不缓存
User getUser(Integer id);
- ⑥. condition:条件判断属性,用来指定符合指定的条件下才可以缓存。也可以通过SpEL 表达式进行设置。这个配置规则和上面表格中的配置规则是相同的
@Cacheable(value = "user",condition = "#id>0")//传入的 id 参数值>0才进行缓存
User getUser(Integer id);
- ⑦. sync该属性用来指定是否使用异步模式,该属性默认值为false,默认为同步模式。异步模式指定sync = true即可,异步模式下unless属性不可用
③. 更新缓存 - CachePut
-
①. @CachePut:不影响方法执行更新缓存(双写模式) 需要有返回值
-
②. @CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中
-
③. @Cacheput注解一般使用在保存,更新方法中
@CachePut(value="emp",key = "#result.id")
public Employee updateEmployee(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmployee(employee);
return employee;
}
④. 删除缓存 - CacheEvict
- @CacheEvict:触发将数据从缓存删除的操作
@CacheEvict(cacheNames = CacheEnum.RedisCacheNameExpGroup.ONE_DAY, cacheManager = CacheEnum.CacheManager.REDIS_MANAGER,
key =CacheEnum.RedisCacheKeys.DISTRICT_DETAIL + "+#params.areaCode" + "+#params.timeRange")
public void districtDetail(DiagnosisParams params) {
log.info("refresh districtDetail!,areaCode:{},timeRange:{}",params.getAreaCode(),params.getTimeRange());
}
⑤. 组合操作- Caching
- ①. 组合以上多个操作: @Caching不常用
- @Caching 注解可以在一个方法或者类上同时指定多个Spring Cache相关的注解。
- 其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和
- ②. @CacheEvict。对于一个数据变动,更新多个缓存的场景,可以通过@Caching来实现:
@Caching(cacheable = @Cacheable(cacheNames = "caching", key = "#age"), evict = @CacheEvict(cacheNames = "t4", key = "#age"))
public String caching(int age) {
return "caching: " + age + "-->" + UUID.randomUUID().toString();
}
⑥. 共享缓存配置 - CacheConfig
-
①. @CacheConfig不常用:@CacheConfig是Spring提供的一个注解,用于统一配置缓存的一些属性,例如缓存名称、缓存管理器等
-
②. 使用@CacheConfig注解需要注意以下几点:
- @CacheConfig可以放在类上,也可以放在方法上。如果放在类上,则该类中所有的缓存方法都会继承该注解的属性。
- @CacheConfig的属性可以被缓存方法上的@Cacheable、@CachePut、@CacheEvict等注解覆盖。
- @CacheConfig的属性可以通过Spring的EL表达式进行动态配置。
// 在上面的示例中,@CacheConfig注解指定了缓存名称为"myCache",缓存管理器为"myCacheManager"。这些属性会被该类中所有的缓存方法继承
// 但是,如果某个缓存方法上使用了@Cacheable、@CachePut、@CacheEvict等注解,并且指定了相同的属性,则该注解的属性会覆盖@CacheConfig注解的属性
@CacheConfig(cacheNames = "myCache", cacheManager = "myCacheManager")
@Service
public class MyService {
@Cacheable(key = "#id")
public MyObject findById(Long id) {
// ...
}
@CachePut(key = "#myObject.id")
public MyObject save(MyObject myObject) {
// ...
}
@CacheEvict(key = "#id")
public void deleteById(Long id) {
// ...
}
}
⑦. 从0搭建缓存项目
- ①. 导入依赖
<?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">
<parent>
<artifactId>study2022_xz</artifactId>
<groupId>com.xiaozhi</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springboot-swagger</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<knife4j.version>2.0.9</knife4j.version>
<lombok.version>1.18.12</lombok.version>
<caffeine.version>2.7.0</caffeine.version>
</properties>
<dependencies>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!--web场景-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.80</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入caffeine依赖-->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>${caffeine.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--引入guava依赖-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
<!--引入redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
- ②. 配置类 - 常量分组概念
public interface CacheEnum {
/**
* cacheTemplate
*/
interface CacheTemplates {
/**
* 业务用redis
*/
String REDIS_CACHE_TEMPLATE = "redisTemplate";
}
/**
* cacheManager
*/
interface CacheManager {
/**
* caffeine Cache Manager
*/
String CAFFEINE_MANAGER = "caffeineManager";
/**
* redis Cache Manager
*/
String REDIS_MANAGER = "redisCacheManager";
}
/**
* redis keys
*/
interface RedisCacheKeys{
String SSM_USER_KEY = "SSM:USER_KEY:";
}
/**
* redis exp group
*/
interface RedisCacheNameExpGroup {
String AN_MINUTE = "ssm:anMinute";
String FIVE_MINUTES = "ssm:fiveMinute";
String AN_HOUR = "ssm:anHour";
String TWO_HOUR = "ssm:twoHour";
String FIVE_HOURS = "ssm:fiveHour";
String ONE_DAY = "ssm:oneDay";
String TWO_DAY ="ssm:twoDay";
}
/**
* caffeine cache names conf
*/
@AllArgsConstructor
@Getter
enum CaffeineCacheNamesConf {
SSM_TEST(CaffeineCacheNames.SSM_TEST,100,10000,1, TimeUnit.DAYS),
;
private String cacheName;
private int initialCapacity;
private int maximumSize;
private int expsNum;
private TimeUnit timeUnit;
}
/**
* Caffeine Cache Names
*/
interface CaffeineCacheNames{
String SSM_TEST = "SSM_TEST";
}
}
/**
* caffeine缓存配置,根据{@link CacheEnum.CaffeineCacheNamesConf}配置,
* 动态设置参数以及自动加载策略
*/
@Configuration
public class CaffeineCacheConfig {
@Bean(CacheEnum.CacheManager.CAFFEINE_MANAGER)
public CacheManager cacheManagerWithCaffeine() {
SimpleCacheManager caffeineCacheManager = new SimpleCacheManager();
caffeineCacheManager.setCaches(buildCaffeineCache());
return caffeineCacheManager;
}
//动态设置缓存参数
private List<CaffeineCache> buildCaffeineCache() {
ArrayList<CaffeineCache> caches = Lists.newArrayList();
CacheEnum.CaffeineCacheNamesConf[] cacheNames = CacheEnum.CaffeineCacheNamesConf.values();
for (CacheEnum.CaffeineCacheNamesConf nameCof : cacheNames) {
caches.add(new CaffeineCache(nameCof.getCacheName(),
Caffeine.newBuilder().recordStats()
.initialCapacity(nameCof.getInitialCapacity())
.maximumSize(nameCof.getMaximumSize())
.expireAfterWrite(nameCof.getExpsNum(), nameCof.getTimeUnit()).build()));
}
return caches;
}
}
@EnableCaching
@Configuration
@Slf4j
public class RedisConfig extends JCacheConfigurerSupport {
@Value("${spring.redis.database}")
private int dbIndex;
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.lettuce.pool.max-active}")
private int redisPoolMaxActive;
@Value("${spring.redis.lettuce.pool.max-wait}")
private int redisPoolMaxWait;
@Value("${spring.redis.lettuce.pool.max-idle}")
private int redisPoolMaxIdle;
@Value("${spring.redis.lettuce.pool.min-idle}")
private int redisPoolMinIdle;
public RedisSerializer<Object> redisSerializer() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
return new GenericJackson2JsonRedisSerializer(objectMapper);
}
public LettuceConnectionFactory lettuceConnectionFactory() {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName(host);
configuration.setPort(port);
configuration.setDatabase(dbIndex);
if (!ObjectUtils.isEmpty(password)) {
RedisPassword redisPassword = RedisPassword.of(password);
configuration.setPassword(redisPassword);
}
GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
genericObjectPoolConfig.setMaxTotal(redisPoolMaxActive);
genericObjectPoolConfig.setMinIdle(redisPoolMinIdle);
genericObjectPoolConfig.setMaxIdle(redisPoolMaxIdle);
genericObjectPoolConfig.setMaxWaitMillis(redisPoolMaxWait);
LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder();
builder.poolConfig(genericObjectPoolConfig);
builder.commandTimeout(Duration.ofSeconds(timeout));
LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(configuration, builder.build());
connectionFactory.afterPropertiesSet();
return connectionFactory;
}
@Bean(CacheEnum.CacheTemplates.REDIS_CACHE_TEMPLATE)
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(redisSerializer());
redisTemplate.setValueSerializer(redisSerializer());
redisTemplate.setConnectionFactory(lettuceConnectionFactory());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean(CacheEnum.CacheManager.REDIS_MANAGER)
@Primary
public CacheManager redisCacheManager() {
RedisCacheConfiguration defConfig = RedisCacheConfiguration.defaultCacheConfig();
ImmutableSet.Builder<String> cacheNames = ImmutableSet.builder();
ImmutableMap.Builder<String, RedisCacheConfiguration> cacheConfig = ImmutableMap.builder();
HashMap<String, Duration> exps = new HashMap<>();
exps.put(CacheEnum.RedisCacheNameExpGroup.AN_MINUTE, Duration.ofMinutes(1));
exps.put(CacheEnum.RedisCacheNameExpGroup.FIVE_MINUTES, Duration.ofMinutes(5));
exps.put(CacheEnum.RedisCacheNameExpGroup.AN_HOUR, Duration.ofHours(1));
exps.put(CacheEnum.RedisCacheNameExpGroup.TWO_HOUR, Duration.ofHours(2));
exps.put(CacheEnum.RedisCacheNameExpGroup.FIVE_HOURS, Duration.ofHours(5));
exps.put(CacheEnum.RedisCacheNameExpGroup.ONE_DAY, Duration.ofDays(1));
exps.put(CacheEnum.RedisCacheNameExpGroup.TWO_DAY, Duration.ofDays(2));
for (String cacheName : exps.keySet()) {
defConfig = defConfig.entryTtl(exps.get(cacheName));
cacheConfig.put(cacheName, defConfig);
cacheNames.add(cacheName);
}
return RedisCacheManager.builder(lettuceConnectionFactory())
.initialCacheNames(cacheNames.build())
.withInitialCacheConfigurations(cacheConfig.build())
.build();
}
@Override
public CacheErrorHandler errorHandler() {
return new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
log.error("handleCacheGetError!!!->{}", exception.getMessage());
}
@Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
log.error("handleCachePutError!!!->{}", exception.getMessage());
}
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
log.error("handleCacheEvictError!!!->{}", exception.getMessage());
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
log.error("handleCacheClearError!!!->{}", exception.getMessage());
}
};
}
}
- ③. 测试
@GetMapping("/testUser")
@Cacheable(cacheNames = {CacheEnum.RedisCacheNameExpGroup.AN_MINUTE,
CacheEnum.RedisCacheNameExpGroup.FIVE_MINUTES}, // 缓存组概念
cacheManager = CacheEnum.CacheManager.REDIS_MANAGER, // 使用哪个manager管理
condition = "#a0>2", // 满足条件
key = "#root.method.name", // key = 当前方法名词
unless = "#result==null || #result.size()==0") // 值为空无效
//key = "'" + CacheEnum.RedisCacheKeys.SSM_USER_KEY + "'" + "+#userId", unless = "#result==null || #result.size()==0")
public List<Integer> testUser(int userId) {
return Lists.newArrayList(userId);
}