当柳上原的风吹向天际的时候...

真正的快乐来源于创造

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  368 Posts :: 1 Stories :: 201 Comments :: 0 Trackbacks
对一个成规模的系统来说,缓存总是不可或缺的一个环节,甚至会成为系统成功的重要因素。

从原理来讲,缓存并不神秘,它本质上只是一个哈希表,内部包含许多提取关键字和缓存内容的键值对,当有读操作(如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就是返回结果,它是可以改变的。因此,如果缓存中有对应内容,取出直接返回,没有的话调用这两句进行老实的数据库操作即可。

到这里,缓存已经可以使用了,当然,它还很不完善,在键的设计和简化,如果数据过多时的硬盘暂存,数据过期,写操作对缓存影响的精细化上都可以下一番工夫,这些我们日后再探讨吧。
posted on 2010-05-24 15:10 何杨 阅读(629) 评论(0)  编辑  收藏

只有注册用户登录后才能发表评论。


网站导航: