一、简单介绍

官网地址:https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#cache

  • Spring从3.1开始定义了org.springframework.cache.Cache 和 org.springframework.cacheCacheManage 接口来统一不同的缓存技术;并使用JCache注解来简化我们的开发。
  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,concurrentMapCache等;
  • 每次调用需要缓存功能的方式时,Spring会检查指定参数的指定目标方法是否已经被调用过;如果有就直接从缓存中获取方法的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
  • 使用Spring缓存抽象时我们需要关注一下两点;
    • 1、确定方法需要被缓存以及他们的缓存策略
    • 2、从缓存中读取之前缓存的数据。

image.png
image.png
CacheManage 缓存管理器可以管理各种各样的缓存,如ConcurrentHashMap、Redis等。Redisson也会继承CacheManage 。

  1. public interface CacheManager {
  2. /**
  3. * Get the cache associated with the given name.
  4. * <p>Note that the cache may be lazily created at runtime if the
  5. * native provider supports it.
  6. * @param name the cache identifier (must not be {@code null})
  7. * @return the associated cache, or {@code null} if such a cache
  8. * does not exist or could be not created
  9. */
  10. @Nullable
  11. Cache getCache(String name);
  12. /**
  13. * Get a collection of the cache names known by this manager.
  14. * @return the names of all caches known by the cache manager
  15. */
  16. Collection<String> getCacheNames();
  17. }

二、简单使用

以下所说的是基于Springboot下的cache使用

2.1 依赖和配置

2.1.1 引入依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-cache</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-data-redis</artifactId>
  8. <exclusions>
  9. <exclusion>
  10. <groupId>io.lettuce</groupId>
  11. <artifactId>lettuce-core</artifactId>
  12. </exclusion>
  13. </exclusions>
  14. </dependency>

