Spring Cache 介绍及使用Demo

1,287 阅读6分钟

这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战


Spring3.1中提供了一个Cache Abstraction来屏蔽各种缓存组件的差异,统一不同缓存组件的使用方式,类似于Spring transaction (事务)使用注解完成事务管理一样,Spring Cache通过注解完成缓存的管理(新增、更新、删除、淘汰等操作)。

Spring Cache将缓存应用于Java方法,即在方法上加上Spring Cache的注解后,每次调用该方法时,Spring Cache都会检查是否已经使用此参数调用了该方法。如果已调用,则直接返回缓存的结果,而不去调用实际的方法。如果尚未调用,则会调用该方法缓存结果并返回给用户。

相同参数调用方法时,只会在第一次调用时执行该方法,其他时候从缓存中返回结果

Spring Cache是一种缓存抽象(不是缓存实现),需要使用实际存储来存储缓存数据。即Spring Cache只提供了缓存逻辑,而没有提供存储数据的能力。Spring提供了该抽象的一些实现:Redis、基于JDK java.util.concurrent.ConcurrentMap的缓存、EhcacheGemfireCaffeine和符合JSR-107的缓存。

缓存抽象对于多线程和多进程环境没有特殊处理,因为这些特性由缓存实现处理。

使用缓存抽象,需要注意两个方面: Caching declaratio:确定需要缓存的方法及其策略。 Cache configuration:存储数据并从中读取数据的后备缓存。

注解

Spring Cache提供了一下注解对缓存进行管理:

  • @Cacheable:将方法返回结果存入缓存
  • @CacheEvict:删除缓存中的数据
  • @CachePut:更新缓存中的数据
  • @Caching:组合多个缓存操作
  • @CacheConfig:在类级别共享缓存的相关设置

@Cacheable

@Cacheable的作用是将方法的返回结果存储在缓存中,以便在后续调用(使用相同的参数)时,返回缓存中的值。可以写在类和方法上,一般写在查询方法上

image.png

  • value: 缓存key的前缀名
  • cacheNames:同value
  • key:缓存key的后缀。基于SpEL表达式, 默认为""
  • keyGenerator: 缓存key的后缀,与key属性互斥。要使用的自定义org.springframework.cache.interceptor.KeyGenerator的 bean name。
  • cacheManager:缓存管理器,实现了缓存逻辑和缓存能力的bean name,例如RedisCacheManager
  • cacheResolver:缓存解析器,与cacheManager互斥
  • condition:缓存数据的条件,即满足该条件的数据才会呗缓存,基于SpEL表达式
  • unless:不缓存数据的条件,与condition作用相反,基于SpEL表达式
  • sync:是否使用异步,默认false。为true时会加锁阻塞

@CacheEvict

@CacheEvict的作用是删除缓存中缓存的相应数据。可以写在类和方法上,一般写在查询方法上

image.png

  • allEntries:是否删除缓存中的所有条目,默认false即仅删除关联键下的值
  • beforeInvocation:是否在调用方法前删除缓存,默认false。为true时无论方法结果如何(是否抛出异常)都会导致删除该缓存。为false ,即先删数据库后删缓存 其余属性同@Cacheable

@CachePut

@CacheEvict的作用是更新缓存中缓存的相应数据。可以写在类和方法上,一般写在更新方法上

image.png

属性同@Cacheable

@Caching

@Caching是一个多功能的注解,既可以只完成@Cacheable@CacheEvict@CachePut注解的功能,也可以同时完成@Cacheable@CacheEvict@CachePut注解的功能

image.png

  • cacheable:一个@Cacheable的数组,可以同时指定多个@Cacheable缓存
  • put:一个@CachePut的数组,可以同时指定多个@CachePut缓存
  • evict:一个@CacheEvict的数组,可以同时指定多个@CacheEvict缓存

@CacheConfig

@CacheConfig提供了一种在类级别共享通用缓存相关设置的机制。此注解为该类中定义的任何缓存操作提供一组默认设置。被@CacheConfig注解的类其中的@Cacheable@CacheEvict@CachePut注解共用@CacheConfig中的配置

image.png

属性同@Cacheable

Spring CacheSpEL

Spring Cache支持SpEL的如下:

表达式位置描述示例
methodNameRoot 属性方法名#root.methodName
methodRoot 属性方法#root.method.name
targetRoot 属性方法的所属类对象#root.target
targetClassRoot 属性方法的所属类Class#root.targetClass
argsRoot 属性方法的参数。类型为数组#root.args[0]:表示第一个参数
cachesRoot 属性该方法调用的其他方法#root.caches[0].name
参数名调用上下文调用时方法时,方法的实参#id , #name等 
result调用上下文调用时方法时,方法的返回结果#result

