Spring Cache

前言

最近新项目中要使用缓存,但是发现的之前的缓存知识忘的差不多了,所以又重新梳理了一下记录下来。

Redis安装

由于Redis官方目前没有提供windows的版本,所以使用微软提供的redis版本
redis下载地址

在Springboot中引入缓存

1.依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
2.配置缓存

application.yml中 配置redis和cache

spring:
  redis:
    database: 2
    lettuce:
      pool:
        max-wait: 1000
        max-idle: 200
        max-active: 200
    timeout: 1000
  cache:
    redis:
    ## 不缓存null值 
       cache-null-values: false
    

@SpringBootApplication类上加入@EnableCaching即可开启系统中的缓存
Spring Cache Abstraction 相关注解

  • @Cacheable 触发缓存添加
  • @CacheEvict触发移除缓存
  • @CachePut 更新缓存,但不会干扰方法的执行
  • @Caching 组合要应用于方法的多个缓存操作
  • @CacheConfig 在类级别共享一些与缓存相关的常见设置

Spring Cache Abstraction 官方文档

3.简单示例

实体类User

@Data
@AllArgsConstructor
public class User implements Serializable {
    private static final long serialVersionUID = 6119134703242129065L;
    private Integer id;
    private String name;
    private Integer age;
}

创建一个SimpleCacheTestService

  @Service
  @Slf4j
  public class SimpleCacheTestService {
  
      @Cacheable(cacheNames = "cache")
      public User cache() {
          log.info("==== 方法内查询数据 ====");
          //模拟查库的接口
          return new User(1, "xi", 19);
      }
  }

测试接口

    @GetMapping("/test")
    public User test() {
        return simpleCacheTestService.cache();
    }

第一次调用/test接口后,即可缓存结果,第二次请求时会走缓存,不会再执行方法,在redis中也可以看到缓存的结果。

C:\Users\Administrator>redis-cli -h 127.0.0.1
127.0.0.1:6379> select 2
OK
127.0.0.1:6379[2]> keys *
1) "cache::SimpleKey []"
127.0.0.1:6379[2]>

4.简单控制的示例

创建一个新的SimpleControllerCacheTestService

public class SimpleControllerCacheTestService {

    //先从缓存中查找,如果没有则执行方法,最后将结果放入缓存中去
    @Cacheable(cacheNames = "myCache")
    public User getFromCache() {
        return null;
    }

    //@CachePut注解,它执行了方法并将返回值被放入缓存中
    @CachePut(cacheNames = "myCache")
    public User populateCache() {
        return new User(1, "ii", 20);
    }

    //删除缓存
    @CacheEvict(cacheNames = "myCache")
    public void removeCache() {
    }
}

接口

    @GetMapping("/test1")
    public User test1() {
        User fromCache = simpleControllerCacheTestService.getFromCache();
        if (fromCache == null) {
            log.info("缓存为空,查库填充");
            User newValue = simpleControllerCacheTestService.populateCache();
            log.info("查库填充: {}", newValue);
            return newValue;
        }
        log.info("Returning from Cache: {}", fromCache);
        return fromCache;
    }

    @GetMapping("/remove1")
    public void removeTest1(){
        simpleControllerCacheTestService.removeCache();
    }

调用两次/test1接口,在调用/remove1接口

  INFO 10204 --- [nio-1025-exec-4] c.e.cache.controller.TestController      : 缓存为空,查库填充
  INFO 10204 --- [nio-1025-exec-4] c.e.cache.controller.TestController      : 查库填充: User(id=1, name=ii, age=20)
  INFO 10204 --- [nio-1025-exec-5] c.e.cache.controller.TestController      : Returning from Cache: User(id=1, name=ii, age=20)
5.根据方法的参数或结果来插入缓存的示例

创建一个新的ControllerCacheTestService

@Service
@CacheConfig(cacheNames = "ControllerCache")
@Slf4j
public class ControllerCacheTestService {
    private static final String CONTROLLED_PREFIX = "user_";

    public static String getCacheKey(String relevant) {
        return CONTROLLED_PREFIX + relevant;
    }


