SpringCache集成Redis
一、SpringCache解决了什么问题?
SpringCache是spring3.1版本发布出来的,他是对使用缓存进行封装和抽象,通过在方法上使用annotation注解就能拿到缓存结果。
因为用注解所以它解决了业务代码和缓存代码的耦合度问题,即再不侵入业务代码的基础上让现有代码即刻支持缓存, 它让开发人员无感知的使用了缓存。
特别注意:
(注意:对于redis的缓存,SpringCache只支持String,其他的Hash 、List、set、ZSet都不支持)
二、写代码
步骤1:pom文件加入依赖包
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--spring cache连接池依赖包-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
复制代码
步骤2: 配置文件,加入redis配置信息
###############################Redis 配置
## Redis数据库索引(默认为0)
spring.redis.database=0
## Redis服务器地址
spring.redis.host=127.0.0.1
## Redis服务器连接端口
spring.redis.port=6379
## Redis服务器连接密码(默认为空)
spring.redis.password=
###############################spring cache配置
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间
spring.redis.lettuce.pool.max-wait=-1ms
# 连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=5000ms
# cache底层使用redis
spring.cache.type=redis
spring.cache.cache-names=redisCache
#全局设置注解缓存超时时间ms 10分钟
spring.cache.redis.time-to-live=600000
#是否可以缓存null值
spring.cache.redis.cache-null-values=true
#是否使用前缀 会把缓存注解中的value作key前缀,默认true
spring.cache.redis.use-key-prefix=true
# key前缀,全局的,会使注解的前缀失效,但是注解过期时间等配置还是生效
spring.cache.redis.key-prefix=ljw:
复制代码
步骤3:开启缓存配置,设置序列化
重点是开启 @EnableCaching
- 简单版配置
/**
* spring cache 配置
*/
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching
public class SpringCacheConfig {
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
//将配置文件中所有的配置都生效
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
复制代码
- 复杂版配置
实现目标:根据业务实现多个缓存区名,每个缓存区过期时间不一样的配置
/**
* spring cache 配置
*/
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching
public class SpringCacheConfig {
@Autowired
private CacheProperties cacheProperties;
/**
* 自定义注解的缓存配置
* <p>
* 配置使用注解的时候缓存配置,默认是序列化反序列化的形式,加上此配置则为 json 形式
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
// 默认策略,只要缓存注解的value不是下面map自定义的就使用这个默认过期时间 10分钟
this.getRedisCacheConfigurationWithTtl(cacheProperties.getRedis().getTimeToLive()),
// @Cacheable(value = "redisExpire1h",key = "'a'+#itemCode") 就是使用缓存名称为redisExpire1h的配置,过期时间1小时。。可自定义多个
this.getRedisCacheConfigurationMap()
);
}
/**
* 注解上的value对应的是cacheManager中的redisCacheConfigurationMap中的配置(map可以放多个配置),
* 这里指定的是redisExpire1h;
* 不写或者匹配不上,使用的是cacheManager中默认的defaultCacheConfig
*
* @return
*/
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
//期时间1小时
redisCacheConfigurationMap.put("redisExpire1h", this.getRedisCacheConfigurationWithTtl(Duration.ofMillis(3600000L)));
//过期时间一天
redisCacheConfigurationMap.put("redisExpire1d", this.getRedisCacheConfigurationWithTtl(Duration.ofMillis(86400000L)));
return redisCacheConfigurationMap;
}
/**
* 配置不同缓存区的配置信息。可根据业务区分
*
* @param duration
* @return
*/
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Duration duration) {
//获取默认缓存配置的
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
//设置每个缓存区的过期时间
.entryTtl(duration)
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
//将配置文件中所有的配置对应在CacheProperties类,想要都生效,这里要重新赋值config
/* if (redisProperties.getKeyPrefix() != null) {
//默认指定为cacheNames为key的前缀 :@CacheConfig(cacheNames = {"user"})和@Cacheable(cacheNames = "redisExpire1h", key = "'checkTTL1Hour'")的前缀为user:: 和redisExpire1h::
//添加自定义前缀,如果这里或配置文件application.properties指定前缀则覆盖注解的前缀
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}*/
if (!redisProperties.isCacheNullValues()) {
//表示不允许缓存空值
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
// 不使用默认前缀
config = config.disableKeyPrefix();
}
return config;
}
}
复制代码
步骤4:逻辑代码
控制器
@Api(description = "用户接口(SpringCache)")
@RestController
@RequestMapping("/userSpringCache")
public class UserSpringCacheController {
@Autowired
private UserSpringCacheService userSpringCacheService;
@ApiOperation("单个用户查询,按userid查用户信息")
@RequestMapping(value = "/findById/{id}", method = RequestMethod.GET)
public UserVO findById(@PathVariable int id) {
User user = this.userSpringCacheService.findUserById(id);
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user, userVO);
return userVO;
}
@ApiOperation("修改某条数据")
@PostMapping(value = "/updateUser")
public void updateUser(@RequestBody UserVO obj) {
User user = new User();
BeanUtils.copyProperties(obj, user);
userSpringCacheService.updateUser(user);
}
@ApiOperation("按id删除用户")
@RequestMapping(value = "/del/{id}", method = RequestMethod.GET)
public void deleteUser(@PathVariable int id) {
this.userSpringCacheService.deleteUser(id);
}
@ApiOperation("查看缓存时间是否1个小时")
@RequestMapping(value = "/checkTTL1Hour", method = RequestMethod.GET)
public String checkTTL1Hour() {
String ttl = this.userSpringCacheService.checkTTL1Hour();
return ttl;
}
@ApiOperation("查看缓存时间是否1天")
@RequestMapping(value = "/checkTTL1Day", method = RequestMethod.GET)
public String checkTTL1Day() {
String ttl = this.userSpringCacheService.checkTTL1Day();
return ttl;
}
@ApiOperation("默认ttl")
@RequestMapping(value = "/deafultTTL", method = RequestMethod.GET)
public String deafultTTL() {
String ttl = this.userSpringCacheService.deafultTTL();
return ttl;
}
@ApiOperation("caching使用")
@RequestMapping(value = "/caching", method = RequestMethod.GET)
public String caching() {
String ttl = this.userSpringCacheService.caching();
return ttl;
}
}
复制代码
服务类
@Service
@CacheConfig(cacheNames = {"user"})
@Slf4j
public class UserSpringCacheServiceImpl extends ServiceImpl<UserMapper, User> implements UserSpringCacheService {
@Autowired
private UserMapper userMapper;
@Autowired
private RedisTemplate redisTemplate;
/**
* 默认10分钟,key为配置文件配置的前缀+id
* 或 @CacheConfig(cacheNames = {"user"})配置的 user::id
*
* @param id
* @return
*/
@Override
@Cacheable(key = "#id")
public User findUserById(Integer id) {
return this.userMapper.selectById(id);
}
/**
* 更新会修改ttl为10分钟
*
* @return
*/
@Override
@CachePut(key = "#obj.id")
public User updateUser(User obj) {
this.userMapper.updateById(obj);
return this.userMapper.selectById(obj.getId());
}
@Override
@CacheEvict(key = "#id")
public void deleteUser(Integer id) {
User user = new User();
user.setId(id);
user.setDeleted((byte) 1);
this.userMapper.updateById(user);
}
/**
* 过期时间使用redisExpire1h缓存区的过期时间
* key-> redisExpire1h::checkTTL1Hour
*
* @return
*/
@Override
@Cacheable(cacheNames = "redisExpire1h", key = "'checkTTL1Hour'")
public String checkTTL1Hour() {
Long expire = redisTemplate.getExpire("checkTTL1Hour");
log.info("checkTTL1Hour:" + expire);
return String.valueOf(expire);
}
/**
* 过期时间使用redisExpire1d缓存区的过期时间
* key-> redisExpire1d::checkTTL1Day
*
* @return
*/
@Override
@Cacheable(cacheNames = "redisExpire1d", key = "'checkTTL1Day'")
public String checkTTL1Day() {
Long expire = redisTemplate.getExpire("checkTTL1Day");
log.info("checkTTL1Hour:" + expire);
return String.valueOf(expire);
}
/**
* 这个缓存名没有配置noExistName ,默认ttl
* key-> noExistName::deafultTTL 以具体的noExistName为key
*
* @return
*/
@Override
@Cacheable(cacheNames = "noExistName", key = "'deafultTTL'")
public String deafultTTL() {
Long expire = redisTemplate.getExpire("deafultTTL");
log.info("checkTTL1Hour:" + expire);
return String.valueOf(expire);
}
/**
* @Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。
* 其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。
*/
@Override
@Caching(
cacheable = {
@Cacheable(cacheNames = "redisExpire1h", key = "'1002'"),
@Cacheable(cacheNames = "redisExpire1d", key = "'1002'")
},
evict = {
@CacheEvict(value = "CacheEvict1", key = "'1002'"),
@CacheEvict(value = "CacheEvict2", key = "'1002'")
})
public String caching() {
redisTemplate.opsForValue().set("CacheEvict1::1002", "Caching");
redisTemplate.opsForValue().set("CacheEvict2::1002", "Caching");
return "Caching";
}
}
复制代码
三、剖析SpringCache常用注解
@CacheConfig
@CacheConfig是类级别的注解,统一该类的所有缓存key的前缀,和应用的缓存名配置,如ttl等,如果
cacheNames不存在则是默认的配置
@CacheConfig(cacheNames = { "user" })
public class UserSpringCacheServiceImpl extends ServiceImpl<UserMapper, User> implements UserSpringCacheService {
复制代码
以上代码,代表了该类的所有缓存可以都是"user::"为前缀
@Cacheable
@Cacheable是方法级别的注解,用于将方法的结果缓存起来。
@Cacheable(key="#id")
public User findUserById(Integer id){
return this.userMapper.selectById(id);
}
复制代码
以上方法被调用时,先从缓存中读取数据,如果缓存没有找到数据,再执行方法体,最后把返回值添加到缓存中。
注意:
@Cacheable 一般是配合@CacheConfig一起使用的
例如上文的@CacheConfig(cacheNames = { "user" }) 和 @Cacheable(key="#id")一起使用时。
key为user::#id
@Cacheable(cacheNames = "redisExpire1d", key = "'checkTTL1Day'")
复制代码
就是应用名称为redisExpire1d的缓存区配置,他们有自定义的过期时间。key为redisExpire1d::checkTTL1Day
@CachePut
@CachePut是方法级别的注解,用于更新缓存。
@CachePut(key = "#obj.id")
public User updateUser(User obj){
this.userMapper.updateById(obj);
return this.userMapper.selectById(obj.getId());
}
复制代码
以上方法被调用时,先执行方法体,然后springcache通过返回值更新缓存,即key = "#obj.id",value=User
@CacheEvict(key = "#id")
@CachePut是方法级别的注解,用于删除缓存。
@CacheEvict(key = "#id")
public void deleteUser(Integer id){
User user=new User();
user.setId(id);
user.setDeleted((byte)1);
this.userMapper.updateById(user);
}
复制代码
以上方法被调用时,先执行方法体,在通过方法参数删除缓存
@Caching
@Caching是方法级别的注解,用于多个操作
@Caching(
cacheable = {
@Cacheable(cacheNames = "redisExpire1h", key = "'1002'"),
@Cacheable(cacheNames = "redisExpire1d", key = "'1002'")
},
evict = {
@CacheEvict(value = "CacheEvict1", key = "'1002'"),
@CacheEvict(value = "CacheEvict2", key = "'1002'")
})
复制代码
以上方法被调用时,有缓存2个操作和删除2个操作.
四、springcache的缺点
-
对于redis的缓存,springcache只支持String,其他的Hash 、List、set、ZSet都不支持,
所以对于Hash 、List、set、ZSet只能用RedisTemplate -
对方法的返回值进行缓存。如果一个方法需要多个复杂的业务缓存,只能用RedisTemplate。




近期评论