參考文檔:
http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/
需求場景:
當前業務通過第三方接口查詢一個業務數據,該數據更新頻率略低(約2小時),但前端查詢的頻率不可控。所以,需要實現一個帶有數據緩存功能的查詢接口。
設計方案:
實時數據由第三方接口獲取,ehcache作為一級緩存(數據有效時間60秒),mysql作為二級緩存和數據持久層(數據有效時間2小時)。查詢優先級:ehcache>mysql>第三方。其中mysql的數據過期與更新需要自行實現管理。
常規的增刪改查在此不表,主要說下之前很少用到的blob對象和@Cacheable。從第三方獲取到的對象由於字段較多且結構復雜,所以不想在數據庫建表然后再做VO-TABLE的映射。選擇偷懶的方式使用blob直接存儲整個對象。
mysql中blob字段對應java中byte[],所以對象在存取的時候要手動序列化(反序列化)的過程,這個借用ObjectInputStream、ByteArrayInputStream這些對象即可完成。
VO對象:
public class BusinessInfoVo { private int id; private String nu; private byte[] data; private Date updatetime; //... }
表結構:
CREATE TABLE `shop_expressinfo` ( `id` int(11) NOT NULL AUTO_INCREMENT, `nu` varchar(255) DEFAULT NULL, `data` blob, `updatetime` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4;
mapper.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.lichmama.demo.dao.mapper.ExpressMapper"> <resultMap type="BusinessInfoVo" id="businessInfoMap"> <result property="id" column="id" /> <result property="nu" column="nu" /> <result property="data" column="data" jdbcType="BLOB" /> <result property="updatetime" column="updatetime" /> </resultMap> <select id="select" resultMap="businessInfoMap" parameterType="BusinessInfoVo"> select * from shop_expressinfo where nu = #{nu} </select> <insert id="insert" parameterType="BusinessInfoVo"> insert into shop_expressinfo(nu, data, updatetime) values(#{nu}, #{data, jdbcType=BLOB}, now()) </insert> <update id="update" parameterType="BusinessInfoVo"> update shop_expressinfo set data = #{data, jdbcType=BLOB}, updatetime = now() where nu = #{nu} </update> <delete id="delete" parameterType="java.lang.String"> delete from shop_expressinfo where nu = #{nu} </delete> </mapper>
對象序列化過程:
/** * 將對象轉化為字節數組 * @author lichmama * @param object * @return * @throws IOException */ private byte[] objectToBytes(Object object) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(object); byte[] bytes = baos.toByteArray(); baos.close(); oos.close(); return bytes; } /** * 將字節數組轉化為對象 * @author lichmama * @param bytes * @return * @throws IOException * @throws ClassNotFoundException */ @SuppressWarnings("unchecked") private <T> T bytesToObject(byte[] bytes) throws IOException, ClassNotFoundException { ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais); Object object = ois.readObject(); bais.close(); ois.close(); return (T) object; }
在業務方法加@Cacheable注解,用於緩存數據到內存中,加速查詢。這里我使用ehcache做緩存容器,下面貼出配置。
ehcache.xml:
<?xml version="1.0" encoding="UTF-8"?> <ehcache updateCheck="false" dynamicConfig="false"> <!-- * maxElementsInMemory - 內存中最大緩存對象數 * eternal - 緩存元素是否永久有效,若配置為true,則其他的緩存生命周期timeout設置均無效 * timeToIdleSeconds - 設置Element在失效前的允許閑置時間。僅當element不是永久有效時使用,可選屬性,默認值是0,也就是可閑置時間無窮大。 * timeToLiveSeconds - 設置Element在失效前允許存活時間。最大時間介於創建時間和失效時間之間。僅當element不是永久有效時使用,默認是0.,也就是element存活時間無窮大。 * overflowToDisk - 配置此屬性,當內存中Element數量達到maxElementsInMemory時,Ehcache將會Element寫到磁盤中。 * maxElementsOnDisk - 磁盤中最大緩存對象數,若是0表示無窮大。 * diskExpiryThreadIntervalSeconds - 磁盤失效線程運行時間間隔,默認是120秒。 * memoryStoreEvictionPolicy - 當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內存。默認策略是LRU(最近最少使用)。你可以設置為FIFO(先進先出)或是LFU(較少使用)。 --> <!-- 默認配置 --> <defaultCache maxElementsInMemory="5000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" memoryStoreEvictionPolicy="LRU" overflowToDisk="false" /> <cache name="businessInfoCache" maxElementsInMemory="10000" maxElementsOnDisk="100000" eternal="false" overflowToDisk="false" timeToIdleSeconds="60" timeToLiveSeconds="60" memoryStoreEvictionPolicy="LRU" /> </ehcache>
關於timeToIdleSeconds和timeToLiveSeconds的解釋參看:http://blog.csdn.net/vtopqx/article/details/8522333
對應的spring配置:
<!-- ehcache --> <cache:annotation-driven cache-manager="ehcacheManager"/> <!-- cacheManager工廠類,指定ehcache.xml的位置 --> <bean id="ehcacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> <property name="configLocation" value="/WEB-INF/conf/ehcache.xml" /> </bean> <!-- 聲明cacheManager --> <bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"> <property name="cacheManager" ref="ehcacheManagerFactory" /> </bean>
在業務方法中使用如下(在首次查詢后,spring會緩存數據並保持60秒):
/** * 獲取訂單的物流跟蹤信息 * @author lichmama * @param nu * @return */ @Cacheable(value="businessInfoCache", key="#root.methodName" + "." + "#nu") public ExpressInfo queryBusinessInfo(String nu) { //... }
*關於@Cacheable中key的說明:
key屬性是用來指定Spring緩存方法的返回結果時對應的key的。該屬性支持SpringEL表達式。當我們沒有指定該屬性時,Spring將使用默認策略生成key。我們這里先來看看自定義策略,至於默認策略會在后文單獨介紹。
自定義策略是指我們可以通過Spring的EL表達式來指定我們的key。這里的EL表達式可以使用方法參數及它們對應的屬性。使用方法參數時我們可以直接使用“#參數名”或者“#p參數index”。除了上述使用方法參數作為key之外,Spring還為我們提供了一個root對象可以用來生成key。通過該root對象我們可以獲取到以下信息。
參考文檔:
http://blog.csdn.net/fireofjava/article/details/48913335 http://blog.csdn.net/wjacketcn/article/details/50945887 http://docs.spring.io/spring/docs/3.2.18.RELEASE/spring-framework-reference/htmlsingle/#cache-annotations-cacheable