Memcached 是一個高性能的分布式內存對象緩存系統,在web應用中有很強大的應用場景。而spring作為一個流行的開源框架,在針對數據緩存中,也有着不少的需求。在眾多的第三方實現中,google的simple-spring-memcached是一種非常好的方式。下面就用一個簡單的訂單系統,描述了在spring-mvc中使用memcached的集成方法。
場景描述:a、用戶在購買某件商品時,先進行商品的選擇,系統生成商品的訂單。訂單內容包括:商品名稱,商品id,訂單id,商品原金額。
b、用戶進行訂單確定,可能會有一定的折扣(例如使用京東的優惠券等),此時需要更新訂單內容,訂單內容需要更新商品的金額。
c、用戶需要選擇支付方式,此時需要更新訂單的支付渠道,同時還可能進行金額更新。(例如通過某寶搞活動等,呵呵)
d、用戶完成支付。
一旦做完了d,訂單數據將進入交易數據庫表中。
如果未做完d,每一步在30分鍾之后,該筆訂單就失效,並在memcached中刪除訂單。
基於這個特性,abc狀態的交易信息其實是交易信息的緩存,后面的增刪改查如果每次都需要去進行數據庫操作,是比較浪費的,因此考慮使用memcached進行訂單狀態的管理。
好了,需求大致落實好了,開干!
首先是在pom.xml完成與memcached相關的配置。(spring本身的pom配置就不在這里說了)
<!-- simple spring memcached --> <dependency> <groupId>com.google.code.simple-spring-memcached</groupId> <artifactId>spring-cache</artifactId> <version>3.6.0</version> </dependency> <dependency> <groupId>com.google.code.simple-spring-memcached</groupId> <artifactId>xmemcached-provider</artifactId> <version>3.6.0</version> </dependency>
然后我們需要進行simple-spring-memcached的配置。
首先我們需要創建一個spring-memcached的基礎配置文件,主要用於聲明與spring相關的memcached配置。(例如命名空間等)
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <import resource="simplesm-context.xml" /> <aop:aspectj-autoproxy /> <context:annotation-config /> <import resource="xmemcached.xml"/> </beans>
這里我們可以看到,我們使用了一個xmemcached.xml的配置文件對memcached具體的內容進行配置。xmemcached.xml內容如下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean name="defaultMemcachedClient" class="com.google.code.ssm.CacheFactory"> <!-- xmemcached配置方法 --> <property name="cacheClientFactory"> <bean class="com.google.code.ssm.providers.xmemcached.MemcacheClientFactoryImpl" /> </property> <!-- 定義了緩存節點的IP地址和端口號 --> <property name="addressProvider"> <bean class="com.google.code.ssm.config.DefaultAddressProvider"> <property name="address" value="localhost:11211" /> </bean> </property> <!-- 定義了緩存節點的查找方法 --> <property name="configuration"> <bean class="com.google.code.ssm.providers.CacheConfiguration"> <property name="consistentHashing" value="true" /> </bean> </property> </bean> </beans>
到此步為止,我們的與memcached配置相關的xml已經完了,接下來開始編寫我們的代碼了。
首先創建一個類,用於緩存我們的訂單信息,由於在memcached中是按照key-value形式進行數據存儲的,因此我們需要指定一個key,這個key可以用注解來實現。
public class OrderInfo implements Serializable{ /** * */ private static final long serialVersionUID = -608764679068384346L; private String orderId; private String productId; private String productName; private int productPrice; private int orderPrice; private String orderTime; private int paymentMethod; private int orderStatus; private String payTime; @CacheKeyMethod public String getOrderId() { return orderId; } ...//get set method
}
這里有兩個注意點,首先,我們這個作為緩存信息的類,需要實現Serializable接口,否在在memcached中是無法緩存對象的。其次我要要指定一個索引key,我們這里使用了orderId作為可以的索引。
然后是我們的dao層。
public interface OrderInfoDao { public OrderInfo getOrderInfo(String orderId); public void orderInit(OrderInfo orderInfo); public void orderChecks(OrderInfo orderInfo); public void orderPayChecks(OrderInfo orderInfo); public void orderSuccess(String orderId); }
dao層相對來說比較簡單,就不展開了。
最后是我們的關鍵了,dao層的實現,代碼如下:
@Repository("orderInfoDao") @ImportResource("classpath:spring_memcached.xml") public class OrderInfoDaoImpl implements OrderInfoDao{ private static final String ORDER_INFO_SSM = "orderInfoSSM"; @ReadThroughSingleCache(namespace = ORDER_INFO_SSM, expiration = 1800) public OrderInfo getOrderInfo(@ParameterValueKeyProvider String orderId) { System.out.println("no cached hitd from ssm"); return null; } @UpdateSingleCache(namespace = ORDER_INFO_SSM, expiration = 1800) public void orderInit( @ParameterValueKeyProvider @ParameterDataUpdateContent OrderInfo orderInfo) { System.out.println("orderInit cached to ssm"); System.out.println(orderInfo.toString()); } @UpdateSingleCache(namespace = ORDER_INFO_SSM, expiration = 1800) public void orderChecks(@ParameterValueKeyProvider @ParameterDataUpdateContent OrderInfo orderInfo) { System.out.println("orderChecks cached to ssm"); System.out.println(orderInfo.toString()); } @UpdateSingleCache(namespace = ORDER_INFO_SSM, expiration = 1800) public void orderPayChecks(@ParameterValueKeyProvider @ParameterDataUpdateContent OrderInfo orderInfo) { System.out.println("orderPayChecks cached to ssm"); System.out.println(orderInfo.toString()); } @InvalidateSingleCache(namespace = ORDER_INFO_SSM) public void orderSuccess(@ParameterValueKeyProvider String orderId) { System.out.println("orderSuccess delet cached from ssm"); System.out.println(orderId); //TODO 進行數據持久化處理 } }
首先我們要進行memcached命名空間的指定,通常來說,每個dao的實現都應在memcached中擁有自己的命名空間。
接下來簡單描述下相關的注解:
@ReadThroughSingleCache
1)、檢查緩存中是否存在,如果存在,返回緩存數據並退出。
2)、如果在緩存中沒有找到,將會繼續訪問下面的方法。
3)、如果方法返回了期望的對象,則memcached將會以注解ParameterValueKeyProvider修飾的對象作為key並把返回的對象記入緩存。
4)、如果方法沒有發揮期望的對象,則memcached將不會寫入該對象。
5)、expiration指定了失效時間,是以秒記的。
@UpdateSingleCache
1)、用於更新緩存,包括兩個概念,一是寫入新的緩存,二是更新現有緩存。
2)、該注解中@ParameterDataUpdateContent代表更新之后進行對象刷新。
3)、由於我們之前在對象中使用了@CacheKeyMethod 的注解,因此我們這里可以直接使用該類進行寫入,key就是對象中寫的orderId。
@InvalidateSingleCache
這個注解就比較簡單了,就是使緩存失效。
好了,大功告成,我們編寫測試案例來試一下
@Test public void testSSM() throws InterruptedException { OrderInfo orderInfo = new OrderInfo(); // step1 下訂單,使用當前時間作為orderId String orderId = String.valueOf(System.currentTimeMillis()); orderInfo.setOrderId(orderId); orderInfo.setOrderTime("20151125115900"); orderInfo.setProductId("10000000"); orderInfo.setProductName("apple phone 6s"); orderInfo.setProductPrice(528800); orderInfoDao.orderInit(orderInfo); // step2 進行確定金額 // 先從緩存中獲取之前的數據,為了證明是從緩存中取出的,我們先初始化一下之前的對象 orderInfo = null; orderInfo = orderInfoDao.getOrderInfo(orderId); System.out.println("get cached afterstep 1 orderInfo:" + orderInfo); orderInfo.setOrderStatus(2); // 更新金額,現在進行活動東,因此優惠288元 orderInfo.setOrderPrice(500000); // 更新緩存 orderInfoDao.orderChecks(orderInfo); // step3 進行支付方式選擇 // 先從緩存中獲取之前的數據,為了證明是從緩存中取出的,我們先初始化一下之前的對象 orderInfo = null; orderInfo = orderInfoDao.getOrderInfo(orderId); System.out.println("get cached afterstep 2 orderInfo:" + orderInfo); orderInfo.setOrderStatus(3); // 確定支付方式 orderInfo.setPaymentMethod(1); // 支付方式繼續進行優惠 orderInfo.setPayPrice(480000); orderInfo.setPayTime("20151125120000"); // 更新緩存 orderInfoDao.orderPayChecks(orderInfo); // step4 進行支付 // 先從緩存中獲取之前的數據,為了證明是從緩存中取出的,我們先初始化一下之前的對象 orderInfo = null; orderInfo = orderInfoDao.getOrderInfo(orderId); System.out.println("get cached afterstep 3 orderInfo:" + orderInfo); // TODO PAY action orderInfoDao.orderSuccess(orderInfo.getOrderId()); // 確認緩存對象已經刪除 orderInfo = null; orderInfo = orderInfoDao.getOrderInfo(orderId); System.out.println("get cached afterstep 4 orderInfo:" + orderInfo); }
然后我們用maven-test觀察下輸出
orderInit cached to ssm OrderInfo [orderId=1448455664090, productId=10000000, productName=apple phone 6s, productPrice=528800, orderPrice=0, orderTime=20151125115900, paymentMethod=0, payPrice=0, orderStatus=0, payTime=null] get cached afterstep 1 orderInfo:OrderInfo [orderId=1448455664090, productId=10000000, productName=apple phone 6s, productPrice=528800, orderPrice=0, orderTime=20151125115900, paymentMethod=0, payPrice=0, orderStatus=0, payTime=null] orderChecks cached to ssm OrderInfo [orderId=1448455664090, productId=10000000, productName=apple phone 6s, productPrice=528800, orderPrice=500000, orderTime=20151125115900, paymentMethod=0, payPrice=0, orderStatus=2, payTime=null] get cached afterstep 2 orderInfo:OrderInfo [orderId=1448455664090, productId=10000000, productName=apple phone 6s, productPrice=528800, orderPrice=500000, orderTime=20151125115900, paymentMethod=0, payPrice=0, orderStatus=2, payTime=null] orderPayChecks cached to ssm OrderInfo [orderId=1448455664090, productId=10000000, productName=apple phone 6s, productPrice=528800, orderPrice=500000, orderTime=20151125115900, paymentMethod=1, payPrice=480000, orderStatus=3, payTime=20151125120000] get cached afterstep 3 orderInfo:OrderInfo [orderId=1448455664090, productId=10000000, productName=apple phone 6s, productPrice=528800, orderPrice=500000, orderTime=20151125115900, paymentMethod=1, payPrice=480000, orderStatus=3, payTime=20151125120000] orderSuccess delet cached from ssm 1448455664090 no cached hitd from ssm get cached afterstep 4 orderInfo:null
是不是很easy。
當然,實際上memcached通常回合數據庫聯合起來使用,例如在@ReadThroughSingleCache中使用訪問數據庫生成緩存,這里咱們的目的是memcached,就不再展開了,嘿嘿。
示例代碼下載:
http://download.csdn.net/detail/highkgao1988/9300325