1. 不要在抽象类(例如Repository)和接口中使用@Cache*注解。
Spring Team recommends that you only annotate concrete classes (and methods of concrete classes) with the @Cache* annotation, as opposed to annotating interfaces. You certainly can place the @Cache* annotation on an interface (or an interface method), but this works only as you would expect it to if you are using interface-based proxies. The fact that Java annotations are not inherited from interfaces means that if you are using class-based proxies (proxy-target-class="true") or the weaving-based aspect (mode="aspectj"), then the caching settings are not recognized by the proxying and weaving infrastructure, and the object will not be wrapped in a caching proxy, which would be decidedly bad.
Spring Team建议您仅使用@Cache *注释来注释具体类(以及具体类的方法),而不是注释接口。 您当然可以将@Cache *注释放在接口(或接口方法)上,但这只能在您使用基于接口的代理时按预期工作。 Java注释不是从接口继承的事实意味着如果您使用基于类的代理(proxy-target-class =“true”)或基于编织的方面(mode =“aspectj”),那么缓存设置是 代理和编织基础设施无法识别,并且该对象不会被包装在缓存代理中,这将是非常糟糕的。
2. 建议不缓存分页查询的结果
在做分页查询时,查询出来的内容只是所有内容的一部分,比如当分页条件pageSize=10,pageNum=1时,查了前10条数据,存入缓存。当pageSize=20,pageNum=1时,查了前20条数据,也存入了缓存。这样缓存中前10条数据就重复存在了,增大了内存负担。
3. 基于 proxy 的 spring aop 带来的内部调用问题
假设对象的方法是内部调用(即 this 引用)而不是外部引用,则会导致 proxy 失效,那么我们的切面就失效,也就是说 @Cacheable、@CachePut 和 @CacheEvict 都会失效。
class Service{
public Account getAccountByName2(String accountName) {
return getAccountByName(accountName);
}
@Cacheable(value="accountCache")// 使用了一个缓存名叫 accountCache
public Account getAccountByName(String accountName) {
// 方法内部实现不考虑缓存逻辑,直接实现业务
return getFromDB(accountName);
}
}
上面我们定义了一个新的方法 getAccountByName2。其自身调用了 getAccountByName 方法,这个时候,发生的是内部调用(this),所以没有走 proxy。导致 spring cache 失效
要避免这个问题,必须使用外部调用,即getAccountByName2和getAccountByName必须在不同的类中
class Service {
public Account getAccountByName2(String accountName) {
return getAccountByName(accountName);
}
}
class XXXDAO {
@Cacheable(value="accountCache")
public Account getAccountByName(String accountName) {
return getFromDB(accountName);
}
}
4.@CacheEvict 的可靠性问题
@CacheEvict
有一个属性 beforeInvocation
。缺省为 false,即缺省情况下。都是在实际的方法运行完毕后。才对缓存进行清空操作。期间假设运行方法出现异常,则会导致缓存清空不被运行。我们演示一下
// 清空 accountCache 缓存
@CacheEvict(value="accountCache",allEntries=true)
public void reload() {
throw new RuntimeException();
}
我们的测试代码例如以下:
accountService.getAccountByName("someone");
accountService.getAccountByName("someone");
try {
accountService.reload();
} catch (Exception e) {
//...
}
accountService.getAccountByName("someone");
注意上面的代码,我们在 reload 的时候抛出了运行期异常,这会导致清空缓存失败。
以上测试代码先查询了两次,然后 reload。然后再查询一次,结果应该是仅仅有第一次查询走了数据库,其它两次查询都从缓存,第三次也走缓存由于 reload 失败了。
那么我们怎样避免这个问题呢?我们能够用 @CacheEvict提供的 beforeInvocation 属性。将其设置为 true,这样,在方法运行前我们的缓存就被清空了。
能够确保缓存被清空。
5. 非 public 方法问题
@Cache*注解的方法必须为public,否则会报错