Mybatis实现【6】--缓存

缓存概述

(同【1】中cache部分)

By default,the first-level cache is enabled;this means that if you'll invoke the same SELECT statement within the same sqlSession interface,results will be fetched from the cache instead of the database.

We can add global second-level cacahes by adding the <cache/> element in SQL Mapper XML files.

When you'll add the <cache/> element the following will occur:

  • All results from the <select> statements in the mapped statement file will be cached

  • All the <insert>、<update>、and <delete> statements in the mapped statement file will flush the cache.

  • The cache will use a Least Recently Used(LRU) algorithm for eviction

  • The cache will not flush on any sort of time-based schedule(no Flush Interval)

  • The cache will store 1024 references to lists or objects(whatever the query method returns)

  • The cache will be treated as a read/write;this means that the objects retrieved will not be shared and can safely be modified by the caller without it interfering with other potential modifications by other callers or threads

实现机制

MyBatis缓存采用了delegate机制及装饰器设计模式,当put、get、remove时,其中会经过多层delegate cache处理。Cache类别有:

-BaseCache(基础缓存):为缓存数据最终存储的处理类,默认为PerpetualCache,基于Map存储;可自定义存储器,如基于EhCache、Memcached等

-EvictionCache(排除算法缓存):当缓存数量达到一定大小后,将通过算法对缓存数据进行清除。默认使用Lru算法(LruCache),其他有fifo算法(FifoCache)

-DecoratorCache(装饰器缓存):缓存put/get处理前后的装饰器,如使用LoggingCache输出缓存命中日志信息、使用SerializedCache对Cache的数据put或get进行序列化处理、当设置flushInterval(1/h)后,会使用ScheduledCache对缓存进行定时刷新等。

MyBatis的Key生成规则为:[hashcode:checksum:mappedStatementId:offset:limit:executeSql:queryParams]

对于并发Read/Write时缓存的数据同步问题,MyBatis默认基于JDK/concurrent中的ReadWriteLock,使用ReentrantReadWriteLock的实现,从而通过Lock机制防止在并发Write Cache过程中的线程问题。

Executor

Executor是最终执行数据获取及更新的实例

麦库截图20141417143231712.jpg 

1、BaseExecutor:基础执行器抽象类。实现了如createCacheKey之类的通用方法。并使用“模版模式”将具体的数据库操作逻辑(doUpdate、doQuery)交由子类实现。

该类采用PerpetualCache实现基于Map存储的一级缓存。query方法如下:

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
   BoundSql boundSql = ms.getBoundSql(parameter);
   //创建缓存KEY
   CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
   return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
   ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
   if (closed) throw new ExecutorException("Executor was closed.");
   if (queryStack == 0 && ms.isFlushCacheRequired()) {
     clearLocalCache();
   }
   List<E> list;
   try {
     queryStack++;
   //从缓存中获取数据
     list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
     if (list != null) {
       handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
     } else {
   //从数据库中查出数据并缓存
       list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
     }
   } finally {
     queryStack--;
   }
   if (queryStack == 0) {
     for (DeferredLoad deferredLoad : deferredLoads) {
       deferredLoad.load();
     }
     deferredLoads.clear(); // issue #601
     if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
       clearLocalCache(); // issue #482
     }
   }
   return list;
 }

三个子类实现了其doQuery、doUpdate等方法,同样都是采用JDBC对数据库的操作

BatchExcutor:批量处理

ReuseExcutor:重用Statement执行

SimpleExcutor:普通方式执行

2、CachingExcutor

二级缓存执行器。这里灵活使用了delegate机制,期委托执行的类是BaseExcutor。当无法从二级缓存中获取数据时,同样需要从DB中进行查询,直接委托给BaseExcutor进行查询。大致流程如下:

麦库截图20141417145435587.jpg 

流程为:从二级缓存中进行查询-->无命中委托BaseExcutor-->从一级缓存中查询-->未命中这执行JDBC查询

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) { 
        ensureNoOutParams(ms, parameterObject, boundSql);
        if (!dirty) {
          //获取读锁(Read锁可由多个Read线程同时保持)
          cache.getReadWriteLock().readLock().lock();
          try {
            //从二级缓存中获取
            @SuppressWarnings("unchecked")
            List<E> cachedList = (List<E>) cache.getObject(key);
            if (cachedList != null) return cachedList;
          } finally {
            cache.getReadWriteLock().readLock().unlock();
          }
        }
        //委托给BaseExcutor
        List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
        return list;
      }
    }
    ///没有设置二级缓存,则直接委托BaseExcutor执行
    return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

Cache委托链

1、委托链的构建

