Spring Cache

Posted lshare

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Cache相关的知识,希望对你有一定的参考价值。

为什么要使用 Spring Cache 管理缓存?

让 Spring 来管理 Bean 的缓存具有以下优势:

  1. Spring 支持 HashMap 缓存,Redis 缓存以及自定义的缓存方式;
  2. Spring 缓存几乎不需要写代码,只需要配置好并声明好注解。

快速开始

(1)依赖引入

这里使用 Spring 的依赖管理器来管理 Spring Cache 的版本,会自动处理内部的模块间依赖,这也是推荐的方式。

buildscript 
    repositories 
        mavenCentral()
    
    dependencies 
        classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.5.RELEASE")
    


apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

repositories 
    mavenCentral()


dependencies 
    compile("org.springframework.boot:spring-boot-starter-cache")

(2)启用缓存

在 SpringApplication 配置类的地方添加以下注解以启用缓存功能。

@SpringBootApplication
@EnableCaching

(3)ConcurrentHashMap 缓存

当没有配置其他缓存库时,默认使用 ConcurrentHashMap 作为缓存仓库。

(3.1)一个简单的实体类

public class Customer 
    public String id;
    public String firstName;
    public String lastName;
    
    public Customer() 

    public Customer(String firstName, String lastName) 
        this.firstName = firstName;
        this.lastName = lastName;
    

(3.2)一个 Repository

public class CustomerRepository 
    
    @Cachable
    Customer getByFirstName(String firstName)
        // 这里应该是从数据库查询数据,DEMO 简省成直接新建了。
        return new Customer(firstName, "Jobs");
    

(3.3)测试一下

如果缓存成功了,那么以下代码执行结果的 HashCode 是一致的。

@Component
public class AppRunner implements CommandLineRunner 

    private static final Logger logger = LoggerFactory.getLogger(AppRunner.class);

    private final CustomerRepository customerRepository;

    public AppRunner(CustomerRepository customerRepository) 
        this.customerRepository = customerRepository;
    

    @Override
    public void run(String... args) throws Exception 
        logger.info("John -->" + customerRepository.getByFirstName("John").hashCode());
        logger.info("John -->" + customerRepository.getByFirstName("John").hashCode());
        logger.info("John -->" + customerRepository.getByFirstName("John").hashCode());
    

(4)配合 Redis 缓存

(4.1)添加 Redis 依赖

在前面的依赖之下再额外新增 Redis 相关的依赖,如下:

// 本环境中的 spring-data-redis 为 1.8.7.RELEASE 版本
// 高版本的配置略有不同,请留意
compile ("org.springframework.data:spring-data-redis") 
compile "redis.clients:jedis:2.9.0"

(4.2)配置 Redis

@Configuration
public class RedisConfig 
  private final static Logger LOGGER = LoggerFactory.getLogger(RedisConfig.class);
  private final static Map<String, Long> CACHE_EXPIRE_MAP = new HashMap<>();

  static 
    CACHE_EXPIRE_MAP.put("cache1", 5 * 60L); //second
  
    
  @Bean
  RedisConnectionFactory redisConnectionFactory() 
    JedisConnectionFactory jedisConFactory = new JedisConnectionFactory();
    jedisConFactory.setHostName("localhost");
    jedisConFactory.setPort(6379);
    return jedisConFactory;
  

  @Bean
  StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) 
    return new StringRedisTemplate(redisConnectionFactory);
  

  @Bean
  RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) 
    RedisTemplate template = new RedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    template.afterPropertiesSet();
    // 默认为 JdkSerializationRedisSerializer, 配合 @Cacheable 时 KEY 会有序列化值在中间
    // 使用 StringRedisSerializer 则不会如此
    template.setKeySerializer(new StringRedisSerializer());
    return template;
  

  @Bean
  public RedisCacheManager cacheManager(RedisTemplate redisTemplate) 
    RedisCacheManager cm = new RedisCacheManager(redisTemplate);
    cm.setCacheNames(CACHE_EXPIRE_MAP.keySet());
    cm.setExpires(CACHE_EXPIRE_MAP);
    cm.setUsePrefix(true);
    cm.setCachePrefix(cacheName -> (cacheName + ":").getBytes());
    return cm;
  

(4.2)序列化实体类

Spring 在将实体类缓存到 Redis 中时进行了序列化操作,如果不对实体类进行序列化将会报错。

public class Customer implements Serializable 
    public String id;
    public String firstName;
    public String lastName;
    
    public Customer() 

    public Customer(String firstName, String lastName) 
        this.firstName = firstName;
        this.lastName = lastName;
    

(4.3)在需要缓存的位置使用注解,并指定缓存名

如果在使用 Redis 缓存时,没有指定缓存名,将会报错:no cache could be resolved for at least one cache should be provided per cache operation

public class CustomerRepository 
    
    @Cachable("cache1")
    Customer getByFirstName(String firstName)
        // 这里应该是从数据库查询数据,DEMO 简省成直接新建了。
        return new Customer(firstName, "Jobs");
    

(4.4)测试一下

测试代码同(3.3)。除此之外还可以通过 Redis CLI 检验缓存结果:

> KEYS *
1) "cache1:cb5775e6-1b39-4f63-85c8-13f134a54f32"
> GET "cache1:cb5775e6-1b39-4f63-85c8-13f134a54f32"
> TTL "cache1:cb5775e6-1b39-4f63-85c8-13f134a54f32"

更进一步

创建自定义的 KeyGenerator

  1. 使上述的 RedisConfig 继承 CachingConfigurerSupport,这一步很重要,否则创建自定义的 KeyGenerator 失败;
  2. 使用 @Bean 声明自定义的 KeyGenerator。代码如下:
@Configuration
public class RedisConfig extends CachingConfigurerSupport 
  @Bean
  @Override
  public KeyGenerator keyGenerator() 
    return new SimpleKeyGenerator() 
      @Override
      public Object generate(Object target, Method method, Object... params) 
          // 这里使用 [`] 分割参数,更进一步的还可以加入 method 名,或者直接重写一个 KeyGenerator。
          return super.generate(target, method, StringUtils.arrayToDelimitedString(params, "`"));
      
    ;
  

这样,就可以覆盖 Spring Cache 默认的 SimpleKeyGenerator 了。

参考

  1. Caching Data with Spring - spring.io
  2. Caching - spring.io
  3. A Guide To Caching in Spring - baeldung.com
  4. Spring Data Redis
  5. spring使用redis做缓存 - cnblogs.com
  6. Spring Cache – Creating a Custom KeyGenerator

以上是关于Spring Cache的主要内容,如果未能解决你的问题,请参考以下文章

你了解Spring从Spring3到Spring5的变迁吗?

Spring全家桶笔记:Spring+Spring Boot+Spring Cloud+Spring MVC

学习笔记——Spring简介;Spring搭建步骤;Spring的特性;Spring中getBean三种方式;Spring中的标签

Spring--Spring入门

Spring框架--Spring事务管理和Spring事务传播行为

Spring框架--Spring事务管理和Spring事务传播行为