Jpetstore是一个典型的web应用,其开发框架为struts(spring-web)+spring+ibatis,因此以它做为例子有很好的实际意义。
本篇的前提是memcached server已经安装并且启动,我们在此只是看看如何使用其java client进行数据的读取和更新,这里所用的client都是比较原始的,没有进行封装。
1.1从whalin下载jar包,然后安装到nexus中,在jpetstore的pom.xml中增加如下依赖:
<dependency> <groupId>com.danga</groupId> <artifactId>memcached</artifactId> <version>2.0.1</version> <type>jar</type> </dependency>
1.2配置
在dataAccessContext-local.xml中增加如下配置
<bean id="memcachedPool" class="com.danga.MemCached.SockIOPool" factory-method="getInstance" init-method="initialize" destroy-method="shutDown"> <constructor-arg><value>eddyMemcachedPool</value></constructor-arg> <property name="servers"> <list> <value>127.0.0.1:11211</value> </list> </property> <property name="initConn"><value>20</value></property> <property name="minConn"><value>10</value></property> <property name="maxConn"><value>50</value></property> <property name="maintSleep"><value>30</value></property> <property name="nagle"><value>false</value></property> <property name="socketTO"><value>3000</value></property> </bean> <bean id="memcachedClient" class="com.danga.MemCached.MemCachedClient"> <constructor-arg><value>eddyMemcachedPool</value></constructor-arg> <property name="compressEnable"><value>true</value></property> <property name="compressThreshold"><value>4096</value></property> </bean>
1.3在dao的实现中增加memcached支持,譬如SqlMapProductDao中作如下改动
增加注入
private MemCachedClient mmc; public MemCachedClient getMmc() { return mmc; } public void setMmc(MemCachedClient mmc) { this.mmc = mmc; }
修改实现代码
public Product getProduct(String productId) throws DataAccessException { String key = "Product-"+productId; Product p = (Product)mmc.get(key); if(p==null){ p = (Product) getSqlMapClientTemplate().queryForObject("getProduct", productId); mmc.set(key, p); System.out.println("ddddddddddddddddddddddddddddddddddddddddddddd"); }else{ System.out.println("mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm"); } return p; }
1.4修改spring配置
<bean id="productDao" class="org.springframework.samples.jpetstore.dao.ibatis.SqlMapProductDao"> <property name="sqlMapClient" ref="sqlMapClient"/> <property name="mmc" ref="memcachedClient"/> </bean>
修改完毕,第一次访问,可以看到后台打印出
ddddddddddddddddddddddddddddddddddddddddddddd
后面的访问都打印出
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
2。spymemcached http://code.google.com/p/spymemcached
参考simple-spring-memcachedhttp://code.google.com/p/simple-spring-memcached/
2.1为项目增加spymemcached依赖,修改pom.xml,增加:
<dependency> <groupId>spy</groupId> <artifactId>memcached</artifactId> <version>2.3.1</version> </dependency>
2.2添加memcached client factory和memcached helper bean
package org.springframework.samples.jpetstore.util; public class MemcachedConnectionBean { private String nodeList; private boolean consistentHashing; public String getNodeList() { return nodeList; } public void setNodeList(final String nodeList) { this.nodeList = nodeList; } public boolean isConsistentHashing() { return consistentHashing; } public void setConsistentHashing(final boolean consistentHashing) { this.consistentHashing = consistentHashing; } }
package org.springframework.samples.jpetstore.util; import java.io.IOException; import java.net.InetSocketAddress; import java.util.List; import net.spy.memcached.AddrUtil; import net.spy.memcached.ConnectionFactory; import net.spy.memcached.DefaultConnectionFactory; import net.spy.memcached.KetamaConnectionFactory; import net.spy.memcached.MemcachedClient; import net.spy.memcached.MemcachedClientIF; public class MemcachedClientFactory { private MemcachedConnectionBean bean; public void setBean(MemcachedConnectionBean bean) { this.bean = bean; } public MemcachedClientIF createMemcachedClient() throws IOException{ if (this.bean == null) { throw new RuntimeException("The MemcachedConnectionBean must be defined!"); } final List<InetSocketAddress> addrs = AddrUtil.getAddresses(this.bean.getNodeList()); final ConnectionFactory connectionFactory = this.bean.isConsistentHashing() ? new KetamaConnectionFactory() : new DefaultConnectionFactory(); final MemcachedClientIF client = new MemcachedClient(connectionFactory, addrs); return client; } }
2.3 修改dataAccessContext-local.xml,增加如下
<bean id="memcachedConnectionBean" class="org.springframework.samples.jpetstore.util.MemcachedConnectionBean"> <property name="consistentHashing" value="true" /> <property name="nodeList" value="127.0.0.1:11211 127.0.0.1:11311" /> </bean> <bean id="memcachedClientFactory" class="org.springframework.samples.jpetstore.util.MemcachedClientFactory" > <property name="bean" ref="memcachedConnectionBean" /> </bean> <bean id="memcachedSpyClient" factory-bean="memcachedClientFactory" factory-method="createMemcachedClient" />
同时修改其中的
<bean id="productDao" class="org.springframework.samples.jpetstore.dao.ibatis.SqlMapProductDao"> <property name="sqlMapClient" ref="sqlMapClient"/> <property name="mmc" ref="memcachedClient"/> </bean>
将mmc换成memcachedSpyClient
2.4修改SqlMapProductDao,将spymemcached client注入
private MemcachedClientIF mmc; public MemcachedClientIF getMmc() { return mmc; } public void setMmc(MemcachedClientIF mmc) { this.mmc = mmc; }
2.5修改客户端调用方式
public Product getProduct(String productId) throws DataAccessException {
String key = "Product-"+productId;
Product p = (Product)mmc.get(key);
if(p==null){
p = (Product) getSqlMapClientTemplate().queryForObject("getProduct", productId);
mmc.set(key, 3600, p);
System.out.println("ddddddddddddddddddddddddddddddddddddddddddddd");
}else{
System.out.println("mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm");
}
return p;
}
修改完毕,其实从这个改动来看,主要是获取memcached client有所区别。
总结:
从上面的两个例子可以看出,两种缓存的方式都对代码进行了侵入,所以在更换memcached client时需要修改不少的代码。因此要让缓存客户端对我们的代码透明,就需要做自己的cache接口和cache client以及其factory。
这两种客户端对于使用各自的set/get没有问题,对于对象为string的互相set/get也没有问题,但是对于复杂object的互相set/get是不行的。譬如,对于对象Product,如下:
import java.io.Serializable; public class Product implements Serializable { /* Private Fields */ private String productId; private String categoryId; private String name; private String description; /* JavaBeans Properties */ public String getProductId() { return productId; } public void setProductId(String productId) { this.productId = productId.trim(); } public String getCategoryId() { return categoryId; } public void setCategoryId(String categoryId) { this.categoryId = categoryId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } /* Public Methods*/ public String toString() { return getName(); } }
使用第一个客户端set,
Product obj = new Product(); obj.setName("test"); obj.setProductId("1-2-3"); mcc.set("P-1-2-3", obj);
使用第二个客户端get,
Product bar = (Product)mcc.get( "P-1-2-3" );
就会产生如下异常:
Exception in thread "main" java.lang.ClassCastException: java.lang.Byte at TestMemcached.objexamples(TestMemcached.java:95) at TestMemcached.main(TestMemcached.java:112)
从异常来看是类型转换错误,分别打印出
System.out.println("mcc|"+mcc.get( "P-1-2-3" )+"|");
和
System.out.println("spy|"+getSpy().get( "P-1-2-3" )+"|");
结果是
mcc|Product@dd5b|
和
spy|?? Product??w categoryIdtxx Ljava/lang/String;Lxx descriptionqxx nameq productIdqxx xppptxx ========name========txx 1-2-3|
xx代表一些特殊字符。
很明显,这两个client不兼容,至今没找到解决方案。(?????)