SpringBoot使用Redis做缓存
首先引入dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.12.0</version>
</dependency>
其次在applicaion.yml里面增加配置:
spring:
cache:
type: redis
redis: # 高层redis注解支持配置信息
enable-statistics: false # 是否开启缓存统计
key-prefix: "${spring.application.name}_${spring.profiles.active}:" # 例子:"demo_dev:",为Redis的KEY拼接前缀
use-key-prefix: true # 是否拼接KEY前缀
time-to-live: 3600000 # 1小时,缓存过期时间,单位ms
cache-null-values: false # 是否允许缓存空数据,当查询到的结果为空时缓存空数据到redis中
data:
redis: # 底层redis连接信息
host: 127.0.0.1
port: 6379
password: passwordtest
database: 0 # 逻辑数据库名字,0到15
timeout: 5000 # 连接超时,毫秒
client-type: lettuce # lettuce连接池或者jedis连接池
lettuce:
pool:
max-active: 50
max-idle: 10
同时可以选择打开log:
logging:
level:
org:
cache: debug
data:
redis: debug
io:
lettuce:
core: debug
然后在application主类增加注解:
@EnableScheduling
@EnableCaching // 启用 Spring Cache
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
再实现一个配置类:
@Configuration
@AutoConfigureBefore(RedisAutoConfiguration.class) //防止冲突,优先使用这里定义的
public class MyRedisConofig extends CachingConfigurerSupport {
private final Logger log = LoggerFactory.getLogger(MyRedisConofig.class);
@Value("${spring.cache.redis.time-to-live}")
private Integer ttl;
@Value("${spring.cache.redis.key-prefix}")
private String keyPrefix;
@Value("${spring.cache.redis.use-key-prefix}")
private Boolean useKeyPrefix;
@Value("${spring.cache.redis.cache-null-values}")
private Boolean cacheNullValues;
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private Integer port;
@Value("${spring.data.redis.password}")
private String password;
@Value("${spring.data.redis.database}")
private Integer database;
@Value("${spring.data.redis.timeout}")
private Integer timeout;
@Value("${spring.data.redis.lettuce.pool.max-active}")
private Integer maxActive;
@Value("${spring.data.redis.lettuce.pool.max-idle}")
private Integer maxIdle;
@Bean
public RedisCacheManager cacheManager() {
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMillis(ttl)) // 默认没有特殊指定的ttl, 通过 cacheName 拼接指定的ttl在 MyRedisCacheManager 类中实现
.computePrefixWith(cacheName -> keyPrefix + cacheName + ":") //默认enable了 usekeyPrefix 和 cacheNullValues
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer())); //序列化换成json方式
if(! useKeyPrefix ) {
log.info("useKeyPrefix is false.");
defaultCacheConfig = defaultCacheConfig.computePrefixWith(cacheName -> cacheName + ":").disableKeyPrefix(); //默认为enbale的
}
if(! cacheNullValues){
log.info("cacheNullValues is false.");
defaultCacheConfig = defaultCacheConfig.disableCachingNullValues(); //默认为enbale的
}
MyRedisCacheManager redisCacheManager = new MyRedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory()), defaultCacheConfig);
return redisCacheManager;
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName(host);
configuration.setPort(port);
configuration.setPassword(password);
configuration.setDatabase(database);
ClientOptions clientOptionsBuilder = ClientOptions.builder()
.socketOptions(SocketOptions.builder().connectTimeout(Duration.ofMillis(timeout)).build())
.autoReconnect(true)
.build();
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(maxActive);
poolConfig.setMaxIdle(maxIdle);
LettucePoolingClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder()
.clientOptions(clientOptionsBuilder)
//.clientResources(DefaultClientResources.builder().build())
//.commandTimeout(Duration.ofMillis(timeout))
//.shutdownTimeout(Duration.ofMillis(timeout))
.poolConfig(poolConfig)
.build();
LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration, clientConfiguration);
return factory;
}
//防止和RedisAutoConfiguration里面的stringRedisTemplate冲突,不定义它了
// @Bean
// public RedisTemplate<String, String> stringRedisTemplate() {
// RedisTemplate<String, String> redisTemplate = new StringRedisTemplate();
// redisTemplate.setConnectionFactory(redisConnectionFactory());
// return redisTemplate;
// }
}
再实现一个MyRedisCacheManager的类,这是为了能让每个cacheName能够单独设置ttl(类似:@CacheConfig(cacheNames = "demoCache#3600")的#号之后的整数值表示ttl为3600秒):
public class MyRedisCacheManager extends RedisCacheManager {
public MyRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
// 类似这种定义 @Cacheable(cacheNames = "demoCache#3600") 或者 @CacheConfig(cacheNames = "demoCache#3600") ,
// 表示缓存的过期时间是3600秒。
String[] array = StringUtils.delimitedListToStringArray(name, "#");
name = array[0];
if (array.length > 1) { // 解析TTL
long ttl = Long.parseLong(array[1]);
cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttl)); // 注意单位我此处用的是秒,而非毫秒
}
return super.createRedisCache(name, cacheConfig);
}
}
最后做一个在Repo的Redis缓存下的代理类:
@Service
@CacheConfig(cacheNames = "DemoEntity#86400") //MyRedisCacheManager里面实现的"#"后面的数字是缓存时间,单位秒
public class DemoRepoRedis {
private final Logger log = LoggerFactory.getLogger(getClass());
@Autowired
private DemoRepo demoRepo;
@CachePut(key="#result.id", unless = "#result == null") //修改或新增后缓存,方便find时直接用id查询
public DemoEntity save(DemoEntity entity){
return demoRepo.save(entity);
}
@Cacheable(key = "#id", unless = "#result == null") //null值不缓存
public DemoEntity findById(Long id){
log.info("redis not cache, findById: " + id);
return demoRepo.findById(id);
}
@CacheEvict(key = "#id") // 清除该id对应缓存
public void deleteOne(Long id){
demoRepo.deleteOne(id);
}
@CacheEvict(allEntries = true) // 同步清除缓存
public void deleteAll(){
demoRepo.deleteAll();
}
}