Spring使用Cache
從3.1開始,Spring引入了對Cache的支援。其使用方法和原理都類似于Spring對事務管理的支援。Spring Cache是作用在方法上的,其核心思想是這樣的:當我們在調用一個緩存方法時會把該方法參數和傳回結果作為一個鍵值對存放在緩存中,等到下次利用同樣的參數來調用該方法時将不再執行該方法,而是直接從緩存中擷取結果進行傳回。是以在使用Spring Cache的時候我們要保證我們緩存的方法對于相同的方法參數要有相同的傳回結果。
使用Spring Cache需要我們做兩方面的事:
n 聲明某些方法使用緩存
n 配置Spring對Cache的支援
和Spring對事務管理的支援一樣,Spring對Cache的支援也有基于注解和基于XML配置兩種方式。下面我們先來看看基于注解的方式。
1 基于注解的支援
Spring為我們提供了幾個注解來支援Spring Cache。其核心主要是@Cacheable和@CacheEvict。使用@Cacheable标記的方法在執行後Spring Cache将緩存其傳回結果,而使用@CacheEvict标記的方法會在方法執行前或者執行後移除Spring Cache中的某些元素。下面我們将來詳細介紹一下Spring基于注解對Cache的支援所提供的幾個注解。
1.1 @Cacheable
@Cacheable可以标記在一個方法上,也可以标記在一個類上。當标記在一個方法上時表示該方法是支援緩存的,當标記在一個類上時則表示該類所有的方法都是支援緩存的。對于一個支援緩存的方法,Spring會在其被調用後将其傳回值緩存起來,以保證下次利用同樣的參數來執行該方法時可以直接從緩存中擷取結果,而不需要再次執行該方法。Spring在緩存方法的傳回值時是以鍵值對進行緩存的,值就是方法的傳回結果,至于鍵的話,Spring又支援兩種政策,預設政策和自定義政策,這個稍後會進行說明。需要注意的是當一個支援緩存的方法在對象内部被調用時是不會觸發緩存功能的。@Cacheable可以指定三個屬性,value、key和condition。
1.1.1 value屬性指定Cache名稱
value屬性是必須指定的,其表示目前方法的傳回值是會被緩存在哪個Cache上的,對應Cache的名稱。其可以是一個Cache也可以是多個Cache,當需要指定多個Cache時其是一個數組。
@Cacheable("cache1")//Cache是發生在cache1上的
public User find(Integer id) {
returnnull;
}
@Cacheable({"cache1", "cache2"})//Cache是發生在cache1和cache2上的
public User find(Integer id) {
returnnull;
}
1.1.2 使用key屬性自定義key
key屬性是用來指定Spring緩存方法的傳回結果時對應的key的。該屬性支援SpringEL表達式。當我們沒有指定該屬性時,Spring将使用預設政策生成key。我們這裡先來看看自定義政策,至于預設政策會在後文單獨介紹。
自定義政策是指我們可以通過Spring的EL表達式來指定我們的key。這裡的EL表達式可以使用方法參數及它們對應的屬性。使用方法參數時我們可以直接使用“#參數名”或者“#p參數index”。下面是幾個使用參數作為key的示例。
@Cacheable(value="users", key="#id")
public User find(Integer id) {
returnnull;
}
@Cacheable(value="users", key="#p0")
public User find(Integer id) {
returnnull;
}
@Cacheable(value="users", key="#user.id")
public User find(User user) {
returnnull;
}
@Cacheable(value="users", key="#p0.id")
public User find(User user) {
returnnull;
}
除了上述使用方法參數作為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對象的屬性。如:
@Cacheable(value={"users", "xxx"}, key="caches[1].name")
public User find(User user) {
returnnull;
}
1.1.3 condition屬性指定發生的條件
有的時候我們可能并不希望緩存一個方法所有的傳回結果。通過condition屬性可以實作這一功能。condition屬性預設為空,表示将緩存所有的調用情形。其值是通過SpringEL表達式來指定的,當為true時表示進行緩存處理;當為false時表示不進行緩存處理,即每次調用該方法時該方法都會執行一次。如下示例表示隻有當user的id為偶數時才會進行緩存。
@Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
public User find(User user) {
System.out.println("find user by user " + user);
return user;
}
1.2 @CachePut
在支援Spring Cache的環境下,對于使用@Cacheable标注的方法,Spring在每次執行前都會檢查Cache中是否存在相同key的緩存元素,如果存在就不再執行該方法,而是直接從緩存中擷取結果進行傳回,否則才會執行并将傳回結果存入指定的緩存中。@CachePut也可以聲明一個方法支援緩存功能。與@Cacheable不同的是使用@CachePut标注的方法在執行前不會去檢查緩存中是否存在之前執行過的結果,而是每次都會執行該方法,并将執行結果以鍵值對的形式存入指定的緩存中。
@CachePut也可以标注在類上和方法上。使用@CachePut時我們可以指定的屬性跟@Cacheable是一樣的。
@CachePut("users")//每次都會執行方法,并将結果存入指定的緩存中
public User find(Integer id) {
returnnull;
}
1.3 @CacheEvict
@CacheEvict是用來标注在需要清除緩存元素的方法或類上的。當标記在一個類上時表示其中所有的方法的執行都會觸發緩存的清除操作。@CacheEvict可以指定的屬性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的語義與@Cacheable對應的屬性類似。即value表示清除操作是發生在哪些Cache上的(對應Cache的名稱);key表示需要清除的是哪個key,如未指定則會使用預設政策生成的key;condition表示清除操作發生的條件。下面我們來介紹一下新出現的兩個屬性allEntries和beforeInvocation。
1.3.1 allEntries屬性
allEntries是boolean類型,表示是否需要清除緩存中的所有元素。預設為false,表示不需要。當指定了allEntries為true時,Spring Cache将忽略指定的key。有的時候我們需要Cache一下清除所有的元素,這比一個一個清除元素更有效率。
@CacheEvict(value="users", allEntries=true)
public void delete(Integer id) {
System.out.println("delete user by id: " + id);
}
1.3.2 beforeInvocation屬性
清除操作預設是在對應方法成功執行之後觸發的,即方法如果因為抛出異常而未能成功傳回時也不會觸發清除操作。使用beforeInvocation可以改變觸發清除操作的時間,當我們指定該屬性值為true時,Spring會在調用該方法之前清除緩存中的指定元素。
@CacheEvict(value="users", beforeInvocation=true)
public void delete(Integer id) {
System.out.println("delete user by id: " + id);
}
其實除了使用@CacheEvict清除緩存元素外,當我們使用Ehcache作為實作時,我們也可以配置Ehcache自身的驅除政策,其是通過Ehcache的配置檔案來指定的。由于Ehcache不是本文描述的重點,這裡就不多贅述了,想了解更多關于Ehcache的資訊,請檢視我關于Ehcache的專欄。
1.4 @Caching
@Caching注解可以讓我們在一個方法或者類上同時指定多個Spring Cache相關的注解。其擁有三個屬性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。
@Caching(cacheable = @Cacheable("users"), evict = { @CacheEvict("cache2"),
@CacheEvict(value = "cache3", allEntries = true) })
public User find(Integer id) {
returnnull;
}
1.5 使用自定義注解
Spring允許我們在配置可緩存的方法時使用自定義的注解,前提是自定義的注解上必須使用對應的注解進行标注。如我們有如下這麼一個使用@Cacheable進行标注的自定義注解。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Cacheable(value="users")
public @interface MyCacheable {
}
那麼在我們需要緩存的方法上使用@MyCacheable進行标注也可以達到同樣的效果。
@MyCacheable
public User findById(Integer id) {
System.out.println("find user by id: " + id);
User user = new User();
user.setId(id);
user.setName("Name" + id);
return user;
}
2 配置Spring對Cache的支援
2.1 聲明對Cache的支援
2.1.1 基于注解
配置Spring對基于注解的Cache的支援,首先我們需要在Spring的配置檔案中引入cache命名空間,其次通過<cache:annotation-driven />就可以啟用Spring對基于注解的Cache的支援。
<?xml version="1.0" encoding="UTF-8"?>
<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-3.0.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<cache:annotation-driven/>
</beans>
<cache:annotation-driven/>有一個cache-manager屬性用來指定目前所使用的CacheManager對應的bean的名稱,預設是cacheManager,是以當我們的CacheManager的id為cacheManager時我們可以不指定該參數,否則就需要我們指定了。
<cache:annotation-driven/>還可以指定一個mode屬性,可選值有proxy和aspectj。預設是使用proxy。
此外,<cache:annotation-driven/>還可以指定一個proxy-target-class屬性,表示是否要代理class,預設為false。我們前面提到的@Cacheable、@cacheEvict等也可以标注在接口上,這對于基于接口的代理來說是沒有什麼問題的,但是需要注意的是當我們設定proxy-target-class為true或者mode為aspectj時,是直接基于class進行操作的,定義在接口上的@Cacheable等Cache注解不會被識别到,那對應的Spring Cache也不會起作用了。
需要注意的是<cache:annotation-driven/>隻會去尋找定義在同一個ApplicationContext下的@Cacheable等緩存注解。
2.1.2 基于XML配置
除了使用注解來聲明對Cache的支援外,Spring還支援使用XML來聲明對Cache的支援。這主要是通過類似于aop:advice的cache:advice來進行的。在cache命名空間下定義了一個cache:advice元素用來定義一個對于Cache的advice。其需要指定一個cache-manager屬性,預設為cacheManager。cache:advice下面可以指定多個cache:caching元素,其有點類似于使用注解時的@Caching注解。cache:caching元素下又可以指定cache:cacheable、cache:cache-put和cache:cache-evict元素,它們類似于使用注解時的@Cacheable、@CachePut和@CacheEvict。下面來看一個示例:
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
<cache:caching cache="users">
<cache:cacheable method="findById" key="#p0"/>
<cache:cacheable method="find" key="#user.id"/>
<cache:cache-evict method="deleteAll" all-entries="true"/>
</cache:caching>
</cache:advice>
上面配置定義了一個名為cacheAdvice的cache:advice,其中指定了将緩存findById方法和find方法到名為users的緩存中。這裡的方法還可以使用通配符“*”,比如“find*”表示任何以“find”開始的方法。
有了cache:advice之後,我們還需要引入aop命名空間,然後通過aop:config指定定義好的cacheAdvice要應用在哪些pointcut上。如:
<aop:config proxy-target-class="false">
<aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.xxx.UserService.*(..))"/>
</aop:config>
上面的配置表示在調用com.xxx.UserService中任意公共方法時将使用cacheAdvice對應的cache:advice來進行Spring Cache處理。更多關于Spring Aop的内容不在本文讨論範疇内。
2.2 配置CacheManager
CacheManager是Spring定義的一個用來管理Cache的接口。Spring自身已經為我們提供了兩種CacheManager的實作,一種是基于Java API的ConcurrentMap,另一種是基于第三方Cache實作——Ehcache,如果我們需要使用其它類型的緩存時,我們可以自己來實作Spring的CacheManager接口或AbstractCacheManager抽象類。下面分别來看看Spring已經為我們實作好了的兩種CacheManager的配置示例。
2.2.1 基于ConcurrentMap的配置
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<beanclass="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="xxx"/>
</set>
</property>
</bean>
上面的配置使用的是一個SimpleCacheManager,其中包含一個名為“xxx”的ConcurrentMapCache。
2.2.2 基于Ehcache的配置
<!-- Ehcache實作 -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"p:cache-manager-ref="ehcacheManager"/>
<bean id="ehcacheManager"class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache-spring.xml"/>
上面的配置使用了一個Spring提供的EhCacheCacheManager來生成一個Spring的CacheManager,其接收一個Ehcache的CacheManager,因為真正用來存入緩存資料的還是Ehcache。Ehcache的CacheManager是通過Spring提供的EhCacheManagerFactoryBean來生成的,其可以通過指定ehcache的配置檔案位置來生成一個Ehcache的CacheManager。若未指定則将按照Ehcache的預設規則取classpath根路徑下的ehcache.xml檔案,若該檔案也不存在,則擷取Ehcache對應jar包中的ehcache-failsafe.xml檔案作為配置檔案。更多關于Ehcache的内容這裡就不多說了,它不屬于本文讨論的内容,欲了解更多關于Ehcache的内容可以參考我之前釋出的Ehcache系列文章,也可以參考官方文檔等。
3 鍵的生成政策
鍵的生成政策有兩種,一種是預設政策,一種是自定義政策。
3.1 預設政策
預設的key生成政策是通過KeyGenerator生成的,其預設政策如下:
n 如果方法沒有參數,則使用0作為key。
n 如果隻有一個參數的話則使用該參數作為key。
n 如果參數多餘一個的話則使用所有參數的hashCode作為key。
如果我們需要指定自己的預設政策的話,那麼我們可以實作自己的KeyGenerator,然後指定我們的Spring Cache使用的KeyGenerator為我們自己定義的KeyGenerator。
使用基于注解的配置時是通過cache:annotation-driven指定的.
<cache:annotation-driven key-generator="userKeyGenerator"/>
<bean id="userKeyGenerator" class="com.xxx.cache.UserKeyGenerator"/>
而使用基于XML配置時是通過cache:advice來指定的。
<cache:advice id="cacheAdvice" cache-manager="cacheManager" key-generator="userKeyGenerator">
</cache:advice>
需要注意的是此時我們所有的Cache使用的Key的預設生成政策都是同一個KeyGenerator。
3.2 自定義政策
自定義政策是指我們可以通過Spring的EL表達式來指定我們的key。這裡的EL表達式可以使用方法參數及它們對應的屬性。使用方法參數時我們可以直接使用“#參數名”或者“#p參數index”。下面是幾個使用參數作為key的示例。
@Cacheable(value="users", key="#id")
public User find(Integer id) {
returnnull;
}
@Cacheable(value="users", key="#p0")
public User find(Integer id) {
returnnull;
}
@Cacheable(value="users", key="#user.id")
public User find(User user) {
returnnull;
}
@Cacheable(value="users", key="#p0.id")
public User find(User user) {
returnnull;
}
除了上述使用方法參數作為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對象的屬性。如:
@Cacheable(value={"users", "xxx"}, key="caches[1].name")
public User find(User user) {
returnnull;
}
4 Spring單獨使用Ehcache
前面介紹的内容是Spring内置的對Cache的支援,其實我們也可以通過Spring自己單獨的使用Ehcache的CacheManager或Ehcache對象。通過在Application Context中配置EhCacheManagerFactoryBean和EhCacheFactoryBean,我們就可以把對應的EhCache的CacheManager和Ehcache對象注入到其它的Spring bean對象中進行使用。
4.1 EhCacheManagerFactoryBean
EhCacheManagerFactoryBean是Spring内置的一個可以産生Ehcache的CacheManager對象的FactoryBean。其可以通過屬性configLocation指定用于建立CacheManager的Ehcache配置檔案的路徑,通常是ehcache.xml檔案的路徑。如果沒有指定configLocation,則将使用預設位置的配置檔案建立CacheManager,這是屬于Ehcache自身的邏輯,即如果在classpath根路徑下存在ehcache.xml檔案,則直接使用該檔案作為Ehcache的配置檔案,否則将使用ehcache-xxx.jar中的ehcache-failsafe.xml檔案作為配置檔案來建立Ehcache的CacheManager。此外,如果不希望建立的CacheManager使用預設的名稱(在ehcache.xml檔案中定義的,或者是由CacheManager内部定義的),則可以通過cacheManagerName屬性進行指定。下面是一個配置EhCacheManagerFactoryBean的示例。
<!-- 定義CacheManager -->
<bean id="cacheManager"class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<!-- 指定配置檔案的位置 -->
<property name="configLocation" value="/WEB-INF/config/ehcache.xml"/>
<!-- 指定建立的CacheManager的名稱 -->
<property name="cacheManagerName" value="cacheManagerName"/>
</bean>
4.2 EhCacheFactoryBean
EhCacheFactoryBean是用來産生Ehcache的Ehcache對象的FactoryBean。定義EhcacheFactoryBean時有兩個很重要的屬性我們可以來指定。一個是cacheManager屬性,其可以指定将用來擷取或建立Ehcache的CacheManager對象,若未指定則将通過CacheManager.create()擷取或建立預設的CacheManager。另一個重要屬性是cacheName,其表示目前EhCacheFactoryBean對應的是CacheManager中的哪一個Ehcache對象,若未指定預設使用beanName作為cacheName。若CacheManager中不存在對應cacheName的Ehcache對象,則将使用CacheManager建立一個名為cacheName的Cache對象。此外我們還可以通過EhCacheFactoryBean的timeToIdle、timeToLive等屬性指定要建立的Cache的對應屬性,注意這些屬性隻對CacheManager中不存在對應Cache時建立的Cache才起作用,對已經存在的Cache将不起作用,更多屬性設定請參考Spring的API文檔。此外還有幾個屬性是對不管是已經存在還是新建立的Cache都起作用的屬性:statisticsEnabled、sampledStatisticsEnabled、disabled、blocking和cacheEventListeners,其中前四個預設都是false,最後一個表示為目前Cache指定CacheEventListener。下面是一個定義EhCacheFactoryBean的示例。
<!-- 定義CacheManager -->
<bean id="cacheManager"class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<!-- 指定配置檔案的位置 -->
<property name="configLocation" value="/WEB-INF/config/ehcache.xml"/>
<!-- 指定建立的CacheManager的名稱 -->
<property name="cacheManagerName" value="cacheManagerName"/>
</bean>
<!-- 定義一個Ehcache -->
<bean id="userCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheName" value="user"/>
<property name="cacheManager" ref="cacheManager"/>
</bean>
(注:本文是基于Spring3.1.0所寫)
ehcache.xml:裡面的注釋寫的很清楚了。
<diskStore> : 當記憶體緩存中對象數量超過maxElementsInMemory時,将緩存對象寫到磁盤緩存中(需對象實作序列化接口)
<diskStore path=""> : 用來配置磁盤緩存使用的實體路徑,Ehcache磁盤緩存使用的檔案字尾名是*.data和*.index
name : "緩存名稱,cache的唯一辨別(ehcache會把這個cache放到HashMap裡)
maxElementsInMemory : 緩存最大個數。
eternal="false" : 對象是否永久有效,一但設定了,timeout将不起作用。 (必須設定)
maxEntriesLocalHeap="1000" : 堆記憶體中最大緩存對象數,0沒有限制(必須設定)
maxEntriesLocalDisk= "1000" : 硬碟最大緩存個數。
overflowToDisk="false" : 當緩存達到maxElementsInMemory值是,是否允許溢出到磁盤(必須設定)(記憶體不足時,是否啟用磁盤緩存。)
diskSpoolBufferSizeMB : 這個參數設定DiskStore(磁盤緩存)的緩存區大小。預設是30MB。每個Cache都應該有自己的一個緩沖區。
diskPersistent="false" : 磁盤緩存在JVM重新啟動時是否保持(預設為false)
timeToIdleSeconds="0" : 導緻元素過期的通路間隔(秒為機關),即當緩存閑置n秒後銷毀。 當eternal為false時,這個屬性才有效,0表示可以永遠空閑,預設為0
timeToLiveSeconds="600" : 元素在緩存裡存在的時間(秒為機關),即當緩存存活n秒後銷毀. 0 表示永遠存在不過期
memoryStoreEvictionPolicy="LFU" : 當達到maxElementsInMemory時,如何強制進行驅逐預設使用"最近使用(LRU)"政策,其它還有先入先出FIFO,最少使用LFU,較少使用LRU
diskExpiryThreadIntervalSeconds :磁盤失效線程運作時間間隔,預設是120秒。
clearOnFlush : 記憶體數量最大時是否清除。
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd"
updateCheck="true" monitoring="autodetect"
dynamicConfig="true">
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxEntriesLocalHeap="10000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskSpoolBufferSizeMB="30"
maxEntriesLocalDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
<cache name="myCache"
maxEntriesLocalHeap="10000"
maxEntriesLocalDisk="1000"
eternal="false"
diskSpoolBufferSizeMB="30"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"
transactionalMode="off">
<persistence strategy="localTempSwap"/>
</cache>
</ehcache>
這是使用緩存的例子 http://www.cnblogs.com/jianjianyang/p/4938765.html
spring整合ehcache 注解實作查詢緩存,并實作實時緩存更新或删除
寫在前面:上一篇部落格寫了spring cache和ehcache的基本介紹,個人建議先把這些最基本的知識了解了才能對今天主題有所感觸。不多說了,開幹!
注:引入jar
<!-- 引入ehcache緩存 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.8.3</version>
</dependency>
第一步:首先配置ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd"
updateCheck="true" monitoring="autodetect"
dynamicConfig="true">
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxEntriesLocalHeap="10000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskSpoolBufferSizeMB="30"
maxEntriesLocalDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
<cache name="myCache"
maxEntriesLocalHeap="10000"
maxEntriesLocalDisk="1000"
eternal="false"
diskSpoolBufferSizeMB="30"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"
transactionalMode="off">
<persistence strategy="localTempSwap"/>
</cache>
</ehcache>
第二步:在spring.xml的配置檔案中引入schema,
xmlns:aop="http://www.springframework.org/schema/aop"和http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.2.xsd
緩存的配置:
<!-- 啟用緩存注解功能,這個是必須的,否則注解不會生效,另外,該注解一定要聲明在spring主配置檔案中才會生效 -->
<cache:annotation-driven cache-manager="ehcacheManager"/>
<!-- cacheManager工廠類,指定ehcache.xml的位置 -->
<bean id="ehcacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache.xml" />
</bean>
<!-- 聲明cacheManager -->
<bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="ehcacheManagerFactory" />
</bean>
OK!緩存的相關配置已經完成。下面開始編寫測試程式。這裡需要連接配接資料庫,我就不寫了。這裡為了友善就随便找了之前寫過的model,這個model就是AOP注解實作日志管理的實體,為了偷懶就直接用了,希望你們不要誤解,沒有特殊意義的
第三步:編寫model,這裡需要注意,要實作緩存的實體必須要序列化 private static final long serialVersionUID = -6579533328390250520L; 關于序列化的生成這裡就不介紹了,大家可以百度看看。
View Code
第四步:編寫dao,service
View Code
public interface SystemLogService {
int deleteSystemLog(String id);
int insert(SystemLog record);
int insertTest(SystemLog record);
SystemLog findSystemLog(String id);
int updateSystemLog(SystemLog record);
int count();
}
第五步:編寫serviceImpl并添加緩存注解。這裡緩存注解的參數不介紹了,不懂得看我上一篇部落格,我這裡先把需要的注解都寫上了,一會一個一個介紹。
@Service("systemLogService")
public class SystemLogServiceImpl implements SystemLogService {
@Resource
private SystemLogMapper systemLogMapper;
@Override
public int deleteSystemLog(String id) {
return systemLogMapper.deleteByPrimaryKey(id);
}
@Override
//@CachePut(value="myCache")
//@CacheEvict(value="myCache",allEntries=true,beforeInvocation=true)
@CacheEvict(value="myCache",key="0",beforeInvocation=true)
public int insert(SystemLog record) {
return systemLogMapper.insertSelective(record);
}
@Override
@Cacheable(value="myCache",key="#id")
public SystemLog findSystemLog(String id) {
return systemLogMapper.selectByPrimaryKey(id);
}
@Override
public int updateSystemLog(SystemLog record) {
return systemLogMapper.updateByPrimaryKeySelective(record);
}
@Override
public int insertTest(SystemLog record) {
return systemLogMapper.insert(record);
}
@Override
@Cacheable(value="myCache",key="0")
public int count() {
int num = systemLogMapper.count();
return num;
}
}
第六步:編寫controller,即我們的測試。
@Controller
@RequestMapping("systemLogController")
public class SystemLogController {
@Resource
private SystemLogService systemLogService;
@RequestMapping("testLog")
public ModelAndView testLog(){
ModelMap modelMap = new ModelMap();
SystemLog systemLog = systemLogService.findSystemLog("c30e2398-079a-406b-a2f7-a85fa15ccac7");
modelMap.addAttribute("data", systemLog);
return new ModelAndView("index",modelMap);
}
@RequestMapping("insert")
@ResponseBody
public boolean Insert(SystemLog record){
systemLogService.insert(record);
return true;
}
@RequestMapping("test1")
public ModelAndView test1(){
ModelMap modelMap = new ModelMap();
int num =systemLogService.count();
modelMap.addAttribute("num", num);
return new ModelAndView("pageEhcache",modelMap);
}
}
我們先測試查詢的緩存,即serviceImpl中的 findSystemLog(String id) 方法,我們通路testLog.do,第一次運作如下圖,注意控制台中的heap和 disk
再一次通路testLog.do,運作你會發現沒有通路資料庫,如圖:
到此查詢的緩存我們實作了,但是關于緩存的處理我們并沒有做完,我們應該在深入思考下,在上面查詢的緩存生命周期内,我們對剛才查詢的表進行了增删改操作,這時我們再通路該查詢方法,你會發現我們的資料并沒有改變,還是增删改操作之前的資料(因為緩存的生命還在),這裡是不是問題呢?此時我們需要對查詢的緩存進行更新或删除。
下面我們看serviceImpl中的insert方法和count()方法,count的方法是統計表中的資料總記錄,insert方法是對該表進行新增一條記錄,insert的緩存注解用的是@CacheEvict(value="myCache",key="0",beforeInvocation=true),這裡清除的是指定緩存,也就是count方法中@Cacheable(value="myCache",key="0")的,(serviceImpl中注釋的@CacheEvict(value="myCache",allEntries=true,beforeInvocation=true)是清除所有的緩存,這裡我就不示範了,道理是一樣的)
這裡我提供一個測試pageEhcache.jsp頁面,
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>測試</title>
<script type="text/javascript" src="<%=request.getContextPath()%>/js/jquery-1.11.1.min.js"></script>
<script type="text/javascript">
function insert(){
var record = $("#formID").serializeArray();
console.info(record);
$.ajax({
url : "<%=request.getContextPath()%>/systemLogController/insert.do",
type : 'post',
async:true,
dataType:'json',
data : record,
success:function(result){
alert("插入成功!");
}
});
}
</script>
</head>
<body>
<h1><%=new Date()%></h1>
<h1>這是一個練習</h1>
<form id="formID" action="">
id: <input name="id" type="text"/><br>
<input type="button" value="插入" οnclick="insert()"/>
</form>
<br>
總數:
<h4>${num}</h4>
</body>
</html>
我們先通路test1.do,看下表中的記錄數并注意控制台變化
頁面顯示如下,注意總數是67,
再一次通路test1.do,沒有通路資料庫,說明count()方法的緩存生效了,
接下來開始新增記錄,點選插入按鈕
注意控制台顯示,這裡執行了inserSQL語句,并remove了count()方法上的緩存,
接下來再次通路test1.do,我們看到總數變化了,增加了一條,說明我們把之前count()方法上的緩存删除了,又執行了查詢總數的sql
再次通路test1.do,count()方法的緩存生效了,對吧!這個就是@CacheEvict注解的作用。
在insert()方法上還有@CachePut(value="myCache")注解,上面的serviceImpl中注釋了,它的作用是:@CachePut标注的方法在執行前不會去檢查緩存中是否存在之前執行過的結果,而是每次都會執行該方法,并将執行結果以鍵值對的形式存入指定的緩存中。
我這裡就不做示範了,你們可以自己動手試試。
總結:我個人的了解,對查詢方法增加緩存容易,但對于緩存的更新的處理就比較麻煩,我上面的serviceImpl中寫了三種處理方式,
1.用@CachePut處理,這中方法需要對指定緩存key保持一緻,盡管這樣,還是不行,因為它傳回的緩存是int(增加或删除或修改的記錄數或是該記錄的對象,這對我們查詢所有或部分記錄的緩存還是不可行的)
2.用@CacheEvict(value="myCache",key="0",beforeInvocation=true)處理,清除我們指定key的緩存,這種方式缺點是麻煩,需要我們注意每一個緩存的key
3.用@CacheEvict(value="myCache",allEntries=true,beforeInvocation=true)處理,清除所有緩存,這種方式最省事,但會把其他緩存也一同清除。
随着業務的複雜性的不斷增加,這些處理方式,可能會增加代碼的複雜性,然後我想到的是對DB層進行緩存,可以利用redis,mamchched的進行處理。當然對于一般的web應用運用ehcache已經刻一解決了,但是對大資料量的運用db級别的緩存效果性能可能會更好。
以上純粹是個人想法。另外我也想了想緩存到底在哪些場景下應用會比較好,不知道你們是怎麼認為的。也請大家給點建議。