springcache使用笔记002_注释驱动的 Spring cache 按条件查询

来源:互联网 发布:锁屏主题软件 编辑:程序博客网 时间:2024/06/06 18:32

如何按照条件操作缓存

前面介绍的缓存方法,没有任何条件,即所有对 accountService 对象的 getAccountByName 方法的调用都会起动缓存效果,不管参数是什么值,如果有一个需求,就是只有账号名称的长度小于等于 4 的情况下,才做缓存,大于 4 的不使用缓存,那怎么实现呢?

Spring cache 提供了一个很好的方法,那就是基于 SpEL 表达式的 condition 定义,这个 condition 是 @Cacheable 注释的一个属性,下面我来演示一下

清单 13. AccountService.java(getAccountByName 方法修订,支持条件)
1
2
3
4
5
@Cacheable(value="accountCache",condition="#userName.length() <= 4")// 缓存名叫 accountCache
public Account getAccountByName(String userName) {
// 方法内部实现不考虑缓存逻辑,直接实现业务
return getFromDB(userName);
}

注意其中的 condition=”#userName.length() <=4”,这里使用了 SpEL 表达式访问了参数 userName 对象的 length() 方法,条件表达式返回一个布尔值,true/false,当条件为 true,则进行缓存操作,否则直接调用方法执行的返回结果。

清单 14. 测试方法
1
2
3
4
s.getAccountByName("somebody");// 长度大于 4,不会被缓存
s.getAccountByName("sbd");// 长度小于 4,会被缓存
s.getAccountByName("somebody");// 还是查询数据库
s.getAccountByName("sbd");// 会从缓存返回
清单 15. 运行结果
1
2
3
real querying db...somebody
real querying db...sbd
real querying db...somebody

可见对长度大于 4 的账号名 (somebody) 没有缓存,每次都查询数据库。

如果有多个参数,如何进行 key 的组合

假设 AccountService 现在有一个需求,要求根据账号名、密码和是否发送日志查询账号信息,很明显,这里我们需要根据账号名、密码对账号对象进行缓存,而第三个参数“是否发送日志”对缓存没有任何影响。所以,我们可以利用 SpEL 表达式对缓存 key 进行设计

清单 16. Account.java(增加 password 属性)
1
2
3
4
5
6
7
private String password;
public String getPassword() {
  return password;
}
public void setPassword(String password) {
  this.password = password;
}
清单 17. AccountService.java(增加 getAccount 方法,支持组合 key)
1
2
3
4
5
6
@Cacheable(value="accountCache",key="#userName.concat(#password)")
public Account getAccount(String userName,String password,boolean sendLog) {
  // 方法内部实现不考虑缓存逻辑,直接实现业务
  return getFromDB(userName,password);
  
}

注意上面的 key 属性,其中引用了方法的两个参数 userName 和 password,而 sendLog 属性没有考虑,因为其对缓存没有影响。

清单 18. Main.java
1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
  ApplicationContext context = new ClassPathXmlApplicationContext(
     "spring-cache-anno.xml");// 加载 spring 配置文件
  
  AccountService s = (AccountService) context.getBean("accountServiceBean");
  s.getAccount("somebody", "123456", true);// 应该查询数据库
  s.getAccount("somebody", "123456", true);// 应该走缓存
  s.getAccount("somebody", "123456", false);// 应该走缓存
  s.getAccount("somebody", "654321", true);// 应该查询数据库
  s.getAccount("somebody", "654321", true);// 应该走缓存
}

上述测试,是采用了相同的账号,不同的密码组合进行查询,那么一共有两种组合情况,所以针对数据库的查询应该只有两次。

清单 19. 运行结果
1
2
real querying db...userName=somebody password=123456
real querying db...userName=somebody password=654321

和我们预期的一致。

如何做到:既要保证方法被调用,又希望结果被缓存

根据前面的例子,我们知道,如果使用了 @Cacheable 注释,则当重复使用相同参数调用方法的时候,方法本身不会被调用执行,即方法本身被略过了,取而代之的是方法的结果直接从缓存中找到并返回了。

