redis分布式缓存(四)一一SpringCache集成R

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

  1. 简单版配置
/**
 * 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;
    }
}
复制代码
  1. 复杂版配置

实现目标:根据业务实现多个缓存区名,每个缓存区过期时间不一样的配置

/**
 * 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的缺点

  1. 对于redis的缓存,springcache只支持String,其他的Hash 、List、set、ZSet都不支持,
    所以对于Hash 、List、set、ZSet只能用RedisTemplate

  2. 对方法的返回值进行缓存。如果一个方法需要多个复杂的业务缓存,只能用RedisTemplate。

五、源码下载

源码

redis分布式缓存系列