2.1.2 引入配置

  • Springboot中的CacheAutoConfiguration会导入RedisCacheConfiguration.class,RedisCacheConfiguration.class里面自动配好了缓存管理器。
  • 配置使用redis作为缓存
    1. spring.cache.type=redis
    2. spring.cache.redis.time-to-live=60000

    2.1.3 开启SpringChache

    1、直接使用注解 @EnableCaching

    1. @EnableCaching
    2. @SpringBootApplication
    3. public class SpringBootStarter {
    4. public static void main(String[] args) {
    5. SpringApplication.run(SpringBootStarter.class, args);
    6. }
    7. }

    2、使用配置类

    ```java @EnableConfigurationProperties(CacheProperties.class) @Configuration @EnableCaching public class MyCacheConfig {

// @Autowired // private CacheProperties cacheProperties

  1. /**
  2. * 1、原来和配置文件绑定的配置类是这样子的
  3. *
  4. * @ConfigurationProperties(prefix = "spring.cache")
  5. * public class CacheProperties
  6. * <p>
  7. * 2、要让其生效
  8. */
  9. @Bean
  10. RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
  11. RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
  12. // 设置序列化器
  13. config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
  14. config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
  15. // 将配置文件中的所有东西让其生效
  16. CacheProperties.Redis redisProperties = cacheProperties.getRedis();
  17. if (redisProperties.getTimeToLive() != null) {
  18. config = config.entryTtl(redisProperties.getTimeToLive());
  19. }
  20. if (redisProperties.getKeyPrefix() != null) {
  21. config = config.prefixKeysWith(redisProperties.getKeyPrefix());
  22. }
  23. if (!redisProperties.isCacheNullValues()) {
  24. config = config.disableCachingNullValues();
  25. }
  26. if (!redisProperties.isUseKeyPrefix()) {
  27. config = config.disableKeyPrefix();
  28. }
  29. return config;
  30. }

}

  1. <a name="W2pcp"></a>
  2. ## 2.2 基本使用
  3. > SpringCache 配置好后主要基于以下几个注解使用
  4. > - **@Cacheable:** Triggers cache population. 触发将数据保存到缓存的操作。
  5. > - **@CacheEvict: **Triggers cache eviction. 触发将数据从缓存删除的操作。
  6. > - **@CachePut:** Updates the cache without interfering with the method execution. 不影响缓存的执行下更新缓存。
  7. > - **@Caching: **Regroups multiple cache operations to be applied on a method. 组合以上多个操作。
  8. > - **@CacheConfig: **Shares some common cache-related settings at class-level. 在类级别共享缓存的相同配置。
  9. <a name="okazK"></a>
  10. ### 2.2.1 @Cacheable
  11. - value:指定Cache的名称,其可以是一个数组表示多个Cache。
  12. ```java
  13. @Cacheable({"cache1", "cache2"})//Cache是发生在cache1和cache2上的
  14. public User find(Integer id) {
  15. return null;
  16. }
  17. @Cacheable(value = "category")
  18. public String getCache() {
  19. System.out.println("没走缓存");
  20. return getCategory();
  21. }
  • Key:用来指定Spring缓存方法的返回结果时对应的key,其支持自定义和默认两种。支持SpEL表达式。

    1. @Cacheable(value = "category2", key = "#root.methodName")
    2. @GetMapping("test2")
    3. public String getCache2() {
    4. System.out.println("没走缓存");
    5. return getCategory();
    6. }
  • Condition:指定发生的条件,指定需要缓存哪些内容。结果为true,缓存到redis,结果为false,不缓存到redis。

    1. @Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
    2. public User find(User user) {
    3. System.out.println("find user by user " + user);
    4. return user;
    5. }
  • unless:结果为false,缓存到redis。结果为true,不缓存到redis

    1. /**
    2. * 测试null,不存null
    3. */
    4. @Cacheable(value = "category2", key = "#root.methodName", unless = "#result == null")
    5. @GetMapping("queryCache2")
    6. public String queryCache2() {
    7. System.out.println("queryCache2......");
    8. return "11";
    9. }
  • sync :使用缓存的时候需要特别注意,防止缓存并发,并发量较高的情况下,很可能会出现多个请求同时查询1个key的情况,如果我们不加控制,让这些请求全部穿透到数据库,容易造成数据库挂掉。正常的做法是:只放一个请求去数据库查询数据,其他的请求等待 (或者立即返回null) ,查询数据库的那个线程返回结果后再将数据放入缓存。

    1. @Cacheable(value = "category2", key = "#root.methodName", sync = true)
    2. public void queryCache() {
    3. }

    当使用 sync = true 时,会调用加锁的 get方法。
    image.png

    2.2.2 @CacheEvict

  • value:缓存名,删除指定的名字的缓存

    1. @CacheEvict(value = "menuById")
    2. public Boolean deleteById(String id) {
    3. return this.removeById(id);
    4. }
  • Key: 删除指定缓存名下的指定key的缓存

    1. @CacheEvict(value = "menuById", key = "#id")
    2. public Boolean deleteById(String id) {
    3. return this.removeById(id);
    4. }
  • allEntries:用来指定是否删除整个缓存(value 指定的),默认是false

    1. /**
    2. * 清除分区category2内所有数据
    3. */
    4. @CacheEvict(value = "category2", allEntries = true)
    5. @GetMapping("/deleteCache3")
    6. public void deleteCache3() {
    7. }
  • beforeInvocation:清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。

    1. @CacheEvict(value="users", beforeInvocation=true)
    2. public void delete(Integer id) {
    3. System.out.println("delete user by id: " + id);
    4. }

    2.2.3 @CachePut

    当需要在不影响方法执行的情况下更新缓存时,可以使用 @CachePut,也就是说,被 @CachePut 注解的缓存方法总是会执行,而且会尝试将结果放入缓存(当然,是否真的会缓存还跟一些注解参数有关,比如:unless 参数)。@CachePut 跟 @Cacheable 有相同的参数属性(但是没有 sync 属性)。@CachePut 更加适合于缓存填充,而不是方法执行流的优化。
    其参数和@Cacheable基本相同,只是没有sync属性,以下阐述下它们的区别。@Cacheable 的逻辑是:查找缓存 -有就返回,没有就执行方法体 - 将结果缓存起来;@CachePut 的逻辑是:执行方法体 - 将结果缓存起来。所以 @Cacheable 适用于查询数据的方法,@CachePut 适用于更新数据的方法。

    1. @Override
    2. @CachePut(value = "menuById", key = "#menu.id")
    3. public Menu ReviseById(Menu menu) {
    4. this.updateById(menu);
    5. return menu;
    6. }

    2.2.4 @Caching

    可以使用多个注解

    1. /**
    2. * 组合注解
    3. */
    4. @Caching(
    5. cacheable = {
    6. @Cacheable(value = "emp", key = "#lastName")
    7. },
    8. put = {
    9. //Put一定是在执行方法之后调用,只要一个方法标了@CachePut,那么每次执行查询都会直接去查数据库,然后再将结果插入到缓存中,但是下次在用id查询的时候就不需要查询数据库了,直接从缓存中lookup->get(key)
    10. @CachePut(value = "emp", key = "#result.id"),
    11. @CachePut(value = "emp", key = "#result.email")
    12. }
    13. )
    14. public Employee getByLastName(String lastName) {
    15. Employee byLastName = employeeMapper.getByLastName(lastName);
    16. System.out.println("查询员工: "+lastName);
    17. return byLastName;
    18. }

    2.2.5 @CacheConfig

    有时候一个类中可能会有多个缓存操作,这些操作可能是重复的,这个时候可以使用@CacheConfig ```java // 这里都是使用相同的value,所以可以统一命名 cacheNames = “emp”

@Service @CacheConfig(cacheNames = “emp”) public class EmployeeService {

@Caching( cacheable = { @Cacheable(/value = “emp”,/ key = “#lastName”) }, put = { //Put一定是在执行方法之后调用,只要一个方法标了@CachePut,那么每次执行查 询都会直接去查数据库,然后再将结果插入到缓存中,但是下次在用id查询的时候就不需要查询数据库了,直接从缓存中lookup->get(key) @CachePut(/value = “emp”,/ key = “#result.id”), @CachePut(/value = “emp”,/ key = “#result.email”) } ) public Employee getByLastName(String lastName) { Employee byLastName = employeeMapper.getByLastName(lastName); System.out.println(“查询员工: “+lastName); return byLastName; } ```

2.3 原理

CacheAutoConfiguration -> RedisCacheConfiguration -> 自动配置了RedisCacheManage ->初始化所有缓存->每个缓存决定使用什么配置-> 如果redisCacheConfiguration 有就自己用,没有就用默认配置 -> 想改缓存的配置,只需要给容器中放一个RedisCacheConfiguration即可-> 就会应用到当前RedisCacheManager管理的所有缓存分区中。