spring cache脱坑指南

     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,否则会报错

 

展开阅读全文

没有更多推荐了,返回首页