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