對一個成規模的系統來說,緩存總是不可或缺的一個環節,甚至會成為系統成功的重要因素。
從原理來講,緩存并不神秘,它本質上只是一個哈希表,內部包含許多提取關鍵字和緩存內容的鍵值對,當有讀操作(如search)新的查詢到來時,系統先到 這個哈希表中看看是否有同樣的關鍵字存在,是則取出對應的值返回,否則進行查詢,并把新的查詢條件和結果存儲進哈希表,以便下次提取;當有寫操作(如 add,delete,update)來臨時,原則上說現有緩存的內容都存在了不確定性,那么簡單的處理就是清空現有緩存。
緩存器的位置可以放在具體要執行的CRUD方法之前,當然我個人是不提倡這種耦合子系統的做法,利用Java的動態代理機制,我們可以把數據庫訪問和緩存 兩部分分離開來,而Spring提供的ProxyFactoryBean和Interceptor正好給我們提供了現成的便利,使我們不需要再重復的發明 車輪。這樣做的最大好處是解耦子系統,因為耦合是導致系統癱瘓的重大因素,所以我們必須盡量避免,隨時提防.
請看具體來說是怎么實現緩存的,下面是需要為之提供緩存服務的TmpServiceImpl類及其接口:
TmpServiceImpl類:
IService接口:
可以看出來,上面的服務類是直接走到數據庫操作記錄的,而我們需要在它的函數執行之前就讓緩存發揮左右,因此,我們需要引入 ProxyFactoryBean和Interepter的幫助,在TmpServiceImpl類的實際方法運行前檢索緩存。這需要進行一定的配置:
這樣就可以了,下面是com.***.service.interceptor.ServiceMethodInterceptor類的代碼,應該很好理解:
之所以使用MethodInterceptor是因為在其中可以修改返回的結果,在上面出現的
Object result=invocation.proceed();
return result;
實際就是對TempServiceImpl函數執行的調用,result就是返回結果,它是可以改變的。因此,如果緩存中有對應內容,取出直接返回,沒有的話調用這兩句進行老實的數據庫操作即可。
到這里,緩存已經可以使用了,當然,它還很不完善,在鍵的設計和簡化,如果數據過多時的硬盤暫存,數據過期,寫操作對緩存影響的精細化上都可以下一番工夫,這些我們日后再探討吧。
從原理來講,緩存并不神秘,它本質上只是一個哈希表,內部包含許多提取關鍵字和緩存內容的鍵值對,當有讀操作(如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;
}
}
/**
* 添加一個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;
}
/**
* 解析參數數組,組合成一個領域對象,然后添加到數據庫(寫方法)
*
* @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>
<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;
}
}
}
}
// 日志記錄器
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就是返回結果,它是可以改變的。因此,如果緩存中有對應內容,取出直接返回,沒有的話調用這兩句進行老實的數據庫操作即可。
到這里,緩存已經可以使用了,當然,它還很不完善,在鍵的設計和簡化,如果數據過多時的硬盤暫存,數據過期,寫操作對緩存影響的精細化上都可以下一番工夫,這些我們日后再探討吧。