| 知乎专栏 |
https://docs.spring.io/spring-boot/docs/current/reference/html/io.html#io.caching.provider.redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
使用 Redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring.data.redis.host=cch.netkiller.cn
spring.data.redis.port=6379
spring.data.redis.password=passw0rd
spring.data.redis.database=10
spring.data.redis.timeout=30000
spring.data.redis.lettuce.pool.max-active=32
spring.data.redis.lettuce.pool.max-wait=-1
spring.data.redis.lettuce.pool.max-idle=8
spring.data.redis.lettuce.pool.min-idle=2
spring.cache.type=redis
spring.cache.redis.key-prefix=spring:
spring.cache.redis.use-key-prefix=true
spring.cache.redis.time-to-live=604800000
spring.cache.redis.cache-null-values=false
添加 @EnableCaching
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Cacheable(value="users", key="#id")
public User find(Integer id) {
return null;
}
@Cacheable(value = "picture:click", key = "#deviceId+'-'+#pictureId", unless = "#result == null")
public AigcResponse click(Integer deviceId, Integer pictureId) {
Optional<PictureClick> optional = pictureClickRepository.findByDeviceIdAndPictureId(deviceId, pictureId);
return optional.map(AigcResponse::new).orElseGet(() -> new AigcResponse(false, AigcResponse.Code.DataNotFoundException, "数据不存在", null));
}
条件判断
@Cacheable(value="messagecache", key="#id", condition="id < 10")
public String getMessage(int id){
return "hello"+id;
}
@Cacheable(value="test",condition="#userName.length()>2")
@Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
#p0 参数索引,p0表示第一个参数
@Cacheable(value="users", key="#p0")
public User find(Integer id) {
return null;
}
@Cacheable(value="users", key="#p0.id")
public User find(User user) {
return null;
}
@Cacheable 如果没有任何参数将会自动生成 key ,前提是必须设置 @CacheConfig(cacheNames = "test")
@GetMapping("/cache/auto")
@Cacheable()
public Attribute auto() {
Attribute attribute = new Attribute();
attribute.setName("sdfsdf");
return attribute;
}
127.0.0.1:6379> keys *
1) "test::SimpleKey []"
@GetMapping("/cache/expire")
@Cacheable("test1#${select.cache.timeout:1000}")
public String expire() {
return "Test";
}
@GetMapping("/cache/expire")
@Cacheable("test1#${select.cache.timeout:1000}#${select.cache.refresh:600}")
public String expire() {
return "Test";
}
使用 unless 排除 null 结果
@Cacheable(value = "translate", key = "#chinese", unless="#result == null")
public String translate(String chinese) {
}
通过配置文件设置spring.cache.redis.cache-null-values
spring.cache.redis.cache-null-values=false
@CachePut 每次都会执行方法,都会将结果存入指定key的缓存中,@CachePut 不会判断是否 key 已经存在,二是始终覆盖。
@CachePut("users")
public User find(Integer id) {
return null;
}
缓存返回结果
@Cacheable("cacheable")
@RequestMapping("/test/cacheable")
@ResponseBody
public String cacheable() {
Date date = new Date();
String message = date.toString();
return message;
}
5秒钟清楚一次缓存
@Scheduled(fixedDelay = 5000)
@CacheEvict(allEntries = true, value = "cacheable")
public void expire() {
Date date = new Date();
String message = date.toString();
System.out.println(message);
}
@Caching(
cacheable = {
@Cacheable(value = "emp",key = "#p0"),
...
},
put = {
@CachePut(value = "emp",key = "#p0"),
...
},
evict = {
@CacheEvict(value = "emp",key = "#p0"),
....
}
)
public User save(User user) {
....
}
@Caching(
evict = {
@CacheEvict(value = "picture:click", key = "#deviceId+'-'+#pictureId"),
@CacheEvict(cacheNames = "picture:share:previous", key = "#pictureId"),
@CacheEvict(cacheNames = "picture:share:next", key = "#pictureId")
}
)
package cn.netkiller.config;
import java.time.Duration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
@Configuration
public class CacheConfiguration {
@Bean
public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration
.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)) // 全局设置,统一TTL时间
.disableCachingNullValues()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}
}
分别配置 products 和 users key 的过期时间
package cn.netkiller.config;
import java.time.Duration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager.RedisCacheManagerBuilder;
import org.springframework.data.redis.cache.RedisCacheManagerBuilderCustomizer;
@Configuration
public class CacheConfiguration {
@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (RedisCacheManagerBuilder builder) -> builder
.withCacheConfiguration("products", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(5)))
.withCacheConfiguration("users", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(1))
);
}
}
Springboot 1.x
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setDefaultExpiration(60); //缓存默认 60 秒
Map<String, Long> expiresMap = new HashMap<>();
expiresMap.put("Product", 5L); //设置 key = Product 时 5秒缓存。你可以添加很多规则。
cacheManager.setExpires(expiresMap);
return cacheManager;
}
Springboot 2.x
package api.config;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
@Configuration
public class CachingConfigurer {
public CachingConfigurer() {
// TODO Auto-generated constructor stub
}
@Bean
public KeyGenerator simpleKeyGenerator() {
return (o, method, objects) -> {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(o.getClass().getSimpleName());
stringBuilder.append(".");
stringBuilder.append(method.getName());
stringBuilder.append("[");
for (Object obj : objects) {
stringBuilder.append(obj.toString());
}
stringBuilder.append("]");
return stringBuilder.toString();
};
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
this.redisCacheConfiguration(600), // 默认配置
this.initialCacheConfigurations()); // 指定key过期时间配置
}
private Map<String, RedisCacheConfiguration> initialCacheConfigurations() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
redisCacheConfigurationMap.put("UserInfoList", this.redisCacheConfiguration(3000));
redisCacheConfigurationMap.put("UserInfoListAnother", this.redisCacheConfiguration(18000));
return redisCacheConfigurationMap;
}
private RedisCacheConfiguration redisCacheConfiguration(Integer seconds) {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).entryTtl(Duration.ofSeconds(seconds));
return redisCacheConfiguration;
}
}
@Cacheable(value = "DefaultKey", keyGenerator = "simpleKeyGenerator") // 600秒,使用默认策略
@Cacheable(value = "UserInfoList", keyGenerator = "simpleKeyGenerator") // 3000秒
@Cacheable(value = "UserInfoListAnother", keyGenerator = "simpleKeyGenerator") // 18000秒
127.0.0.1:6379> keys *
1) "test2::SimpleKey []"
127.0.0.1:6379> ttl "test2::SimpleKey []"
(integer) 584
org.springframework.data.redis.serializer.SerializationException: Cannot serialize
Caused by: java.io.InvalidClassException: cn.netkiller.domain.PictureClick; class invalid for deserialization
解决方法
package cn.netkiller.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.Table;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.*;
import java.io.Serializable;
@Entity
@Table
@Data
@DynamicInsert
@DynamicUpdate
@Comment("图片点击数据")
public class PictureClick implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", unique = true, nullable = false, insertable = false, updatable = false, columnDefinition = "int unsigned")
@Comment("主键")
private Integer id;
@ManyToOne
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(nullable = false, insertable = true, updatable = false)
@JsonIgnore
@Comment("设备")
private Device device;
@Comment("图片")
@ManyToOne
@OnDelete(action = OnDeleteAction.CASCADE)
// @JoinColumn(name = "picture_id", nullable = false, insertable = true, updatable = false)
// @JsonIgnore
private Picture picture;
@Comment("点赞")
private Boolean likes = false;
@Comment("收藏")
private Boolean favorites = false;
@Comment("转发")
private Boolean forward = false;
}
public class PictureClick implements Serializable