MyBatis缓存类的设计采用装饰器模式,基于委托的调用机制。

MyBatis在解析其Mapper配置文件时就对Cache实例进行构建。在XMLMapperBuilder中:

private void cacheElement(XNode context) throws Exception {
  if (context != null) {
  //基础缓存类型  
  String type = context.getStringAttribute("type", "PERPETUAL");
    Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
  //采用LRU排除算法
    String eviction = context.getStringAttribute("eviction", "LRU");
    Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
  //设置自动刷新时间  
  Long flushInterval = context.getLongAttribute("flushInterval");
  //设置缓存大小  
  Integer size = context.getIntAttribute("size");
    boolean readWrite = !context.getBooleanAttribute("readOnly", false);
    Properties props = context.getChildrenAsProperties();
  //初始化缓存实现  
  builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);
  }
}

初始化缓存实现源码如下:

public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      Properties props) {
    typeClass = valueOrDefault(typeClass, PerpetualCache.class);
    evictionClass = valueOrDefault(evictionClass, LruCache.class);
    //这里构建Cache实例采用Builder模式,每一个Namespace生产一个Cache实例
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(typeClass)
        .addDecorator(evictionClass)
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

build方法如下:

public Cache build() {
    setDefaultImplementations();
    //创建基础缓存实例
    Cache cache = newBaseCacheInstance(implementation, id);
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    //初始化排除算法实例,并将其委托到基础缓存中
    if (cache.getClass().getName().startsWith("org.apache.ibatis")) {
      for (Class<? extends Cache> decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      //标准装饰器缓存设置,如LoggingCache之类,并将其委托至基础缓存中
      cache = setStandardDecorators(cache);
    }
    return cache;
  }

最后的调用链如下:

麦库截图20141517151545511.jpg 

2、Cache的实例分析

2.1、SynchronizedCache

用于控制ReadWriteLock,避免并发所产生的线程安全问题

public void putObject(Object key, Object object) {
   acquireWriteLock();
   try {
     delegate.putObject(key, object);
   } finally {
     releaseWriteLock();
   }
 }

 public Object getObject(Object key) {
   acquireReadLock();
   try {
     return delegate.getObject(key);
   } finally {
     releaseReadLock();
   }
 }
 private void acquireReadLock() {
   getReadWriteLock().readLock().lock();
 }
 private void acquireWriteLock() {
   getReadWriteLock().writeLock().lock();
 }

2.2、LoggingCache

用于日志记录处理,主要输出命中率信息

public Object getObject(Object key) {
  requests++;
  final Object value = delegate.getObject(key);
  if (value != null) {
    hits++;
  }
  if (log.isDebugEnabled()) {
    log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
  }
  return value;
}

2.3、SerializedCache

向缓存中put和get数据时序列化和反序列化处理

public void putObject(Object key, Object object) {
  if (object == null || object instanceof Serializable) {
    delegate.putObject(key, serialize((Serializable) object));
  } else {
    throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
  }
}

public Object getObject(Object key) {
  Object object = delegate.getObject(key);
  return object == null ? null : deserialize((byte[]) object);
}

2.4、LruCache

这里的LRU算法基于LinkedHashMap,并覆盖其removeEldestEntry方法实现。初始化的大小为1024个元素

  public LruCache(Cache delegate) {
    this.delegate = delegate;
    setSize(1024);//设置默认大小为1024
  }
  public void setSize(final int size) {
    // TODO look for a better solution to this, see issue #335
    keyMap = Collections.synchronizedMap(new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;
    //覆盖该方法,当每次往map中put数据时,如果该方法返回true,则移除该Map中使用最少的Entry
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        if (tooBig) {
          //记录当前最老的缓存数据的key,以便委托给下一个cache实现删除
            eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    });
  }

  public void putObject(Object key, Object value) {
    delegate.putObject(key, value);
    //每次put后,调用移除最老的key
    cycleKeyList(key);
  }
//如果有最老的eldestKey,有就调用并移除
  private void cycleKeyList(Object key) {
    keyMap.put(key, key);
    if (eldestKey != null) {
      delegate.removeObject(eldestKey);
      eldestKey = null;
    }
  }

  public Object getObject(Object key) {
    //便于该Map统计get该key的次数
    keyMap.get(key); //touch
    return delegate.getObject(key);

  }


该系列文章参考如下书籍及文章:

《Java Persistence with MyBatis 》

《http://www.cnblogs.com/hzhuxin/p/3349836.html》

《http://www.iteye.com/topic/1112327》

《http://www.iteye.com/blogs/subjects/mybatis_internals》

《http://denger.me/2011/05/mybatis-and-spring-interface-integrated/》























  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值