Skip to content

缓存调用

概述

目前支持使用Spring标准注解进行缓存的调用和操作,常用注解包括:

  • @Cacheable 用于缓存查询结果
  • @Cacheput 用于更新缓存
  • @CacheEvict 用于删除缓存
  • @Caching 用于同时指定多个缓存注解

Pangea2.0的缓存同时支持一级和二级缓存。查询时默认从一级缓存读取,如果找不到则从二级缓存中查找,同时同步更新一级缓存。

适用范围

Pangea v2.0.1.6+

快速上手

1、在业务模块的pom.xml中添加依赖

html
<!-- 缓存模块 -->
<dependency>
    <groupId>com.hisense.pangea</groupId>
    <artifactId>pangea-common-cache</artifactId>
    <version>${pangea.version}</version>
</dependency>

2、在需要调用缓存的方法上添加注解@Cacheable

 @Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存;当标记在一个类上时,表示该类所有的方法都支持缓存。适用于缓存查询。

 @Cacheable 的逻辑是:查找缓存 - 有就返回 -没有就执行方法体 - 将结果缓存起来;

作用和配置方法

参数解释示例
value缓存的名称,在 spring 配置文件中定义,必须指定至少一个例如: @Cacheable(value="mycache") @Cacheable(value=
key缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合@Cacheable(value="testcache",key="#userName")
condition缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存@Cacheable(value="testcache",condition="#userName.length()>2")
sync表示是否同步,默认false,表示不同步。同步多个请求有阻塞风险。@Cacheable(value="testcache",sync=true)

如果是二级缓存,属性value代表redis的key。例如

jsx
@Cacheable(value="mycache",key="#id"),如果id的传参为1,则在redis中存储的key为mycache:1

//从Redis中获取缓存,key为ACCESS_USERID:id
@Cacheable(value = CacheConstants.ACCESS_USERID, key = "#id", unless = "#result == null")
public JSONObject getCache(String id) {
    return null;
}
 
// 以第一个参数为key进行缓存
@Cacheable(value="users", key="#p0")
public Long find(Long id) {
    return id;
}
 
// 以User中的id值为key进行缓存
@Cacheable(value="users", key="#user.id")
public User find(User user) {
     return user;
}
 
// 以User中的id值为key,且 condition 条件满足则缓存
@Cacheable(value="users", key="#user.id", condition="#user.id%2==0")
public User find(User user) {
   return user;
}

3、在需要调用缓存的方法上添加注解@CachePut

@CachePut注解的缓存方法总是会执行,而且会尝试将结果放入缓存。适用于缓存更新。

@CachePut 的逻辑是:执行方法体 - 将结果缓存起来;

作用和配置方法

参数解释示例
value缓存的名称,在 spring 配置文件中定义,必须指定至少一个例如: @CachePut(value="mycache") @CachePut(value=
key缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合@CachePut(value="testcache",key="#userName")
condition缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存@CachePut(value="testcache",condition="#userName.length()>2")

如果是二级缓存,属性value代表redis的key。例如@CachePut(value="mycache",key="#id"),如果id的传参为1,则在redis中存储的keymycache:1

注意

当在方法链中使用@CachePut注解保存数据到缓存中时会无法保存成功,下面样例用的@CachePut不会生效

jsx
    public void createToken(LoginUserModel loginUser) {
        setUserAgent(loginUser);
        refreshToken(loginUser.getToken(), loginUser);
    }

    @CachePut(value = LOGIN_TOKEN_KEY, key = "#uuid", unless = "#result == null")
    public LoginUserModel refreshToken(String uuid, LoginUserModel loginUser) {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + 12 * MILLIS_MINUTE * MILLIS_SECOND);
        return loginUser;
    }

需要将@CachePut的注解放在createToken方法上,并给方法添加返回值,样例代码如下:

jsx
@CachePut(value = LOGIN_TOKEN_KEY, key = "#loginUser.getToken()", unless = "#result == null")
 public LoginUserModel createToken(LoginUserModel loginUser) {
     setUserAgent(loginUser);
     refreshToken(loginUser);
     return loginUser;
 }
 public void refreshToken(LoginUserModel loginUser) {
     loginUser.setLoginTime(System.currentTimeMillis());
     loginUser.setExpireTime(loginUser.getLoginTime() + 12 * MILLIS_MINUTE * MILLIS_SECOND);
     return loginUser;
 }

4、在需要删除缓存的方法上添加注解@CacheEvict

@CacheEvict主要用于清除缓存,主要应用到删除数据的方法上。适用于缓存删除。

作用和配置方法

参数解释示例
value缓存的名称,该处表是清除操作是发生在哪些Cache上的@CacheEvict(value="mycache") @CacheEvict(value=
key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key,如想批量删除则使用通配符即可@CacheEvict(value="testcache",key="#userName")#精确清除缓存 @CacheEvict(value="testcache",key="'*'") #批量清除缓存
condition表示清除操作发生的条件@CacheEvict(value="testcache",condition="#userName.length()>2")
allEntries表示是否需要清除缓存中的所有元素。默认为false,表示不需要;当指定了为true时,Spring Cache将忽略指定的key@CacheEvict(value="testcache",allEntries=true)
beforeInvocation表示清除操作默认触发时机。默认为false,表示先执行方法体,再执行清除操作;当指定为true时,表示先执行清除操作,再执行方法体@CacheEvict(value="testcache",beforeInvocation=true)

5、@Caching注解

@Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheableputevict,分别用于指定@Cacheable、@CachePut和@CacheEvict。 示例如下:

java
@Caching(evict = {
        @CacheEvict("addresses"),  //#清除缓存
        @CacheEvict(value = "directory", key = "#customer.name"), //#精准清除缓存
        @CacheEvict(value = "number", key = "'*'")})   //#批量清除缓存
public String getAddress(Customer customer) {...}

6、缓存过期策略

一级缓存(caffeine)支持基于大小过期和基于时间过期两种方式。配置方式如下所示:

yaml
#基于大小回收策略
pangea:
    cache:
       caffeine:
           maximum-size: 100  //最大缓存对象个数,超过此数量时之前放入的缓存将过期
 
#基于时间回收策略
pangea:
    cache:
       caffeine:
           expire-after-access:60000  //基于访问后过期时间回收,单位毫秒
 
pangea:
    cache:
       caffeine:
           expire-after-access:60000  //写入后过期时间回收,单位毫秒

二级缓存(redis)支持基于全局过期和局部过期两种方式,局部过期策略比全局过期策略优先级高,即优先遵守局部过期策略配置的有效期。配置方式如下所示:

yaml
#基于全局默认失效时间策略,单位毫秒。
pangea:
    cache:
      redis:
        #此配置表示二级缓存中所有的数据有效期只有10秒,超过10秒,二级缓存将自动清除
        default—expiration: 10000     
        
#基于局部默认失效时间策略,单位毫秒。
pangea:
    cache:
      redis:
        #此配置表示二级缓存中key为access_userid开头的数据有效期只有5秒,organizationTree开头的数据有效期只有10秒
        expires: {access_userid: 5000, organizationTree: 10000}

注意

在二级缓存基于局部过期方式中,access_userid对应@Cacheable注解中的value属性。如下所示:

java
@Cacheable(value = "access_userid", key = "#currentUserId", unless = "#result == null")
public SysUser getSysUserCache(String currentUserId) {
    return  sysUserMapper.selectUserById(Long.parseLong(currentUserId));

}

7、缓存键的生成方式

框架默认采用自定义策略生成缓存的key,通过Spring的EL表达式来指定key。

可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”

jsx
//使用id的参数值作为key
 @Cacheable(value="users", key="#id")
 public User find(Integer id) {
     return null;
 }
 
//第一个传参值作为key
@Cacheable(value="users", key="#p0")
 public User find(Integer id) {
    return null;
 }
  
 //对象user中的属性id的值作为key
 @Cacheable(value="users", key="#user.id")
 public User find(User user) {
    return null;
 }
 
 //第一个参数对象user的属性id的值作为key
 @Cacheable(value="users", key="#p0.id")
 public User find(User user) {
    return null;
 }

除了上述使用方法参数作为key之外,Spring还为我们提供了一个root对象可以用来生成key。通过该root对象我们可以获取到以下信息。

属性名称描述示例
methodName当前方法名#root.methodName
method当前方法#root.method.name
target当前被调用的对象#root.target
targetClass当前被调用的对象的class#root.targetClass
args当前方法参数组成的数组#root.args[0]
caches当前被调用的方法使用的Cache#root.caches[0].name

当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。如:

jsx
   @Cacheable(value={"users", "xxx"}, key="caches[1].name")
   public User find(User user) {
      return null;
   }

常见问题

二级缓存使用场景

数据不会被第三方修改

一般情况下,会被带有一级缓存的持久层工具以外修改的数据最好不要配置二级缓存,以免引起不一致的数据。

但是如果此数据因为性能的原因需要被缓存,同时又有可能被第3方比如SQL修改,也可以为其配置二级缓存。只是此时需要在sql执行修改后手动调用cache的清除方法,以保证数据的一致性。

数据大小在可接收范围之内

如果数据表数据量特别巨大,此时不适合于二级缓存。原因是缓存的数据量过大可能会引起内存资源紧张,反而降低性能。

如果数据表数据量特别巨大,但是经常使用的往往只是较新的那部分数据。此时,也可为其配置二级缓存。但是必须单独配置其持久化类的缓存策略,比如最大缓存数、缓存过期时间等,将这些参数降低至一个合理的范围(太高会引起内存资源紧张,太低了缓存的意义不大)。

数据更新频率低;

对于数据更新频率过高的数据,频繁同步缓存中数据的代价可能和 查询缓存中的数据从中获得的好处相当,坏处益处相抵消。此时缓存的意义也不大。

非关键数据(不是财务数据等)

非常重要的数据,绝对不允许出现或使用无效的数据,所以此时为了安全起见最好不要使用二级缓存。因为此时 “正确性”的重要性远远大于 “高性能”的重要性。

注意事项

1、官方强烈不推荐将 @Cacheable 和 @CachePut 注解到同一个方法。

参考文档

1、Spring官网文档
2、Spring Boot + Spring Cache 实现两级缓存
3、Java缓存适合使用的情况