    @Cacheable(key = "'user_'.concat(#id)", unless = "#result==null")
    public User getFromCache(Integer id, String name) {
        log.info("=== getFromCache ===");
        return null;
    }

    @CachePut(key = "'user_'.concat(#result.id)")
    public User addUser(Integer id, String name, Integer age) {
        log.info("=== addUser ===");
        return new User(id, name, age);
    }

    @CacheEvict(key = "T(com.example.cache.service.ControllerCacheTestService).getCacheKey(#id)")
    public void delete(Integer id) {
        log.info("=== delete ===");
    }
}

  • 缓存key的生成方式使用的是 SpEL表达式,
  • 在redis中存储的key = cacheNames+key
  • #result 方法返回的结果

接口测试

    @GetMapping("/test2")
    public User test2(){
      return   controllerCacheTestService.getFromCache(1,"iii");
    }

    @GetMapping("/test3")
    public User test3(){
        return   controllerCacheTestService.addUser(1,"iii",20);
    }

    @GetMapping("/remove2")
    public void removeTest2(){
        controllerCacheTestService.delete(1);
    }

缓存的TTL(过期时间)

1.全局的TTL设置
  cache:
    redis:
       ## 缓存的存活时间 100 秒
      time-to-live: 100s
2.对某些cacheNames进行定制

考虑到后期扩展,所以将需要定制的cacheName 写入到配置文件中 去。

spring:
 cache:
   ##定制缓存 的到期时间
   custom:
     cache-key-expire-times:
       ##myCache 的缓存时间为20s
       myCache: 20s

定义一个spring.cache.custom.cacheKeyExpireTimes 的key,值为Map(cacheName->过期时间).
创建一个配置文件CacheConfigurationProperties用于读取这些配置

@Data
@ConfigurationProperties(prefix = "spring.cache.custom")
public class CacheConfigurationProperties {
    private Map<String, Duration> cacheKeyExpireTimes = new HashMap<>();
}

配置CacheManager

@Configuration
@Import({CacheConfigurationProperties.class, CacheProperties.class})
public class RedisConfig {

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, CacheConfigurationProperties properties, CacheProperties cacheProperties) {
        Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();

        for (Map.Entry<String, Duration> cacheNameAndTimeout : properties.getCacheKeyExpireTimes().entrySet()) {
            cacheConfigurations.put(cacheNameAndTimeout.getKey(), createCacheConfiguration(cacheNameAndTimeout.getValue()));
        }

        return RedisCacheManager
                .builder(redisConnectionFactory)
                .cacheDefaults(createCacheConfiguration(cacheProperties.getRedis().getTimeToLive()))
                .withInitialCacheConfigurations(cacheConfigurations).build();
    }
    private static RedisCacheConfiguration createCacheConfiguration(Duration timeoutInSeconds) {
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(timeoutInSeconds);
    }
}

设置缓存对象的值为JSON 而不是序列化后的值

/**
 * @author peter
 * date: 2019-10-16 09:23
 **/
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
       			 .serializeValuesWith(
	        		RedisSerializationContext.SerializationPair
		        		.fromSerializer(getObjectJackson2JsonRedisSerializer())
        		);
    }

    private Jackson2JsonRedisSerializer<Object> getObjectJackson2JsonRedisSerializer() {
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper()
                .registerModule(new ParameterNamesModule())
                .registerModule(new Jdk8Module())
                .registerModule(new JavaTimeModule())
                .registerModule(new Hibernate5Module()); // new module, NOT JSR310Module;
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
        objectMapper.enable(SerializationFeature.FAIL_ON_EMPTY_BEANS);

        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

        serializer.setObjectMapper(objectMapper);
        return serializer;
    }
}

需要添加的依赖

<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-parameter-names</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
 <dependency>
     <groupId>com.fasterxml.jackson.datatype</groupId>
     <artifactId>jackson-datatype-hibernate5</artifactId>         
</dependency>

使用缓存方法的注意事项

绝对不要在同一类中调用缓存的方法。 因为 Spring Cache 代理了缓存方法的访问,以使Cache Abstraction起作用,在同一个类中调用时会使代理失效。

源码地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值