对一个成规模的系统来说,缓存总是不可或缺的一个环节,甚至会成为系统成功的重要因素。
从原理来讲,缓存并不神秘,它本质上只是一个哈希表,内部包含许多提取关键字和缓存内容的键值对,当有读操作(如search)新的查询到来时,系统先到
这个哈希表中看看是否有同样的关键字存在,是则取出对应的值返回,否则进行查询,并把新的查询条件和结果存储进哈希表,以便下次提取;当有写操作(如
add,delete,update)来临时,原则上说现有缓存的内容都存在了不确定性,那么简单的处理就是清空现有缓存。
缓存器的位置可以放在具体要执行的CRUD方法之前,当然我个人是不提倡这种耦合子系统的做法,利用Java的动态代理机制,我们可以把数据库访问和缓存
两部分分离开来,而Spring提供的ProxyFactoryBean和Interceptor正好给我们提供了现成的便利,使我们不需要再重复的发明
车轮。这样做的最大好处是解耦子系统,因为耦合是导致系统瘫痪的重大因素,所以我们必须尽量避免,随时提防.
请看具体来说是怎么实现缓存的,下面是需要为之提供缓存服务的TmpServiceImpl类及其接口:
TmpServiceImpl类:
public class TmpServiceImpl extends BaseService implements IService{
/**
* 添加一个Tmp对象到数据库
* @param args
* @return
* @throws Exception
*/
public String add(String[] args) throws Exception{
String name=args[0];
int age=Integer.parseInt(args[1]);
float salary=Float.parseFloat(args[2]);
// 同名检测
if(hasSameName(name)){
throw new BreakException("已经有和"+name+"同名的对象存在了.");
}
Tmp tmp=new Tmp(name,age,salary);
dao.create(tmp);
return tmp.toXML();
}
/**
* 将TMP对象的信息组合成一个字符串返回
*
* 说明:要修改此方法请与何杨商议,请勿自行修改!
* @param args
* @return
* @throws Exception
*/
public String getInfoById(String[] args) throws Exception{
String id=args[0];
Tmp tmp=(Tmp)getObjById(id);
if(tmp==null){
throw new BreakException("找不到Id'"+id+"'对应的Tmp对象.");
}
StringBuilder sb=new StringBuilder();
sb.append("姓名:"+tmp.getName()+""r"n");
sb.append("年龄:"+tmp.getAge()+""r"n");
sb.append("薪水:"+tmp.getSalary()+""r"n");
sb.append("添加时间:"+tmp.getAddTime()+""r"n");
sb.append("更新时间:"+tmp.getRefreshTime()+""r"n");
return sb.toString();
}
/**
* 按ID取得一个Tmp对象
* @param args
* @return
* @throws Exception
*/
public String getById(String[] args) throws Exception{
String id=args[0];
Tmp tmp=(Tmp)getObjById(id);
if(tmp==null){
throw new BreakException("找不到Id'"+id+"'对应的Tmp对象.");
}
return tmp.toXML();
}
/**
* 按薪水来查询Tmp对象
* @param args
* @return
* @throws Exception
*/
public String searchBySalary(String[] args)throws Exception{
float salary=Float.parseFloat(args[0]);
StringBuilder sb = new StringBuilder();
sb.append(" from " + domainClass + " d where d.valid=true and ");
sb.append(" d.salary >='"+salary+"' " );
sb.append(" order by id asc ");
String hql=sb.toString();
return convertListToXml(dao.search(hql));
}
/**
* 按ID来更新一个Tmp对象
* @param args
* @return
* @throws Exception
*/
public String update(String[] args)throws Exception{
String id=args[0];
String name=args[1];
int age=Integer.parseInt(args[2]);
float salary=Float.parseFloat(args[3]);
Tmp tmp=(Tmp)getObjById(id);
if(tmp==null){
throw new BreakException("找不到Id'"+id+"'对应的Tmp对象.");
}
tmp.setName(name);
tmp.setAge(age);
tmp.setSalary(salary);
dao.update(tmp);
return tmp.toXML();
}
/**
* 删除一个对象
* @param args
* @return
* @throws Exception
*/
public String delete(String[] args) throws Exception{
String id=args[0];
Tmp tmp=(Tmp)getObjById(id);
if(tmp==null){
throw new BreakException("找不到Id'"+id+"'对应的Tmp对象.");
}
dao.delete(tmp);
return tmp.toXML();
}
/**
* 分页搜索
* @param args
* @return
* @throws Exception
*/
public String pagedSearchBy(String[] args) throws Exception{
int currentPage=Integer.parseInt(args[0]); // 当前页
int pageSize=Integer.parseInt(args[1]); // 页面记录数
String name=args[2]; // 姓名
String salaryFrom=args[3]; // 薪水起点
String salaryTo=args[4]; // 薪水终点
String ageFrom=args[5]; // 年龄起点
String ageTo=args[6]; // 年龄终点
// 组合Sql语句
StringBuilder sb = new StringBuilder();
sb.append(" from " + domainClass + " d where d.valid=true ");
if(StringUtils.isNotBlank(name)){
sb.append(" and d.name like '%"+name+"%' " );
}
if(StringUtils.isNotBlank(salaryFrom)){
sb.append(" and d.salary >='"+salaryFrom+"' " );
}
if(StringUtils.isNotBlank(salaryTo)){
sb.append(" and d.salary <'"+salaryTo+"' " );
}
if(StringUtils.isNotBlank(ageFrom)){
sb.append(" and d.age >='"+ageFrom+"' " );
}
if(StringUtils.isNotBlank(ageTo)){
sb.append(" and d.age <'"+ageTo+"' " );
}
sb.append(" order by id asc ");
String hql=sb.toString();
// 取得分页查询结果
return getPagedSearchResultInXML(hql,currentPage,pageSize);
}
@Override
public String search(String[] args) throws Exception {
return null;
}
}
IService接口:
public interface IService{
/**
* 解析参数数组,组合成一个领域对象,然后添加到数据库(写方法)
*
* @param args
* @return
* @throws Exception
*/
public String add(String[] args) throws Exception;
/**
* 解析参数数组,更新领域对象的一个或多个属性,然后更新数据库中的对应记录
*
* @param args
* @return
* @throws Exception
*/
public String update(String[] args)throws Exception;
/**
* 解析参数数组得到要删除的领域对象的Id,然后根据它删除数据库中的对应记录
*
* @param args
* @return
* @throws Exception
*/
public String delete(String[] args) throws Exception;
/**
* 解析参数数组得到要取得的领域对象的Id,然后根据它渠道数据库中的对应记录
*
* @param args
* @return
* @throws Exception
*/
public String getById(String[] args) throws Exception;
/**
* 按条件进行分页查询
* 注意这里的条件最好写全,最好根据数组内容走不同的分支,不要写各种各样的查询函数,这样不方便缓存的处理
*
* @param args
* @return
* @throws Exception
*/
public String pagedSearchBy(String[] args) throws Exception;
/**
* 按条件进行查询,除了不分页要求和上面函数(pagedSearchBy)一致
*
* @param args
* @return
* @throws Exception
*/
public String search(String[] args) throws Exception;
/**
* 按ID取得信息
*
* @param args
* @return
* @throws Exception
*/
public String getInfoById(String[] args) throws Exception;
}
可以看出来,上面的服务类是直接走到数据库操作记录的,而我们需要在它的函数执行之前就让缓存发挥左右,因此,我们需要引入
ProxyFactoryBean和Interepter的帮助,在TmpServiceImpl类的实际方法运行前检索缓存。这需要进行一定的配置:
<!-- Tmp对象服务实现类类方法拦截器(一) -->
<bean id="tmpServiceMethodInterceptor" class="com.***.service.interceptor.ServiceMethodInterceptor"/>
<!-- Tmp对象服务实现类(二) -->
<bean id="TmpServiceImpl" class="com.***.service.TmpServiceImpl">
<property name="domainClass">
<value>Tmp</value>
</property>
<property name="dao">
<ref bean="dao"/>
</property>
<property name="transactionTemplate">
<ref bean="transactionTemplate"/>
</property>
</bean>
<!-- 对外的TmpService,实际上是TmpServiceImpl的代理(三) -->
<bean id="TmpService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.***.service.base.IService</value>
</property>
<property name="interceptorNames">
<list>
<value>tmpServiceMethodInterceptor</value>
</list>
</property>
<property name="target">
<ref bean="TmpServiceImpl"/>
</property>
</bean>
这样就可以了,下面是com.***.service.interceptor.ServiceMethodInterceptor类的代码,应该很好理解:
public class ServiceMethodInterceptor implements MethodInterceptor{
// 日志记录器
private static Logger logger = Logger.getLogger(ServiceMethodInterceptor.class);
// 作为缓存的哈希表
private Map<String,Object> cacheMap=new Hashtable<String,Object>();
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String className=invocation.getClass().getName();
String mothodName=invocation.getMethod().getName();
logger.info("类'"+className+"'的方法'"+mothodName+"'将得到调用!");
if(mothodName.contains("add") || mothodName.contains("update") || mothodName.contains("delete") ){
// 写方法来了,这意味着数据变更了,缓存可能不可靠,为安全起见需要重新来过
cacheMap.clear();
Object result=invocation.proceed();
logger.info("类'"+className+"'的方法'"+mothodName+"'调用完毕!");
return result;
}
else{
// 来的是读方法
// 通过组合方法名和参数来得到key
StringBuffer sb=new StringBuffer();
sb.append(mothodName+";");
Object[] arr=invocation.getArguments();
String[] arr2=(String[])arr[0];// 这一步的转化是很重要的
for(Object obj:arr2){
sb.append(obj+",");
}
String key=sb.toString();
// 拿Key查看缓存中是否有内容,有则直接返回即可
if(cacheMap.containsKey(key)){
logger.info("直接得到缓存中的结果!");
return cacheMap.get(key);
}
else{
Object result=invocation.proceed();
cacheMap.put(key, result);
logger.info("类'"+className+"'的方法'"+mothodName+"'调用完毕!");
return result;
}
}
}
}
之所以使用MethodInterceptor是因为在其中可以修改返回的结果,在上面出现的
Object result=invocation.proceed();
return result;
实际就是对TempServiceImpl函数执行的调用,result就是返回结果,它是可以改变的。因此,如果缓存中有对应内容,取出直接返回,没有的话调用这两句进行老实的数据库操作即可。
到这里,缓存已经可以使用了,当然,它还很不完善,在键的设计和简化,如果数据过多时的硬盘暂存,数据过期,写操作对缓存影响的精细化上都可以下一番工夫,这些我们日后再探讨吧。