Spring中的缓存抽象

Spring中的缓存

Spring为不同的缓存提供一层抽象

  • 为java方法增加缓存,缓存执行结果。下次相同的参数访问,直接从缓存取。
  • 支持ConcurrentMap、EhCache、redis、Caffeine、JCache(JSR-7)
  • 接口
    • org.springframework.cache.Cache
    • org.springframework.cache.CacheManager

对于需要集群的缓存,可以用支持分布式缓存的,如redis

缓存相关的注解

  • @EnableCaching 开启缓存支持
    • @Cacheble 方法缓存,没有的话,拿到方法值并存缓存。有的话直接从缓存去。
    • @CacheEvict 缓存的清理
    • @CachePut 不管方法的执行情况,直接做缓存的设置
    • @Caching 缓存方法的打包,可以执行上述多个操作
    • @CacheConfig 缓存的设置,可以设置缓存的名字

@EnableCaching于spring版本3.1起加入了该注解。如果你使用了这个注解,那么你就不需要在XML文件中配置cache manager了。

示例

下面是一个示例,通过注解添加对方法返回值的缓存、以及缓存清除。

配置文件

spring.cache.type=redis
# 程序启动时创建缓存名
#spring.cache.cache-names=count
#缓存的时间5s
spring.cache.redis.time-to-live=5000
spring.cache.redis.cache-null-values=false

演示类

//对service添加缓存
@Slf4j
@Service
@CacheConfig(cacheNames = "count")
public class CountingService {

    @CacheEvict //清除缓存
    public void clearCount() {
    }

    @Cacheable  //开启缓存
    public int getNumber() {
        log.error("Reading from cache.......");
        return 1000;
    }
    // Spring 3.X
    //@Cacheable(value="artisan")
    
    // Spring 4.0新增了value的别名cacheNames,更贴切,推荐使用
    //@Cacheable(cacheNames="artisan")
}

//JedisDemoApplication类
@SpringBootApplication
@Slf4j
@EnableCaching(proxyTargetClass = true)  //开启缓存支持,也可加到按需其他类上
public class JedisDemoApplication implements ApplicationRunner {

    @Autowired
    private CountingService countingService;

    public static void main(String[] args) {
        SpringApplication.run(JedisDemoApplication.class, args);
    }

    //测试方法调用
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("Count: {}", countingService.getNumber());
        for (int i = 0; i < 10; i++) {
            if (i == 5){
                countingService.clearCount();
            }
            log.info("time:{} Count: {}", i, countingService.getNumber());
        }
    }
}

//运行的log如下
-: Reading from cache.......
-: Count: 1000
-: time:0 Count: 1000
-: time:1 Count: 1000
-: time:2 Count: 1000
-: time:3 Count: 1000
-: time:4 Count: 1000
//i=5时进行缓存清空,故重新从缓存读取
-: Reading from cache.......
-: time:5 Count: 1000
-: time:6 Count: 1000
-: time:7 Count: 1000
-: time:8 Count: 1000
-: time:9 Count: 1000

Spring Data Redis

配置与使用

Spring Data Redis是Spring Data系列的一部分,可以从Spring应用程序轻松配置和访问Redis。它提供了与store交互的低级(RedisTemplate)和高级(ListOperations)抽象,使用户可以对基础设施少操点心。

支持 Jedis和Lettuce, 2.X以后默认实现是Lettuce(JRedis和SRP已被标记为过期)。

使用:需要引入依赖spring-boot-starter-cachespring-boot-starter-data-redis

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--spring2.0集成redis所需common-pool2-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.4.2</version>
</dependency>

修改相关配置:

spring.cache.type=redis
# 程序启动时创建缓存名
spring.cache.cache-names=count
#缓存的时间5s
spring.cache.redis.time-to-live=5000
spring.cache.redis.cache-null-values=false

spring.redis.host=localhost
spring.redis.port=6379

其他代码与缓存相关的注解 > 示例相同,可以看到数据直接存到redis中了。

如果存储class,需要实现Serializable以便进行序列化工作。

@Data
public class HUser implements Serializable {
    private String id;
    private String nickName;
    private Integer age;
}

RedisTemplate

redisTemplate.opsForXXX系列:

非绑定key操作

  • ValueOperations opsForValue();
  • HashOperations opsForHash();
  • ListOperations opsForList();
  • SetOperations opsForSet();
  • ZSetOperations opsForZSet();

绑定key操作,若以bound开头,则意味着在操作之初就会绑定一个key,后续的所有操作便默认认为是对该key的操作

  • BoundValueOperations boundValueOps(K key);
  • BoundHashOperations boundHashOps(K key);
  • BoundListOperations boundListOps(K key);
  • BoundSetOperations boundSetOps(K key);
  • BoundZSetOperations boundZSetOps(K key);

示例1:简单的存取

配置文件

spring.redis.host=localhost
spring.redis.port=6379

测试类

@Autowired
private RedisTemplate<String, Object> redisTemplate;

//因为使用了`RedisTemplate<String, Object>`这里需要添加对应的bean
@Bean 
public RedisTemplate<String, Object> redisObjectTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

private void redisTemplateSimpleDemo() {
    Map<String, Object> properties = new HashMap<>();
    properties.put("123", "hello");
    properties.put("456", "world");
    properties.put("abc", 456);
    //存
    redisTemplate.opsForHash().putAll("hash", properties);
    //设置过期时间
    //redisTemplate.expireAt("hash",date)
    redisTemplate.expire("hash",20, TimeUnit.SECONDS);
    //取
    Map<Object, Object> ans = redisTemplate.opsForHash().entries("hash");
    log.info(ans.toString());
}

示例2:存储class,以及lettuce相关配置

配置文件

spring.redis.host=localhost
spring.redis.port=6379
#spring.redis.url=redis://user:password@localhost:6379

#Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
#spring.redis.database=0

#需要配置bean似乎
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
 #连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0

自定义的class需要支持implements Serializable

@Data
public class HUser implements Serializable {
    private String id;
    private String nickName;
    private Integer age;
}

因为配置了lettuce相关参数而与示例1稍有区别:

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Bean
public RedisTemplate<String, HUser> redisUserTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<String, HUser> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

@Bean
public LettuceClientConfigurationBuilderCustomizer customizer() {
    //因为配置了spring.redis.lettuce.pool,lettuce配置的bean不可缺少。可以采用如下几种方式
    //LettuceClientConfiguration
    //LettucePoolingClientConfiguration
    //LettuceClientConfigurationBuilderCustomizer

    // lettuce支持读写分离
    // 只读主、只读从
    // 优先读主、优先读从
    //这里设置从主节点读取
    return builder -> builder.readFrom(ReadFrom.MASTER_PREFERRED);
}

private void redisAddEntity() {
    HUser user = new HUser();
    user.setAge(11);
    user.setId("01");
    user.setNickName("Ken");
    redisUserTemplate.opsForValue().set("user:ken", user);
    HUser u2 = redisUserTemplate.opsForValue().get("user:ken");
    log.info(u2.toString());
}

示例2: cluster和sentinel

TODO:待完善

其他

代码示例

相关参考 - Spring之RedisTemplate配置与使用 - Redis 序列化方式StringRedisSerializer、FastJsonRedisSerializer和KryoRedisSerializer - https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/ - Redis学习之Spring-data-redis使用 - https://blog.csdn.net/yangshangwei/article/details/78153531 - https://juejin.im/post/5bac97606fb9a05cd8492e48