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
- An improved Java API maintained by Dustin Sallings.
- Aggressively optimised, ability to run async, supports binary protocol, etc. See site for details.
参考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不兼容,至今没找到解决方案。(?????)