spring的cache方案需要相同参数调用同一个方法在数据一致的情况下返回结果也应该是一致的。
使用spring cache只需要完成两部分:
·缓存声明:
在方法上加上相应缓存注解和相应策略
·configuration:
定义缓存位置和具体保存策略
(spring cache并不是完全由spring提供,和transaction一样,只是对第三方框架进行上层封装)。
第一分部:缓存声明
3、基于注解的缓存声明:
我们只需要学习四个注解:
@Cacheable、@CachePut 、 @CacheEvict 和@Caching。
@Cacheable:
正如其名字,@Cacheable用于添加在需高速缓存的方法上。
这些方法默认会以参数为主键把返回结果存储到高速缓存中,以便在随后的调用(使用相同的参数)方法,直接返回高速缓存中的值,不需要实际执行此方法。
@Cacheable注解有三个参数,value是必须的,还有key和condition。
第一个参数,也就是value指明了缓存将被存到什么地方。
最简单的方式,只需要声明一个相关缓存策略的名称:
@Cacheable("books")
public Book findBook(ISBN isbn) {...}
也可以设置多个缓冲块,其中一个缓冲块命中即会返回,并会同步其他缓存块:
@Cacheable({ "books", "isbns" })
public Book findBook(ISBN isbn) {...}
默认缓存主键:
缓存是采用键值的方式存储,所以每次调用都要将相应的参数转化成一个合适的高效缓存主键。
默认的主键生成策略:
·如果没有参数,返回0;
·如果存在一个参数,则返回该参数实例;
·如果不止一个参数,返回所有参数的哈希计算值。
也可以同时实现org.springframework.cache.KeyGenerator来定义自己特定的主键生成策略。
自定义缓存主键:
由于缓存块是通用的,所以不能简单的进行缓存主键声明,这样将导致生成的主键与业务不服或者与其他业务重复,如:
@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
看上去应该并不是所有参数对于生成缓存主键都是有意义的。
任何存储在缓存中的数据为了高速访问都需要一个key。Spring默认使用被@Cacheable注解的方法的签名来作为key,当然你可以重写key,自定义key可以使用SpEL表达式。
像这种情况下,允许通过key属性来指定主键生成策略,且key支持使用SpEL:
@Cacheable(value="books", key="#isbn"
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed);
注解中"#isbn"是一个SpEL表达式,他将使用findBook()方法中的isbn参数作为key。
@Cacheable(value="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed);
@Cacheable(value="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed);
上面的key可以简单的通过SpEL调用参数的属性。
缓存条件(何时缓存):
同样可以对condition参数通过SpEL表达式,当返回true时将被缓存,若返回false会执行方法。
如下,当名字长度小于32时才会缓存:
@Cacheable(value="book", condition="#name.length < 32")
public Book findBook(String name)
@CachePut:
用法类似于@Cacheable,但是用于存放数据声明(如更新数据),所以每次都会执行,将执行后的结果存入缓存。
所以不建议把@CachePut and @Cacheable放在同一方法上,
对于需要更新的数据我们应使用 @CachePut。
@CacheEvict:
此注解对于去除无效的数据是非常重要的。@CacheEvict用于触发去除缓存中的数据。
除了和上面的注解用于标识缓存策略、主键和判断方式等外,又添加了allEntries属性,用于标识是否不仅只删除基于主键的数据:
@CacheEvict(value = "books", allEntries=true)
public void loadBooks(InputStream batch);
如上当需要清除一个“区域”的所有数据,而不是只清除一条数据,所以即使指定主键也是无用的。
除次之外还提供了一个beforeInvocation属性,用于表示是在执行前清除还是之后。这个属性是很有用的,比如当你在声明一个更新缓存方法之上(结合@Cacheable的场景)。
If the method does not execute (as it might be cached) or an exception is thrown, the eviction does not occur.
The latter (beforeInvocation=true) causes the eviction to occur always, before the method is invoked - this is useful in cases where the eviction does not need to be tied to the method outcome.
但是当无返回值(void)时,结合Cacheable将没有什么意义。
It is important to note that void methods can be used with @CacheEvict - as the methods act as triggers, the return values are ignored (as they don't interact with the cache) - this is not the case with @Cacheable which adds/update data into the cache and thus requires a result.
@Caching annotation:
有时我们需要添加多个注解,可以通过此注解嵌套在一起。
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(value = "secondary", key =
"#p0") })
public Book importBooks(String deposit, Date date);
自定义注解:
有时缓存的一些注解配置是常用重复的,为了避免来回拷贝,你可以自定义自己的注解,如:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Cacheable(value=“books”, key="#isbn")
public @interface SlowService {
}
然后你就可以通过你自定义的@SlowService注解替代下面的做法:
@Cacheable(value="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
替代方式:
@SlowService
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
(这样Spring就会自动识别,当然前提是你已经开启了缓存注解支持)
学习了,注解如何使用,那么如何通知Spring扫描相关注解呢?
缓存注解开启开关:
有时,你需要一个统一的开关进行控制缓存的开启和关闭。
只需要在你标注了@Configuration注解的类上添加@EnableCaching注解即可。
@Configuration
@EnableCaching
public class AppConfig {
}
或者通过XML方式配置使用的缓存:annotation-driven
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://
www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://
www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/
cache/spring-cache.xsd">
<cache:annotation-driven />
</beans>
<cache:annotation-driven />用于指定了缓存“封装”策略,那么具体的“实现”策略如何指定呢?
第二部分:configuration
4、Configuring the cache storage:
使用过Spring的同学应该很清楚,通过IoC,spring让各个层面的实现易于替换,spring cache也是如此。
添加缓存注解声明后,我们需要指定一个缓存管理器--“明确”具体的数据保存策略。
下面我们列出了两种:通过ConcurrentMap和ehcache。
JDK ConcurrentMap-based Cache:
<!-- generic cache manager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>
</set>
</property>
</bean>
上面的cache策略,简单的通过SimpleCacheManager来实现,可以用来测试或简单缓存(但是会非常高效,这种简单方式并没有指定超时等策略),配置中添加了两个缓存名称default、books。
Ehcache-based Cache:
ehcache的实现放在包org.springframework.cache.ehcache中,同样,也只需要非常简单的配置:
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cachemanager-
ref="ehcache"/>
<!-- Ehcache library setup -->
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:configlocation="
ehcache.xml"/>
第一部分扩充:基于XML的缓存
如果项目不允许或者不习惯使用注解,也可以像transaction一样,使用XML方式进行声明指定。
cache:advice配置中添加了对bookService的缓存策略,缓存策略名为books,对方法findBook进行了缓存,主键为#isbn。还包括清除缓存的声明。
aop:advisor通过AspectJ的方式进行了通知。
方法和transaction非常相似,可参考相关文章。
通过XML方式有一些好处:对已有代码没有任何侵入;便于统一指定范围内的类等。
第三部分:Hello World
第一步:添加缓存注解
// 添加缓存声明,demo1为缓存策略名称(我们会在XML中声明此策略)
@Cacheable("demo1")
@Override
public User get(Serializable entityId) {
return super.get(entityId);
}
第二步:打开缓存注解(我们采用的XML开启方式)
<cache:annotation-driven />
第三步:声明具体缓存策略
<cache:annotation-driven cache-manager="cacheManager" />
<!-- generic cache manager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean
class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
p:name="default" />
<bean
class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
p:name="demo1" />
</set>
</property>
</bean>
整体配置代码
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.1.xsd">
<cache:annotation-driven cache-manager="cacheManager" />
<!-- generic cache manager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean
class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
p:name="default" />
<bean
class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
p:name="gps" />
</set>
</property>
</bean>
</beans>
更新数据时,使缓存失效,下一次从数据库中获取
在Demo1基础上,更改如下即可:
@CacheEvict(value = "demo1", key = "#entity.userId")
@Override
public void update(Object entity) {
super.update(entity);
}
@CacheEvict的使用规则参照文章中相关解释。
试验步骤:打开列表页 -- > 修改数据--提交 -- > 刷新列表页数据已被更改 -- > 继续点击此数据修改连接(关闭只打开不修改),显示的数据为修改后的,证明@CacheEvict发挥了清除缓存的功能 -- > 然后直接SQL通过工具修改数据库该字段(一定要在修改后再次点击修改之后) -- > 再次点击列表页中此数据的修改连接,回显的数据非数据库实时数据,而是上次修改后的值,证明获取继续走了缓存。
将更新后的数据进行缓存,且更新前清除缓存
//不添加@CacheEvict,只添加@Cacheable,更新是无效的
@CacheEvict(value = "demo1", key = "#entity.userId", beforeInvocation = true)
@Cacheable(value = "demo1", key = "#entity.userId")
@Override
public User updateUser(User entity) {
// 更改了原有设计,返回了更改后的对象
super.update(entity);
return entity;
}
测试步骤:打开列表页 --> 修改一条数据 --> 打开数据库,验证更新生效,且从数据库中修改本条数据 --> 继续修改此条数据,回显数据为界面更改后的,不是数据库中实时数据(因为更新的主键和查询缓存主键相同)
也可以通过一个注解实现:
@CachePut(value = "demo1", key = "#entity.userId")
@Override
public User updateUser(User entity) {
// 更改了原有设计,返回了更改后的对象
super.update(entity);
return entity;
}
ehcache支持
我们接下来是介绍的Spring如何借助ehcache来对bean(dao、service、controller...)的调用结果进行缓存。(一般还有另外一种结合方案,如hibernate本身支持对ehcache的结合)
缓存注解无需变更和SimpleCacheManager一致,没有任何区别,所用其他缓存策略,只需要更改配置即可:
首先配置文件分两部分,spring指定ehcache和ehcache本身的缓存策略配置:
<cache:annotation-driven cache-manager="cacheManager"
key-generator="keyGenerator" />
<!-- spring-cache:cache相关 -->
<bean id="cacheManagerFactory"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
p:configLocation="classpath:META-INF/ehcache.xml" />
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"
p:cacheManager-ref="cacheManagerFactory" />
ehcache.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!--
• timeToIdleSeconds – The maximum number of seconds an element can exist in the cache without being accessed. The element expires at this limit and will no longer be returned from the cache. The default value is 0, which means no TTI eviction takes place (infinite lifetime).
• timeToLiveSeconds – The maximum number of seconds an element can exist in the cache regardless of use. The element expires at this limit and will no longer be returned from the cache. The default value is 0, which means no TTL eviction takes place (infinite lifetime).
• maxElementsOnDisk – The maximum sum total number of elements (cache entries) allowed for a distributed cache in all Terracotta clients. If this target is exceeded, eviction occurs to bring the count within the allowed target. The default value is 0, which means no eviction takes place (infinite size is allowed). Note that this value reflects storage allocated on the Terracotta Server Array. A setting of 0 means that no eviction of the cache's entries takes place on Terracotta Server Array, and consequently can cause the servers to run out of disk space.
• eternal – If the cache–™s eternal flag is set, it overrides any finite TTI/TTL values that have been set.
-->
<ehcache>
<defaultCache maxElementsInMemory="10" eternal="false"
timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false">
</defaultCache>
<cache name="demo1" maxElementsInMemory="20000" eternal="false"
timeToIdleSeconds="3600" timeToLiveSeconds="1800" overflowToDisk="false" />
<!-- <terracottaConfig url="localhost:9510"/> -->
</ehcache>
注意:@Cacheable(cacheName="demo1") 中的name属性和ehcache.xml 中的<cache name="demo1"必须要一致。
加入相关的jar:
ehcache-core.2.4.7 不能用2.5否则会报错
配置的过程的出现转换的错误,解决方案链接:
http://zhouhaitao.iteye.com/blog/852225
参考:
http://www.oschina.net/question/82993_70254
http://jinnianshilongnian.iteye.com/blog/2001040