javascript
Spring Cache抽象-缓存注解
文章目錄
- 概述
- Spring緩存的基本原理
- @Cacheable :主要針對方法配置,能夠根據方法的請求參數對其結果進行緩存
- 鍵生成器
- 帶條件的緩存
- @Cacheable 注解參數說明
- 示例-緩存管理器為SimpleCacheManager基于ConcurrentMap的配置
- @CachePut 主要針對方法配置,能夠根據方法的請求參數對其結果進行緩存,和 @Cacheable 不同的是,它每次都會觸發真實方法的調用
- @CachePut 注解參數說明
- 示例
- @CacheEvict 主要針對方法配置,能夠根據一定的條件對緩存進行清空
- @CacheEvict注解參數說明
- 示例
- @Caching 主要針對方法配置, 是一個組注解
- @CacheConfig 類級別的全局緩存注解
- 完整示例
概述
Spring Cache提供了5種可以在方法級別或者類級別上使用的緩存注解。這些注解定義了哪些方法的返回值會被緩存或者從緩存中移除。
需要注意的是,只有public定義的方法才可以被緩存, private、protected或者使用default修飾符的方法都不能被緩存。
當在一個類上使用注解時,該類中每個公共方法的返回值都將被緩存到指定的緩存項或者從中移除。
來簡單看下5種注解:
@Cacheable: triggers cache population@CacheEvict: triggers cache eviction@CachePut: updates the cache without interfering with the method execution@Caching: regroups multiple cache operations to be applied on a method@CacheConfig: shares some common cache-related settings at class-levelSpring緩存的基本原理
和 spring 的事務管理類似,spring cache 的關鍵原理就是 spring AOP,通過 spring AOP,其實現了在方法調用前、調用后獲取方法的入參和返回值,進而實現了緩存的邏輯。
我們通過下圖來理解一下
原始方法調用圖:
當客戶端“Calling code”調用一個普通類 Plain Object 的 foo() 方法的時候,是直接作用在 pojo 類自身對象上的,客戶端擁有的是被調用者的直接的引用
動態代理調用圖:
Spring cache 利用了 Spring AOP 的動態代理技術,即當客戶端嘗試調用 pojo 的 foo()方法的時候,給他的不是 pojo 自身的引用,而是一個動態生成的代理類,這個時候,實際客戶端擁有的是一個代理的引用,那么在調用 foo() 方法的時候,會首先調用 proxy 的 foo() 方法,這個時候 proxy 可以整體控制實際的 pojo.foo() 方法的入參和返回值,比如緩存結果,比如直接略過執行實際的 foo() 方法等,都是可以輕松做到的.
@Cacheable :主要針對方法配置,能夠根據方法的請求參數對其結果進行緩存
@Cacheable是最主要注解,它指定了被注解方法的返回值是可以被緩存的。 其工作原理是Spring首先會在緩存中查找數據,如果沒有則執行方法并緩存結果,然后返回數據。
緩存名稱是必須要提供的,可以使用 引號、Value或者acheNames屬性來定義名稱。 比如
@Cacheable("artisan") // Spring 3.X @Cacheable(value="artisan") // Spring 4.0新增了value的別名cacheNames,更貼切,推薦使用 @Cacheable(cacheNames="artisan")此外,還可以以列表的形式提供多個緩存,在該列表中使用逗號分隔緩存名稱,并用花括號括起來。比如
@Cacheable(cacheNames = {"cache1","cache2"})我們看下如何在方法上使用@Cacheable
/*** * * @Title: getArtisan* * @Description: @Cacheable(cacheNames = "littleArtisan")* 使用名為littleArtisan的緩存* * * @return* * @return: LittleArtisan*/@Cacheable(cacheNames = "littleArtisan")public LittleArtisan getArtisan(String artisanName) {// 方法內部實現不考慮緩存邏輯,直接實現業務System.out.println("查找Artisan:" + artisanName);return getFromDB(artisanName);}getArtisan(String artisanName) 方法以artisanName為鍵將Artisan緩存到littleArtisan緩存段中。
鍵生成器
緩存的本質就是鍵值對集合。 在默認情況下,緩存抽象使用方法簽名以及參數作為key,并將該鍵與方法調用的結果作為Value,如果在Cache注解上沒有指定Key,則Spring會使用KeyGenerator來生成一個key.
我們來看下org.springframework.cache.interceptor.KeyGenerator
Spring默認提供了SimpleKeyGenerator生成器。 Spring4.0廢棄了3.X的DefaultKeyGenerator ,使用SimpleKeyGenerator作為默認的生成器,避免了使用DefaultKeyGenerator 造成key沖突的問題。
/**......* @since 3.1* @deprecated as of Spring 4.0, in favor of {@link SimpleKeyGenerator}* or custom {@link KeyGenerator} implementations based on hash codes*/ @Deprecated public class DefaultKeyGenerator implements KeyGenerator { /**...... * @since 4.0* @see SimpleKey* @see DefaultKeyGenerator* @see org.springframework.cache.annotation.CachingConfigurer*/ public class SimpleKeyGenerator implements KeyGenerator {我們來看下SimpleKeyGenerator的生成Key的規則:
/*** Generate a key based on the specified parameters.*/public static Object generateKey(Object... params) {if (params.length == 0) {return SimpleKey.EMPTY;}if (params.length == 1) {Object param = params[0];if (param != null && !param.getClass().isArray()) {return param;}}return new SimpleKey(params);} /*** Create a new {@link SimpleKey} instance.* @param elements the elements of the key*/public SimpleKey(Object... elements) {Assert.notNull(elements, "Elements must not be null");this.params = new Object[elements.length];System.arraycopy(elements, 0, this.params, 0, elements.length);this.hashCode = Arrays.deepHashCode(this.params);}通過源碼 ,可以得出生成規則如下:
-
如果方法沒有入參,這是用SimpleKey,EMPTY作為key
-
如果只有一個入參,這是用該入參作為Key
-
如果有多個入參,則返回包含所有入參的一個SimpleKey
此外,還可以我在聲明中指定鍵值,@Cacheable注解提供了實現該功能的key屬性,通過該屬性,可以使用SpELl指定自定義鍵。 如下所示
@Cacheable(cacheNames="artisan" ,key="#artisan.artisanCode") public Artisan getArtisan(Artisan,boolean checkLogout)如果我們不想boolean checkLogout 作為key的一部分,則可以通過key屬性指定使用artisanCode作為緩存鍵。
當然了,我們也可以實現org.springframework.cache.interceptor.KeyGenerator接口來定義個性化的key生成器。比如自定義一個MyKeyGenerator類并實現了KeyGenerator接口 ,使用如下:
@Cacheable(cacheNames="artisan" ,key="myKeyGenerator") public Artisan getArtisan(Artisan,boolean checkLogout)帶條件的緩存
使用@Cacheable注解的condition屬性可按條件進行緩存,condition屬性使用了SpELl表達式動態評估方法入參是否滿足緩存條件。
比如:
@Cacheable(cacheNames=”artisanCache”,condition=”#artisan.age <18` public Artisan getArtisan(Artisan artisan){...... }在上述代碼中, #artisan引用方法的同名入參變量,接著通過.age訪問artisan入參對象的age屬性。 在調用方法前,將對注解中聲明的條件進行評估,滿足條件才緩存。
與condition屬性相反,可以使用unless屬性排除某些不希望緩存的對象。 如下
@Cacheable(cacheNames=”artisanCache”,unless=”#artisan.age >= 18` public Artisan getArtisan(Artisan artisan){...... }@Cacheable 注解參數說明
| value/cacheNames | 緩存的名稱,在 spring 配置文件中定義,必須指定至少一個 | @Cacheable(cacheNames=”mycache”) 或者@Cacheable(cacheNames={”cache1”,”cache2”} |
| key | 緩存的 key,可以為空,如果指定要按照 SpEL 表達式編寫,如果不指定,則缺省按照方法的所有參數進行組合 | @Cacheable(cacheNames=”artisanCache”,key=”#artisanName”) |
| condition | 緩存的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才進行緩存,unless屬性和condition屬性相反,滿足條件則不進行緩存 | @Cacheable(cacheNames=”artisanCache”,condition=”#artisanName.length()>2”) |
示例-緩存管理器為SimpleCacheManager基于ConcurrentMap的配置
)
我們數據庫中有個little_artisan表,表中數據如下:
我們的目標是只緩存 artisan_name=masterArtisan的數據。
步驟:
domian實體類 Artisan.java
package com.xgj.cache.springCacheAnno;import java.io.Serializable;/*** * * @ClassName: LittleArtisan* * @Description: Java中的緩存和序列化是息息相關的,注意實現Serializable接口* * @author: Mr.Yang* * @date: 2017年10月2日 下午1:40:53*/public class Artisan implements Serializable {private static final long serialVersionUID = 1L;private String artisanId;private String artisanName;private String artisanDesc;public String getArtisanId() {return artisanId;}public void setArtisanId(String artisanId) {this.artisanId = artisanId;}public String getArtisanName() {return artisanName;}public void setArtisanName(String artisanName) {this.artisanName = artisanName;}public String getArtisanDesc() {return artisanDesc;}public void setArtisanDesc(String artisanDesc) {this.artisanDesc = artisanDesc;}public static long getSerialversionuid() {return serialVersionUID;}}可供查詢數據庫的服務層ArtisanSpringCacheService, 在其中標注緩存的對象及條件
package com.xgj.cache.springCacheAnno.cacheable;import java.sql.ResultSet; import java.sql.SQLException;import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; import org.springframework.jdbc.core.RowCallbackHandler; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Service;import com.xgj.cache.springCacheAnno.Artisan;/*** * * @ClassName: LittleArtisanSpringCacheService* * @Description: @Service標注的服務層,受Spring管理* * @author: Mr.Yang* * @date: 2017年10月2日 下午5:34:29*/@Service public class ArtisanSpringCacheService {private Logger logger = Logger.getLogger(ArtisanSpringCacheService.class);// 模糊查詢 在參數的值里設置(%),查詢sql語句就只是個命名參數private static final String selectArtisanSQL = "select artisan_id ,artisan_name ,artisan_desc "+ " from little_artisan "+ " where artisan_name like :artisanName ";private NamedParameterJdbcTemplate jdbcTemplate;@Autowiredpublic void setJdbcTemplate(NamedParameterJdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}/*** * * @Title: getArtisan* * @Description: @Cacheable(cacheNames = "littleArtisan")* 使用名為littleArtisan的緩存,符合#artisanName == 'masterArtisan'才緩存* * * @return* * @return: LittleArtisan*/@Cacheable(cacheNames = "littleArtisan", condition = "#artisanName == 'masterArtisan'")// @Cacheable(cacheNames = "littleArtisan", unless =// "#artisanName == 'masterArtisan'")public Artisan getArtisan(String artisanName) {// 方法內部實現不考慮緩存邏輯,直接實現業務System.out.println("根據ArtisanName查找Artisan:" + artisanName);return getFromDB(artisanName);}/*** * * @Title: getFromDB* * @Description: 這里只是為了演示下* 使用NamedParameterJdbcTemplate模糊查詢的用法。其實有可能返回的是一個List。* * @param artisanName* @return* * @return: LittleArtisan*/private Artisan getFromDB(String artisanName) {System.out.println("getFromDB");final Artisan littleArtisan = new Artisan();// 使用MapSqlParameterSource綁定參數 ,拼接模糊查詢MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource().addValue("artisanName", "%" + artisanName + "%");jdbcTemplate.query(selectArtisanSQL, mapSqlParameterSource,new RowCallbackHandler() {@Overridepublic void processRow(ResultSet rs) throws SQLException {littleArtisan.setArtisanId(rs.getString("artisan_id"));littleArtisan.setArtisanName(rs.getString("artisan_name"));littleArtisan.setArtisanDesc(rs.getString("artisan_desc"));}});return littleArtisan;}}Spring配置文件
<?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:p="http://www.springframework.org/schema/p"xmlns:context="http://www.springframework.org/schema/context"xmlns:util="http://www.springframework.org/schema/util"xmlns:cache="http://www.springframework.org/schema/cache"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsdhttp://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"><!-- 掃描類包,將標注Spring注解的類自動轉化Bean,同時完成Bean的注入 --><context:component-scan base-package="com.xgj.cache.springCacheAnno" /><!-- 使用context命名空間,加載數據庫的properties文件 --><context:property-placeholder location="classpath:spring/jdbc.properties" /><!-- 數據庫 --><bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close" p:driverClassName="${jdbc.driverClassName}"p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}" /><!-- 配置namedParameterJdbcTemplate模板 --><bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"><constructor-arg ref="dataSource"/></bean><!-- (1)添加cache命名空間和schema文件 --> <!-- (2)開啟支持緩存的配置項 --><cache:annotation-driven cache-manager="cacheManager" proxy-target-class="true"/><!-- (3) 配置cacheManger --><bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager"p:caches-ref="cacheObjects"></bean><!-- (4)caches集合 --><util:set id="cacheObjects"><bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"p:name="default"/><!-- @Cacheable(cacheNames = "littleArtisan")標注的cache名稱 --><bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"p:name="littleArtisan"/></util:set></beans>單元測試
package com.xgj.cache.springCacheAnno.cacheable;import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext;import com.xgj.cache.springCacheAnno.Artisan;public class CacheableTest {ClassPathXmlApplicationContext ctx = null;ArtisanSpringCacheService service = null;@Beforepublic void initContext() {// 啟動Spring 容器ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/cache/springCacheAnno/conf_spring.xml");service = ctx.getBean("artisanSpringCacheService",ArtisanSpringCacheService.class);System.out.println("initContext successfully");}@Testpublic void testLoadArtisanFromDBAndCache() {Artisan littleArtisan = new Artisan();// 第一次 從數據庫中獲取littleArtisan = service.getArtisan("littleArtisan");printArtisanInfo(littleArtisan);// @Cacheable(cacheNames = "littleArtisan", condition =// "#artisanName == 'masterArtisan'")// 根據condition 來看,我們只是緩存#artisanName == 'masterArtisan'的記錄// 第一次 從數據庫中獲取littleArtisan = service.getArtisan("masterArtisan");printArtisanInfo(littleArtisan);// 第二次 查詢masterArtisan ,根據緩存條件,滿足,應該從緩存中獲取littleArtisan = service.getArtisan("masterArtisan");printArtisanInfo(littleArtisan);// 第二次 查詢littleArtisan ,根據緩存條件,不滿足,應該從數據庫中獲取littleArtisan = service.getArtisan("littleArtisan");printArtisanInfo(littleArtisan);}private void printArtisanInfo(Artisan littleArtisan) {System.out.println("Id:" + littleArtisan.getArtisanId());System.out.println("Name:" + littleArtisan.getArtisanName());System.out.println("Desc:" + littleArtisan.getArtisanDesc());System.out.println();}@Afterpublic void closeContext() {if (ctx != null) {ctx.close();}System.out.println("close context successfully");}}輸出結構分析
2017-10-03 01:09:30,338 INFO [main] (AbstractApplicationContext.java:583) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@5f0ab5d: startup date [Tue Oct 03 01:09:30 BOT 2017]; root of context hierarchy 2017-10-03 01:09:30,422 INFO [main] (XmlBeanDefinitionReader.java:317) - Loading XML bean definitions from class path resource [com/xgj/cache/springCacheAnno/conf_spring.xml] initContext successfully 根據ArtisanName查找Artisan:littleArtisan getFromDB Id:AAAYbEAAZAAAK9fAAB Name:littleArtisan Desc:EhCache根據ArtisanName查找Artisan:masterArtisan getFromDB Id:AAAYbEAAZAAAK9fAAA Name:masterArtisan Desc:Spring CacheId:AAAYbEAAZAAAK9fAAA Name:masterArtisan Desc:Spring Cache根據ArtisanName查找Artisan:littleArtisan getFromDB Id:AAAYbEAAZAAAK9fAAB Name:littleArtisan Desc:EhCache2017-10-03 01:09:32,826 INFO [main] (AbstractApplicationContext.java:984) - Closing org.springframework.context.support.ClassPathXmlApplicationContext@5f0ab5d: startup date [Tue Oct 03 01:09:30 BOT 2017]; root of context hierarchy close context successfully可以看到,我們每次查詢littleArtisan都是從數據庫中獲取,而masterArtisan只有第一次是從數據庫中獲取,第二次查詢則是從緩存中獲取數據。 反之,推理 unless ,滿足條件則不進行緩存。
unless : unless ,滿足條件則不進行緩存。 請注意,該條件適用于方法的返回值。#result表示方法返回值。
@Cacheable(cacheNames="products", key="#product.name", condition="#product.price<500", unless="#result.outofstock") public Product findProduct(Product product){ .. return aproduct; }@CachePut 主要針對方法配置,能夠根據方法的請求參數對其結果進行緩存,和 @Cacheable 不同的是,它每次都會觸發真實方法的調用
@CachePut 和@Cacheable的效果幾乎一樣。@CachePut首先執行方法,然后將返回值放入緩存。當希望使用方法返回值來更新緩存時可以選擇這種方法
如果使用了 @Cacheable 注釋,則當重復使用相同參數調用方法的時候,方法本身不會被調用執行,即方法本身被略過了,結果直接從緩存中找到并返回了。
現實中并不總是如此,有些情況下我們希望方法一定會被調用,因為其除了返回一個結果,還做了其他事情,例如記錄日志,調用接口等,這個時候,我們可以用 @CachePut 注釋,這個注釋可以確保方法被執行,同時方法的返回值也被記錄到緩存中。
@CachePut 注解參數說明
和@Cacheable一樣,@CachePuts也提供了key、condition、和unless屬性
| value/cacheNames | 緩存的名稱,在 spring 配置文件中定義,必須指定至少一個 | @CachePut(cacheNames=”mycache”) 或者@CachePut(cacheNames={”cache1”,”cache2”} |
| key | 緩存的 key,可以為空,如果指定要按照 SpEL 表達式編寫,如果不指定,則缺省按照方法的所有參數進行組合 | @CachePut(cacheNames=”artisanCache”,key=”#artisanName”) |
| condition | 緩存的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才進行緩存,unless屬性和condition屬性相反,滿足條件則不進行緩存 | @CachePut(cacheNames=”artisanCache”,condition=”#artisanName.length()>2”) |
舉個場景:更新產品的操作,我們希望重新計算特定產品[可能是由于新的價格],然后將該產品存儲在緩存中以供將來參考。 請注意,雖然@CacheEvict用于從緩存中刪除項目[或全部],但@CachePut則是更新項目。
@CachePut(cacheNames= "products", key = "#product.name" , unless="#result==null") public Product updateProduct(Product product) {logger.info("<!----------Entering updateProduct ------------------->");for(Product p : products){if(p.getName().equalsIgnoreCase(product.getName()))p.setPrice(product.getPrice());return p;}return null; }<font color=red上述方法將在每次調用時執行,如果結果不為空,則將被存儲在緩存中。
示例
僅修改 將@Cacheable改為 @CachePut (這個示例不是特別合適, @CachePut比較適合更新的場景,僅演示每次都會執行的場景。)
@CachePut(cacheNames = "littleArtisan", condition = "#artisanName == 'masterArtisan'")public Artisan getArtisan(String artisanName) {// 方法內部實現不考慮緩存邏輯,直接實現業務System.out.println("根據ArtisanName查找Artisan:" + artisanName);return getFromDB(artisanName);}再次測試:
2017-10-03 01:40:26,019 INFO [main] (AbstractApplicationContext.java:583) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7b8269b6: startup date [Tue Oct 03 01:40:26 BOT 2017]; root of context hierarchy 2017-10-03 01:40:26,139 INFO [main] (XmlBeanDefinitionReader.java:317) - Loading XML bean definitions from class path resource [com/xgj/cache/springCacheAnno/conf_spring.xml] initContext successfully 根據ArtisanName查找Artisan:littleArtisan getFromDB Id:AAAYbEAAZAAAK9fAAB Name:littleArtisan Desc:EhCache根據ArtisanName查找Artisan:masterArtisan getFromDB Id:AAAYbEAAZAAAK9fAAA Name:masterArtisan Desc:Spring Cache根據ArtisanName查找Artisan:masterArtisan getFromDB Id:AAAYbEAAZAAAK9fAAA Name:masterArtisan Desc:Spring Cache根據ArtisanName查找Artisan:littleArtisan getFromDB Id:AAAYbEAAZAAAK9fAAB Name:littleArtisan Desc:EhCache2017-10-03 01:40:28,588 INFO [main] (AbstractApplicationContext.java:984) - Closing org.springframework.context.support.ClassPathXmlApplicationContext@7b8269b6: startup date [Tue Oct 03 01:40:26 BOT 2017]; root of context hierarchy close context successfully可以看到使用@CachePut后,每次都從是先執方法內的邏輯。
@CacheEvict 主要針對方法配置,能夠根據一定的條件對緩存進行清空
@CacheEvict注解是@Cacheable注解的反向操作,它負責從給定的緩存中移除一個值 。
大多數緩存框架都提供了緩存數據的有效期,使用該注解可以顯式的從緩存中刪除失效的緩存數據。 該注解通常用于更新或者刪除的操作。
@CacheEvict注解參數說明
| value/cacheNames | 緩存的名稱,在 spring 配置文件中定義,必須指定至少一個 | @CacheEvict(cacheNames=”mycache”) 或者@CacheEvict(cacheNames={”cache1”,”cache2”} |
| key | 緩存的 key,可以為空,如果指定要按照 SpEL 表達式編寫,如果不指定,則缺省按照方法的所有參數進行組合 | @CachEvict(cacheNames=”artisanCache”,key=”#artisanName”) |
| condition | 緩存的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才進行緩存,unless屬性和condition屬性相反,滿足條件則不進行緩存 | @CachEvict(cacheNames=”artisanCache”,condition=”#artisanName.length()>2”) |
| allEntries | 是否清空所有緩存內容,缺省為 false,如果指定為 true,則方法調用后將立即清空所有緩存 | @CachEvict(cacheNames=”artisan”,allEntries=true)”) |
| beforeInvocation | 是否在方法執行前就清空,缺省為 false,如果指定為 true,則在方法還沒有執行的時候就清空緩存,缺省情況下,如果方法執行拋出異常,則不會清空緩存 | @CachEvict(cacheNames=”artisan”,beforeInvocation=true)”) |
示例
@CacheEvict(cacheNames = "products", key = "#product.name") public void refreshProduct(Product product) {//This method will remove only this specific product from 'products' cache. } @CacheEvict(cacheNames= "products", allEntries = true) public void refreshAllProducts() {//This method will remove all 'products' from cache, say as a result of flush-all API. } package com.xgj.cache.springCacheAnno.cacheEvict;import java.sql.ResultSet; import java.sql.SQLException;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.jdbc.core.RowCallbackHandler; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Service;import com.xgj.cache.springCacheAnno.Artisan;/*** * * @ClassName: ArtisanSpringCacheService* * @Description: @Service標注的服務層,受Spring管理* * @author: Mr.Yang* * @date: 2017年10月2日 下午5:34:29*/@Service public class ArtisanSpringCacheEvictService {// 模糊查詢 在參數的值里設置(%),查詢sql語句就只是個命名參數private static final String selectArtisanSQL = "select artisan_id ,artisan_name ,artisan_desc "+ " from little_artisan "+ " where artisan_name like :artisanName ";private NamedParameterJdbcTemplate jdbcTemplate;@Autowiredpublic void setJdbcTemplate(NamedParameterJdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}/*** * * @Title: getArtisan* * @Description: @Cacheable(cacheNames = "littleArtisan")* 使用名為littleArtisan的緩存,符合#artisanName == 'masterArtisan'才緩存* * * @return* * @return: LittleArtisan*/@Cacheable(cacheNames = "littleArtisan", condition = "#artisanName == 'masterArtisan'")public Artisan getArtisan(String artisanName) {// 方法內部實現不考慮緩存邏輯,直接實現業務System.out.println("根據ArtisanName查找Artisan:" + artisanName);return getFromDB(artisanName);}/*** * * @Title: remove* * @Description: 清除緩存* * @param artisnName* * @return: void*/@CacheEvict(cacheNames = "littleArtisan")public void remove(String artisnName) {System.out.println("littleArtisan cache removed ");}/*** * * @Title: getFromDB* * @Description: 這里只是為了演示下* 使用NamedParameterJdbcTemplate模糊查詢的用法。其實有可能返回的是一個List。* * @param artisanName* @return* * @return: Artisan*/private Artisan getFromDB(String artisanName) {System.out.println("getFromDB");final Artisan artisan = new Artisan();// 使用MapSqlParameterSource綁定參數 ,拼接模糊查詢MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource().addValue("artisanName", "%" + artisanName + "%");jdbcTemplate.query(selectArtisanSQL, mapSqlParameterSource,new RowCallbackHandler() {@Overridepublic void processRow(ResultSet rs) throws SQLException {artisan.setArtisanId(rs.getString("artisan_id"));artisan.setArtisanName(rs.getString("artisan_name"));artisan.setArtisanDesc(rs.getString("artisan_desc"));}});return artisan;}}單元測試
package com.xgj.cache.springCacheAnno.cacheEvict;import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext;import com.xgj.cache.springCacheAnno.Artisan;public class CacheEvictTest {ClassPathXmlApplicationContext ctx = null;ArtisanSpringCacheEvictService service = null;@Beforepublic void initContext() {// 啟動Spring 容器ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/cache/springCacheAnno/conf_spring.xml");service = ctx.getBean("artisanSpringCacheEvictService",ArtisanSpringCacheEvictService.class);System.out.println("initContext successfully");}@Testpublic void testLoadArtisanFromDBAndCache() {Artisan littleArtisan = new Artisan();// @Cacheable(cacheNames = "littleArtisan", condition =// "#artisanName == 'masterArtisan'")// 根據condition 來看,我們只是緩存#artisanName == 'masterArtisan'的記錄// 第一次 從數據庫中獲取littleArtisan = service.getArtisan("masterArtisan");printArtisanInfo(littleArtisan);// 第二次 查詢masterArtisan ,根據緩存條件,滿足,應該從緩存中獲取littleArtisan = service.getArtisan("masterArtisan");printArtisanInfo(littleArtisan);// 將littleArtisan從緩存中移除,再次查詢masterArtisanservice.remove("masterArtisan");littleArtisan = service.getArtisan("masterArtisan");printArtisanInfo(littleArtisan);// 再次 查詢masterArtisan,根據緩存條件,滿足,應該從緩存中獲取littleArtisan = service.getArtisan("masterArtisan");printArtisanInfo(littleArtisan);}private void printArtisanInfo(Artisan littleArtisan) {System.out.println("Id:" + littleArtisan.getArtisanId());System.out.println("Name:" + littleArtisan.getArtisanName());System.out.println("Desc:" + littleArtisan.getArtisanDesc());System.out.println();}@Afterpublic void closeContext() {if (ctx != null) {ctx.close();}System.out.println("close context successfully");}}測試結果
2017-10-03 02:11:54,223 INFO [main] (AbstractApplicationContext.java:583) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7efeedca: startup date [Tue Oct 03 02:11:54 BOT 2017]; root of context hierarchy 2017-10-03 02:11:54,378 INFO [main] (XmlBeanDefinitionReader.java:317) - Loading XML bean definitions from class path resource [com/xgj/cache/springCacheAnno/conf_spring.xml] initContext successfully 根據ArtisanName查找Artisan:masterArtisan getFromDB Id:AAAYbEAAZAAAK9fAAA Name:masterArtisan Desc:Spring CacheId:AAAYbEAAZAAAK9fAAA Name:masterArtisan Desc:Spring CachelittleArtisan cache removed 根據ArtisanName查找Artisan:masterArtisan getFromDB Id:AAAYbEAAZAAAK9fAAA Name:masterArtisan Desc:Spring CacheId:AAAYbEAAZAAAK9fAAA Name:masterArtisan Desc:Spring Cache2017-10-03 02:11:58,732 INFO [main] (AbstractApplicationContext.java:984) - Closing org.springframework.context.support.ClassPathXmlApplicationContext@7efeedca: startup date [Tue Oct 03 02:11:54 BOT 2017]; root of context hierarchy close context successfully清除后,又重新加載數據,并存入緩存。
@Caching 主要針對方法配置, 是一個組注解
@Caching 是一個組注解,可以為一個方法定義提供基于@Cacheable、@CacheEvict或者@CachePut注解的數組。
當我們想要指定相同類型的多個注釋(例如同一方法的@CacheEvict或@CachePut)時,@Caching注釋很方便。
假設我們有兩個包含相同產品的緩存,使用相同的key。 現在,如果要從兩個緩存中清除記錄,如下即可
@CacheEvict(cachenames = {"products", "items"}, key = "#product.name") public void refreshProduct(Product product) {//This method will remove only this specific product from 'products' & 'items' cache. }如果key不同呢, JDK1.8以下不允許使用重復的注解在一個方法上,
這個時候使用@Caching
@Caching(evict = {@CacheEvict(cacheNames = "products", key = "#product.name"),@CacheEvict(cacheNames = "items", key = "#product.id") })public void refreshProduct2(Product product) {// This method will remove only this specific product from 'products' &// 'items' cache.}@CacheConfig 類級別的全局緩存注解
Spring4.0之前并沒有類級別的全局緩存注解。 前面的4個注解都是基于方法的,如果在同一個類中需要緩存的方法注解屬性都類似,則需要一個個的重復增加,Spring4增加了@CacheConfig類級別的注解解決這個問題。
比如
package com.xgj.cache.springCacheAnno.cacheConfig;import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.Cacheable;@CacheConfig(cacheNames = "artisans", keyGenerator="myKeyGenerator") public class ArtisanService {@Cacheablepublic Artisan getArtisanA(String artisanName) {Artisan artisan = new Artisan();return artisan;}@Cacheable(cacheNames="artisanB")public Artisan getArtisanB(String artisanName) {Artisan artisan = new Artisan();return artisan;} }在上面的例子中,getArtisanA將使用“artisans”緩存,而getArtisanB覆蓋類級別的緩存名,使用指定的“artisanB”換承諾。 另外,他們都將使用在class級別指定的keyGenerator。
完整示例
Spring Cache抽象-使用Java類注解的方式整合EhCache
總結
以上是生活随笔為你收集整理的Spring Cache抽象-缓存注解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Cache-缓存概述及使用
- 下一篇: Spring Cache抽象-使用Jav