示例

  1. 新建项目 新建项目勾选如下选项:

image.png

  1. 编写yml
server:
  port: 7909

spring:
  data:
    jpa:
      repositories:
        enabled: true
    redis:
      repositories:
        enabled: true
  jpa:
    show-sql: true
    database: mysql
    hibernate:
      ddl-auto: none
    generate-ddl: false

  datasource:
    url: jdbc:mysql://**********:****/user?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
    username: ****
    password: ****
    driver-class-name: com.mysql.cj.jdbc.Driver

redis:
  host: *********
  port: ****
  password: *******
cache:
  type: redis          # 使用redis存储缓存数据
  redis:
    time-to-live: -1           # 过期时间,-1:永不过期
    use-key-prefix: true           # 开启缓存key的前缀
    key-prefix: spring_cache_     # 缓存key的前缀
    cache-null-values: false     # 缓存null
    
  1. 开启缓存 在启动类上加上@EnableCaching注解开启缓存功能。

image.png

  1. 创建实体类 创建User实体类,并加上@Data@Entity@Table注解
@Data
@Entity
@Table(name = "user")
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    private Date birthday;

    private String sex;
    
}
  1. 创建实体类的Repository 创建接口,继承JpaRepository接口。JpaRepository接口提供了基本的增删改查方法
public interface UserRepository extends JpaRepository<User, Integer> {
}
  1. 创建service 创建UserService 定义增删改查方法并实现, 在对应的增删改查方法上放上相应的缓存注解
public interface UserService {

    /**
     * value: 缓存key的前缀
     * #id:参数id
     *
     **/
    @Cacheable(value = "user", key = "#id")
    User getUserById(Integer id);

    /**
     *  #result: 方法的返回结果
     *
     **/
    @CachePut(value = "user", key = "#result.id")
    User create(User user);

    /**
     * #a0: 方法的第一个参数, 等同于#arg[0]
     *
     **/
    @CachePut(value = "user", key = "#a0.id")
    User update(User user);

    @CacheEvict(value = "user", key = "#id")
    void del(Integer id);

    @Caching(
            cacheable = {
                    @Cacheable(value= "user::name", key = "#name")
            },
            put = {
                    @CachePut(value = "user::name", key = "#result.id")
            }
    )
    User getUserByName(String name);
}


@Slf4j
@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserRepository userRepository;

    @Override
    public User getUserById(Integer id) {

        log.info("执行getUserById方法,id: {}", id);
        return userRepository.findById(id).orElse(null);
    }


    @Override
    public User create(User user) {

        return userRepository.save(user);
    }

    @Override
    public User update(User user) {

        return userRepository.save(user);
    }

    @Override
    public void del(Integer id) {

        userRepository.deleteById(id);
    }

    @Override
    public User getUserByName(String name) {

        log.info("执行getUserByName方法,name: {}", name);
        return userRepository.findByName(name);
    }
}

缓存注解也可以加载方式的实现上

  1. 编写controller
@RestController
public class UserController {

    @Resource
    private UserService userService;

    @GetMapping("users/{id}")
    public User getUserById(@PathVariable("id") Integer id) {

        return userService.getUserById(id);
    }

    @PostMapping("users")
    public User create(@RequestBody User user) {

        return userService.create(user);
    }

    @PutMapping("users")
    public User update(@RequestBody User user) {

        return userService.update(user);
    }

    @DeleteMapping("users/{id}")
    public void del(@PathVariable("id") Integer id) {

        userService.del(id);
    }

    @GetMapping("users")
    public User getUserByName(@RequestParam("name") String name) {

        return userService.getUserByName(name);
    }
}

测试

  1. 创建User 调用创建User的接口 image.png 查看redis,可以看到缓存了一个id为10的User image.png

  2. 修改User 调用修改User的接口,修改User信息

image.png

  1. 调用获取User的接口 image.png 查看控制台,可以发现并没有打印方法中的日志 image.png 删除Redis中的数据后再次调用获取User的接口,可以看到控制台打印了该方法的日志,证明调用了该方法

image.png

  1. 调用通过用户名查询的接口 image.png 查看redis,可以看到新增了两条缓存数据 image.png

  2. 调用删除User的方法 调用后查看redis,发现后缀为10的缓存数据已经被删除 image.png 此时在调用获取User的接口,控制台依然打印调用该方法的日志,但此时缓存的是空值 image.png image.png image.png