對(duì)一個(gè)成規(guī)模的系統(tǒng)來(lái)說(shuō),緩存總是不可或缺的一個(gè)環(huán)節(jié),甚至?xí)蔀橄到y(tǒng)成功的重要因素。
從原理來(lái)講,緩存并不神秘,它本質(zhì)上只是一個(gè)哈希表,內(nèi)部包含許多提取關(guān)鍵字和緩存內(nèi)容的鍵值對(duì),當(dāng)有讀操作(如search)新的查詢到來(lái)時(shí),系統(tǒng)先到
這個(gè)哈希表中看看是否有同樣的關(guān)鍵字存在,是則取出對(duì)應(yīng)的值返回,否則進(jìn)行查詢,并把新的查詢條件和結(jié)果存儲(chǔ)進(jìn)哈希表,以便下次提取;當(dāng)有寫操作(如
add,delete,update)來(lái)臨時(shí),原則上說(shuō)現(xiàn)有緩存的內(nèi)容都存在了不確定性,那么簡(jiǎn)單的處理就是清空現(xiàn)有緩存。
緩存器的位置可以放在具體要執(zhí)行的CRUD方法之前,當(dāng)然我個(gè)人是不提倡這種耦合子系統(tǒng)的做法,利用Java的動(dòng)態(tài)代理機(jī)制,我們可以把數(shù)據(jù)庫(kù)訪問(wèn)和緩存
兩部分分離開來(lái),而Spring提供的ProxyFactoryBean和Interceptor正好給我們提供了現(xiàn)成的便利,使我們不需要再重復(fù)的發(fā)明
車輪。這樣做的最大好處是解耦子系統(tǒng),因?yàn)轳詈鲜菍?dǎo)致系統(tǒng)癱瘓的重大因素,所以我們必須盡量避免,隨時(shí)提防.
請(qǐng)看具體來(lái)說(shuō)是怎么實(shí)現(xiàn)緩存的,下面是需要為之提供緩存服務(wù)的TmpServiceImpl類及其接口:
TmpServiceImpl類:
public class TmpServiceImpl extends BaseService implements IService{
/**
* 添加一個(gè)Tmp對(duì)象到數(shù)據(jù)庫(kù)
* @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]);
// 同名檢測(cè)
if(hasSameName(name)){
throw new BreakException("已經(jīng)有和"+name+"同名的對(duì)象存在了.");
}
Tmp tmp=new Tmp(name,age,salary);
dao.create(tmp);
return tmp.toXML();
}
/**
* 將TMP對(duì)象的信息組合成一個(gè)字符串返回
*
* 說(shuō)明:要修改此方法請(qǐng)與何楊商議,請(qǐng)勿自行修改!
* @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+"'對(duì)應(yīng)的Tmp對(duì)象.");
}
StringBuilder sb=new StringBuilder();
sb.append("姓名:"+tmp.getName()+""r"n");
sb.append("年齡:"+tmp.getAge()+""r"n");
sb.append("薪水:"+tmp.getSalary()+""r"n");
sb.append("添加時(shí)間:"+tmp.getAddTime()+""r"n");
sb.append("更新時(shí)間:"+tmp.getRefreshTime()+""r"n");
return sb.toString();
}
/**
* 按ID取得一個(gè)Tmp對(duì)象
* @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+"'對(duì)應(yīng)的Tmp對(duì)象.");
}
return tmp.toXML();
}
/**
* 按薪水來(lái)查詢Tmp對(duì)象
* @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來(lái)更新一個(gè)Tmp對(duì)象
* @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+"'對(duì)應(yīng)的Tmp對(duì)象.");
}
tmp.setName(name);
tmp.setAge(age);
tmp.setSalary(salary);
dao.update(tmp);
return tmp.toXML();
}
/**
* 刪除一個(gè)對(duì)象
* @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+"'對(duì)應(yīng)的Tmp對(duì)象.");
}
dao.delete(tmp);
return tmp.toXML();
}
/**
* 分頁(yè)搜索
* @param args
* @return
* @throws Exception
*/
public String pagedSearchBy(String[] args) throws Exception{
int currentPage=Integer.parseInt(args[0]); // 當(dāng)前頁(yè)
int pageSize=Integer.parseInt(args[1]); // 頁(yè)面記錄數(shù)
String name=args[2]; // 姓名
String salaryFrom=args[3]; // 薪水起點(diǎn)
String salaryTo=args[4]; // 薪水終點(diǎn)
String ageFrom=args[5]; // 年齡起點(diǎn)
String ageTo=args[6]; // 年齡終點(diǎn)
// 組合Sql語(yǔ)句
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();
// 取得分頁(yè)查詢結(jié)果
return getPagedSearchResultInXML(hql,currentPage,pageSize);
}
@Override
public String search(String[] args) throws Exception {
return null;
}
}
IService接口:
public interface IService{
/**
* 解析參數(shù)數(shù)組,組合成一個(gè)領(lǐng)域?qū)ο螅缓筇砑拥綌?shù)據(jù)庫(kù)(寫方法)
*
* @param args
* @return
* @throws Exception
*/
public String add(String[] args) throws Exception;
/**
* 解析參數(shù)數(shù)組,更新領(lǐng)域?qū)ο蟮囊粋€(gè)或多個(gè)屬性,然后更新數(shù)據(jù)庫(kù)中的對(duì)應(yīng)記錄
*
* @param args
* @return
* @throws Exception
*/
public String update(String[] args)throws Exception;
/**
* 解析參數(shù)數(shù)組得到要?jiǎng)h除的領(lǐng)域?qū)ο蟮腎d,然后根據(jù)它刪除數(shù)據(jù)庫(kù)中的對(duì)應(yīng)記錄
*
* @param args
* @return
* @throws Exception
*/
public String delete(String[] args) throws Exception;
/**
* 解析參數(shù)數(shù)組得到要取得的領(lǐng)域?qū)ο蟮腎d,然后根據(jù)它渠道數(shù)據(jù)庫(kù)中的對(duì)應(yīng)記錄
*
* @param args
* @return
* @throws Exception
*/
public String getById(String[] args) throws Exception;
/**
* 按條件進(jìn)行分頁(yè)查詢
* 注意這里的條件最好寫全,最好根據(jù)數(shù)組內(nèi)容走不同的分支,不要寫各種各樣的查詢函數(shù),這樣不方便緩存的處理
*
* @param args
* @return
* @throws Exception
*/
public String pagedSearchBy(String[] args) throws Exception;
/**
* 按條件進(jìn)行查詢,除了不分頁(yè)要求和上面函數(shù)(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;
}
可以看出來(lái),上面的服務(wù)類是直接走到數(shù)據(jù)庫(kù)操作記錄的,而我們需要在它的函數(shù)執(zhí)行之前就讓緩存發(fā)揮左右,因此,我們需要引入
ProxyFactoryBean和Interepter的幫助,在TmpServiceImpl類的實(shí)際方法運(yùn)行前檢索緩存。這需要進(jìn)行一定的配置:
<!-- Tmp對(duì)象服務(wù)實(shí)現(xiàn)類類方法攔截器(一) -->
<bean id="tmpServiceMethodInterceptor" class="com.***.service.interceptor.ServiceMethodInterceptor"/>
<!-- Tmp對(duì)象服務(wù)實(shí)現(xiàn)類(二) -->
<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>
<!-- 對(duì)外的TmpService,實(shí)際上是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類的代碼,應(yīng)該很好理解:
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+"'將得到調(diào)用!");
if(mothodName.contains("add") || mothodName.contains("update") || mothodName.contains("delete") ){
// 寫方法來(lái)了,這意味著數(shù)據(jù)變更了,緩存可能不可靠,為安全起見需要重新來(lái)過(guò)
cacheMap.clear();
Object result=invocation.proceed();
logger.info("類'"+className+"'的方法'"+mothodName+"'調(diào)用完畢!");
return result;
}
else{
// 來(lái)的是讀方法
// 通過(guò)組合方法名和參數(shù)來(lái)得到key
StringBuffer sb=new StringBuffer();
sb.append(mothodName+";");
Object[] arr=invocation.getArguments();
String[] arr2=(String[])arr[0];// 這一步的轉(zhuǎn)化是很重要的
for(Object obj:arr2){
sb.append(obj+",");
}
String key=sb.toString();
// 拿Key查看緩存中是否有內(nèi)容,有則直接返回即可
if(cacheMap.containsKey(key)){
logger.info("直接得到緩存中的結(jié)果!");
return cacheMap.get(key);
}
else{
Object result=invocation.proceed();
cacheMap.put(key, result);
logger.info("類'"+className+"'的方法'"+mothodName+"'調(diào)用完畢!");
return result;
}
}
}
}
之所以使用MethodInterceptor是因?yàn)樵谄渲锌梢孕薷姆祷氐慕Y(jié)果,在上面出現(xiàn)的
Object result=invocation.proceed();
return result;
實(shí)際就是對(duì)TempServiceImpl函數(shù)執(zhí)行的調(diào)用,result就是返回結(jié)果,它是可以改變的。因此,如果緩存中有對(duì)應(yīng)內(nèi)容,取出直接返回,沒(méi)有的話調(diào)用這兩句進(jìn)行老實(shí)的數(shù)據(jù)庫(kù)操作即可。
到這里,緩存已經(jīng)可以使用了,當(dāng)然,它還很不完善,在鍵的設(shè)計(jì)和簡(jiǎn)化,如果數(shù)據(jù)過(guò)多時(shí)的硬盤暫存,數(shù)據(jù)過(guò)期,寫操作對(duì)緩存影響的精細(xì)化上都可以下一番工夫,這些我們?nèi)蘸笤偬接懓伞?