zoukankan      html  css  js  c++  java
  • iBATIS缓存实现分析[转]

    为了提高应用程序性能,一种比较通用的方法是使用缓存技术来减少与数据库之间的交互。缓存技术是一种“以空间换时间”的设计理念,利用内存空间资源来提高数据检索速度的有效手段之一。

         iBATIS以一种简单、易用、灵活的方式实现了数据缓存。下面,首先看一下iBATIS关于缓存部分的核心类图:

    iBATIS缓存核心类图

         关于这些类的用途,在注释中做了比较概括性的说明,下面就来仔细的讲一下这些类的用途以及它们是如何工作的。

         在iBATIS中,可以配置多个缓存,每个cacheModel的配置对应一个CacheModel类的一个对象。其中包括id等配置信息。iBATIS通过这些配置信息来定义缓存管理的行为。

         缓存的目的是为了能够实现数据的高速检索。在程序中,数据是用对象表示的;为了能够检索到以缓存的数据对象,每个数据对象必须拥有一个唯一标识,在iBATIS中,这个唯一标识用CacheKey来表示。

         那么,缓存的数据保存到什么地方了呢?如何实现数据的快速检索呢?答案在CacheController的实现类中。每个CacheController中都有一个Map类型的属性cache来保存被缓存的数据,其中key为CacheKey类型,value为Object类型;需要关注的是CacheKey对象的hashCode的生成算法,每次调用CacheKey对象的update方法时,都会更新它的hashCode值,关于hashCode值的计算方法后续在给出详细说明。

         在拥有了数据缓存区后,就可以向其中存放数据和检索数据了。在iBATIS中,有多种的缓存管理策略,也可以自定义缓存管理策略。

         关于缓存的功能,主要有两种类型:一种是对外提供的功能:数据存储和数据检索;另外一种是内部管理的功能:缓存对象标识的生成,缓存区刷新,数据检索算法等。下面就逐一介绍这些功能的代码实现。

         1. 数据存储

             首先看一下CacheModel中的putObject方法是如何实现的

    1. public void putObject(CacheKey key, Object value) {  
    2.     if (null == value) value = NULL_OBJECT;  
    3.     //关于缓存的操作,需要互斥  
    4.     synchronized ( this )  {  
    5.      if (serialize && !readOnly && value != NULL_OBJECT) {  
    6.        //需要序列化,并且非只读,则需要将缓存对象序列化到内存,以供后续检索使用  
    7.         //readOnly为false时,不能直接将对象引用直接返回个客户程序  
    8.        try {  
    9.          ByteArrayOutputStream bos = new ByteArrayOutputStream();  
    10.          ObjectOutputStream oos = new ObjectOutputStream(bos);  
    11.          oos.writeObject(value);  
    12.          oos.flush();  
    13.          oos.close();  
    14.          value = bos.toByteArray();  
    15.        } catch (IOException e) {  
    16.          throw new RuntimeException("Error caching serializable object.  Cause: " + e, e);  
    17.        }  
    18.      }  
    19.      //如果执行了内存序列化,则保存的是它的字节数组  
    20.      controller.putObject(this, key, value);  
    21.      if ( log.isDebugEnabled() )  {  
    22.        log("stored object"true, value);  
    23.      }  
    24.    }  
    25.  }  

           因为真正缓存数据对象的地方是在CacheController中,所以CacheModel的putObject方法中会调用CacheController的putObject方法执行真正的数据存储。由于不同的CacheController实现的缓存管理方式不同,所以putObject实现也各不相同。下面分别介绍不同的CacheController实现的putObject方法

          1) FifoCacheController

    1. public void putObject(CacheModel cacheModel, Object key, Object value) {  
    2.   //保存到Map中  
    3.   cache.put(key, value);  
    4.   //保存key到keyList  
    5.   keyList.add(key);  
    6.   //如果当前key的数量大于缓存容量时,移除keyList和cache中的第一个元素,达到先进先出的目的  
    7.   if (keyList.size() > cacheSize) {  
    8.     try {  
    9.       Object oldestKey = keyList.remove(0);  
    10.       cache.remove(oldestKey);  
    11.     } catch (IndexOutOfBoundsException e) {  
    12.       //ignore  
    13.     }  
    14.   }  
    15. }  

           2)LruCacheController

    1. public void putObject(CacheModel cacheModel, Object key, Object value) {  
    2.   cache.put(key, value);  
    3.   keyList.add(key);  
    4.   if (keyList.size() > cacheSize) {  
    5.     try {  
    6.       //取得keyList中的第一个元素作为最近最少用的key,为什么呢?  
    7.        //这个问题等到讲解它的getObject方法时别会知晓  
    8.       Object oldestKey = keyList.remove(0);  
    9.       cache.remove(oldestKey);  
    10.     } catch (IndexOutOfBoundsException e) {  
    11.       //ignore  
    12.     }  
    13.   }  
    14. }  

          3)MemoryCacheController

    1. public void putObject(CacheModel cacheModel, Object key, Object value) {  
    2.   Object reference = null;  
    3.   //根据配置创建响应的引用类型,此种缓存管理方式完全交给jvm的垃圾回收器来管理  
    4.    //创建好引用后,将数据对象放入到引用中  
    5.   if (referenceType.equals(MemoryCacheLevel.WEAK)) {  
    6.     reference = new WeakReference(value);  
    7.   } else if (referenceType.equals(MemoryCacheLevel.SOFT)) {  
    8.     reference = new SoftReference(value);  
    9.   } else if (referenceType.equals(MemoryCacheLevel.STRONG)) {  
    10.     reference = new StrongReference(value);  
    11.   }  
    12.   //在缓存中保存引用  
    13.   cache.put(key, reference);  
    14. }  

          4)OSCacheController

          这个缓存管理使用了OSCache来管理缓存,这里就不做仔细的介绍了。

         2. 数据检索

         在数据被放置到缓存区中以后,程序需要根据一定的条件进行数据检索。首先看一下CacheModel类的getObject方法是如何检索数据的

    1. public Object getObject(CacheKey key) {  
    2.     Object value = null;  
    3.   //互斥访问缓冲区  
    4.   synchronized (this) {  
    5.     if (flushInterval != NO_FLUSH_INTERVAL  
    6.         && System.currentTimeMillis() - lastFlush > flushInterval) {  
    7.       //如果到了定期刷新缓冲区时,则执行刷新  
    8.       flush();  
    9.     }  
    10.     //根据key来从CacheController中取得数据对象  
    11.     value = controller.getObject(this, key);  
    12.     if (serialize && !readOnly &&  
    13.             (value != NULL_OBJECT && value != null)) {  
    14.       //如果需要序列化,并且非只读,则从内存中序列化出一个数据对象的副本  
    15.       try {  
    16.         ByteArrayInputStream bis = new ByteArrayInputStream((byte[]) value);  
    17.         ObjectInputStream ois = new ObjectInputStream(bis);  
    18.         value = ois.readObject();  
    19.         ois.close();  
    20.       } catch (Exception e) {  
    21.         throw new RuntimeException("Error caching serializable object.  Be sure you're not attempting to use " +  
    22.                                          "a serialized cache for an object that may be taking advantage of lazy loading.  Cause: " + e, e);  
    23.       }  
    24.     }  
    25.     //下面的两个操作是用来计算缓存区数据检索的命中率的  
    26.     //对于缓冲区的数据检索请求加一操作  
    27.     requests++;  
    28.     //如果检索到数据,则命中数加一  
    29.     if (value != null) {  
    30.       hits++;  
    31.     }  
    32.     if ( log.isDebugEnabled() )  {  
    33.         if ( value != null )  {  
    34.           log("retrieved object"true, value);  
    35.         }  
    36.         else  {  
    37.             log("cache miss"falsenull);  
    38.         }  
    39.     }  
    40.   }  
    41.   return value;  
    42. }  

        真正的数据检索操作是在CacheController的实现类中进行的,下面就分别来看一下各个实现类是如何检索数据的。

         1) FifoCacheController

    1. public Object getObject(CacheModel cacheModel, Object key) {  
    2.   //直接从Map中取得  
    3.   return cache.get(key);  
    4. }  

         2) LruCacheController

    1. public Object getObject(CacheModel cacheModel, Object key) {  
    2.   Object result = cache.get(key);  
    3.   //因为这个key被使用了,如果检索到了数据,则将其移除并重新放置到队尾  
    4.    //这样的目的就是保持最近使用的key放在队尾,而对头为最近未使用的  
    5.    //如果没有检索到对象,则直接将该key移除  
    6.   keyList.remove(key);  
    7.   if (result != null) {  
    8.     keyList.add(key);  
    9.   }  
    10.   return result;  
    11. }  

         3) MemoryCacheController

    1. public Object getObject(CacheModel cacheModel, Object key) {  
    2.   Object value = null;  
    3.   //取得引用对象  
    4.   Object ref = cache.get(key);  
    5.   if (ref != null) {  
    6.     //从引用对象中取得数据对象  
    7.     if (ref instanceof StrongReference) {  
    8.       value = ((StrongReference) ref).get();  
    9.     } else if (ref instanceof SoftReference) {  
    10.       value = ((SoftReference) ref).get();  
    11.     } else if (ref instanceof WeakReference) {  
    12.       value = ((WeakReference) ref).get();  
    13.     }  
    14.   }  
    15.   return value;  
    16. }  

       3 唯一标识的生成

          在iBATIS中,用CacheKey来标识一个缓存对象,而CacheKey通常是作为Map中的key存在,所以CacheKey的hashCode的计算方法异常重要。影响hashCode的值有很多方面的因素,对每一个影响hashCode的元素,都需要调用CacheKey的update方法来重新计算hashCode值。下面我们就来看一下CacheKey的创建以及计算的相关过程。

          首先CacheKey是在BaseDataExchange类的getCacheKey方法中被创建的。

    1. public CacheKey getCacheKey(StatementScope statementScope, ParameterMap parameterMap, Object parameterObject) {  
    2.   CacheKey key = new CacheKey();  
    3.   //取得parameterObject中的数据,这个parameterObject就是客户端传递过来的参数对象  
    4.   Object[] data = getData(statementScope, parameterMap, parameterObject);  
    5.   //根据parameterObject中的数据去重计算hashCode  
    6.   for (int i = 0; i < data.length; i++) {  
    7.     if (data[i] != null) {  
    8.       key.update(data[i]);  
    9.     }  
    10.   }  
    11.   return key;  
    12. }  

          这个方法被MappedStatement中的getCacheKey调用

    1. public CacheKey getCacheKey(StatementScope statementScope, Object parameterObject) {  
    2.   Sql sql = statementScope.getSql();  
    3.   ParameterMap pmap = sql.getParameterMap(statementScope, parameterObject);  
    4.   CacheKey cacheKey = pmap.getCacheKey(statementScope, parameterObject);  
    5.   //statement id对hashCode有影响  
    6.   cacheKey.update(id);  
    7.   cacheKey.update(baseCacheKey);  
    8.   //sql语句对hashCode有影响  
    9.   cacheKey.update(sql.getSql(statementScope, parameterObject)); //Fixes bug 953001  
    10.   return cacheKey;  
    11. }  

         真正需要CacheKey对象的地方是在CacheStatement类中

    1. public CacheKey getCacheKey(StatementScope statementScope, Object parameterObject) {  
    2.   CacheKey key = statement.getCacheKey(statementScope, parameterObject);  
    3.   //如果不可读并且不被序列化,那么当前的SessionScope也对hashCode有影响  
    4.    //而真正起作用的是SessionScope的id属性  
    5.    //也就是说这个缓存与调用线程的会话有关,当前线程所存储的数据不能被其他线程使用  
    6.   if (!cacheModel.isReadOnly() && !cacheModel.isSerialize()) {  
    7.     key.update(statementScope.getSession());  
    8.   }  
    9.   return key;  
    10. }  

        经过上述一系列的getCacheKey调用,将对CacheKey有影响的因素施加给了hashCode。其中对CacheKey的hashCode起影响作用的因素主要有:baseCacheKey,sql语句,参数值,statement id。可能产生影响的因素是session id。

        现在我们知道了决定CacheKey的相关因素,也就知道了iBATIS是如何唯一的确定一个缓存对象。

        经过以上的代码分析,可以掌握iBatis如何生成CacheKey对象和计算其hashCode值,以及存储和检索数据对象。这些正是iBATIS缓存的基础,掌握了这些实现原理,有助于我们更高效的使用iBATIS缓存功能,或者是开发自己的缓存系统。

  • 相关阅读:
    十五周作业
    第十四周总结
    十三周总结与感想
    排球比赛积分规则
    Centos + nginx + JBOSS AS 7 搭建Java web application
    eclipse + maven + jboss 遇到ClassNotFoundException
    “/”应用程序中的服务器错误。 纠错方法
    Sharepoint 问题集锦
    Sharepoint 问题集锦
    Sharepoint 问题集锦
  • 原文地址:https://www.cnblogs.com/lvpei/p/1984138.html
Copyright © 2011-2022 走看看