上一篇博客针对Key值生成策略和Redis数据源读取的问题,想了一些解决的思路,停滞了一段时间后,最近实现好了。下面就根据实现,再结合解决思路说明一下。本篇博客先说KEY值生成策略的问题。

前提

    我们的系统是Spring mvc + EJB实现的。因为分布式调用的问题,为了避免脏数据我们的数据缓存需要加在Service层。这个之前没有交代过,通过下面这个图,解释一下:

       

redis 双主键 redis主键生成策略_键值

    图中①和②分别表明了两个加数据缓存的位置,另外如图,远程调用是发生在EJB容器之间的。另外,查询方法的结果加入缓存后还需要清除在增、删、改这些操作的时候清除缓存,以保证不出现脏数据。如果是在模块1的①的位置加入数据缓存,如果缓存存在时就不会再往后调用了。而问题是如果这个方法的数据涉及到了其模块2的数据,模块2的数据变更是无法通知到模块1的IOC容器的。那么脏数据就无法避免了。

  由上可知,数据缓存需要加在②位置,并且是没有发生远程调用的方法上。因为同一个类上的对数据操作是可知的。这样,加缓存和清缓存的操作就可知了。

  这个前提决定了缓存的操作可以精确到类,也就决定了KEY值的生成策略(请看上篇博客:Redis+EJB实现缓存(三)),加缓存的KEY值可以以类名+方法名+参数,删除缓存则以类为单位删除。        

加缓存的拦截器方法实现


public Object cache(InvocationContext ctx) throws Exception{
    	 System.out.println("进入拦截器");  
    	 if(cachepro.getFlag() == false){
    		 InputStream in = (ctx.getTarget().getClass().getResourceAsStream("/config/cache.xml"));
             byte[] byt = new byte[in.available()];
             in.read(byt);
             cache.init(byt);
    	 }   
    	 //取得类名
    	 String className = ctx.getTarget().getClass().getSimpleName();
		 //取得方法名
    	 String methodName = ctx.getMethod().getName();
		 //取得参数
    	 Object[] args = ctx.getParameters();
    	 String arg = "";
    	 for(int i=0;i<args.length;i++)  
         {  
            arg = arg + args[i].toString();
         }  
		 //主键值,由系统名称+类名,删除时用
    	 String mainKey = cachepro.getPrefixion() + className;
		 //次级键值由主键值再加上方法名和参数
    	 String key = mainKey + methodName + arg;
		 //次级键值存在则返回结果
    	 if(cache.ishave(key)){
    		 return cache.get(key);
    	 }else{
			 //次级键值不存在则将次级键值存入主键值下
    		 Object result = ctx.proceed(); 	 
        	 cache.set(mainKey, key);
        	 cache.set(key,result);
        	 return null;
    	 }	
      }

删除缓存拦截器方法实现


public Object cache(InvocationContext ctx) throws Exception{ 
    	 if(cachepro.getFlag() == false){
    		 InputStream in = (ctx.getTarget().getClass().getResourceAsStream("/config/cache.xml"));
             byte[] byt = new byte[in.available()];
             in.read(byt);
             cache.init(byt);
    	 }  
		 //取得类名
    	 String className = ctx.getTarget().getClass().getSimpleName();
		 //系统名+类名生成主键值,对应添加时的
    	 String mainKey = cachepro.getPrefixion() + className;
		 //删除时,取得主键值下的次级键值,删除缓存
    	 cache.del(mainKey);  
    	 return null;
    }

    这样,键值的生成策略就算是基本完成了。但是后面思考了一下,当参数类型是Object时还有问题。

问题描述

    Object的toString方法,是getClass().getName()+ '@' + Integer.toHexString(hashCode())这个hashCode只是标明了类的在hashTable中的位置。换句话说,两个一样的类由于两次生成的类,可能不存在同一个位置下。那么Object参数作为键值时就无法精确定位同一个键值,那么这样在清除缓存时就无法清除了。

解决思路

    作为参数的Object都是实体,因此我们需要重写toString方法,使得类中的字段一样时返回的String是一样的。这样就解决了,两个实体属性一样的类作为参数时,生成的键值是一样的。问题就解决了。

    小结,新的问题,明天就实现,应该是可行。下篇博客我们再说,Redis数据源的解决实现。这篇博客就到这里了。上面的代码主要看注释的那部分。