现实中并不总是如此,有些情况下我们希望方法一定会被调用,因为其除了返回一个结果,还做了其他事情,例如记录日志,调用接口等,这个时候,我们可以用 @CachePut 注释,这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中。

清单 20. AccountService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
@Cacheable(value="accountCache")// 使用了一个缓存名叫 accountCache
public Account getAccountByName(String userName) {
  // 方法内部实现不考虑缓存逻辑,直接实现业务
  return getFromDB(userName);
}
@CachePut(value="accountCache",key="#account.getName()")// 更新 accountCache 缓存
public Account updateAccount(Account account) {
  return updateDB(account);
}
private Account updateDB(Account account) {
  System.out.println("real updating db..."+account.getName());
  return account;
}
清单 21. Main.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
  ApplicationContext context = new ClassPathXmlApplicationContext(
     "spring-cache-anno.xml");// 加载 spring 配置文件
  
  AccountService s = (AccountService) context.getBean("accountServiceBean");
  
  Account account = s.getAccountByName("someone");
  account.setPassword("123");
  s.updateAccount(account);
  account.setPassword("321");
  s.updateAccount(account);
  account = s.getAccountByName("someone");
  System.out.println(account.getPassword());
}

如上面的代码所示,我们首先用 getAccountByName 方法查询一个人 someone 的账号,这个时候会查询数据库一次,但是也记录到缓存中了。然后我们修改了密码,调用了 updateAccount 方法,这个时候会执行数据库的更新操作且记录到缓存,我们再次修改密码并调用 updateAccount 方法,然后通过 getAccountByName 方法查询,这个时候,由于缓存中已经有数据,所以不会查询数据库,而是直接返回最新的数据,所以打印的密码应该是“321”

清单 22. 运行结果
1
2
3
4
real querying db...someone
real updating db...someone
real updating db...someone
321

和分析的一样,只查询了一次数据库,更新了两次数据库,最终的结果是最新的密码。说明 @CachePut 确实可以保证方法被执行,且结果一定会被缓存。

@Cacheable、@CachePut、@CacheEvict 注释介绍

通过上面的例子,我们可以看到 spring cache 主要使用两个注释标签,即 @Cacheable、@CachePut 和 @CacheEvict,我们总结一下其作用和配置方法。

表 1. @Cacheable 作用和配置方法
@Cacheable 的作用主要针对方法配置,能够根据方法的请求参数对其结果进行缓存@Cacheable 主要的参数value缓存的名称,在 spring 配置文件中定义,必须指定至少一个例如:
@Cacheable(value=”mycache”) 或者 
@Cacheable(value={”cache1”,”cache2”}key缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合例如:
@Cacheable(value=”testcache”,key=”#userName”)condition缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存例如:
@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
表 2. @CachePut 作用和配置方法
@CachePut 的作用主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用@CachePut 主要的参数value缓存的名称,在 spring 配置文件中定义,必须指定至少一个例如:
@Cacheable(value=”mycache”) 或者 
@Cacheable(value={”cache1”,”cache2”}key缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合例如:
@Cacheable(value=”testcache”,key=”#userName”)condition缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存例如:
@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
表 3. @CacheEvict 作用和配置方法
@CachEvict 的作用主要针对方法配置,能够根据一定的条件对缓存进行清空@CacheEvict 主要的参数value缓存的名称,在 spring 配置文件中定义,必须指定至少一个例如:
@CachEvict(value=”mycache”) 或者 
@CachEvict(value={”cache1”,”cache2”}key缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合例如:
@CachEvict(value=”testcache”,key=”#userName”)condition缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才清空缓存例如:
@CachEvict(value=”testcache”,
condition=”#userName.length()>2”)allEntries是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存例如:
@CachEvict(value=”testcache”,allEntries=true)beforeInvocation是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存例如:
@CachEvict(value=”testcache”,beforeInvocation=true)
原创粉丝点击