Mybatis的一级缓存和二级缓存

1、简介

MyBatis提供了支持一级缓存二级缓存

一级缓存:基于PerpetualCache 的 HashMap本地缓存,其存储作用域是Session,当session flush或者close时,该session中的cache全部清空。

二级缓存:与一级缓存机制类似,也是使用的PerpetualCache的HashMap实现,其作用域是mapper.xml(namespace),可自定义存储源,比如Encache。

更新机制:只要在作用域内进行了增删改操作后,该作用域所有的select的cache全部clear。

PerpetualCache源码:

package org.apache.ibatis.cache.impl;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;

/**
 * @author Clinton Begin
 */
public class PerpetualCache implements Cache {

  private String id;

  private Map<Object, Object> cache = new HashMap<Object, Object>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public int getSize() {
    return cache.size();
  }

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  @Override
  public boolean equals(Object o) {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    if (this == o) {
      return true;
    }
    if (!(o instanceof Cache)) {
      return false;
    }

    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }

  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }

}

2、MyBatis的一级缓存

作用域是Session,当session flush或者close时,该session中的cache全部清空,默认开启。

注:a、单独使用MyBatis而不继承Spring时,使用原生的Mybatis的SqlSessionFactory来创建session,一级缓存有效。

b、集成spring(使用Mybatis-spring)时,每次查询spring会重新创建sqlSession,所以一级缓存不生效。但是开启事务时,在一个事务中,使用的是一个sqlSession,这时一级缓存生效。

3、MyBatis的二级缓存

二级缓存的使用:

全局缓存,其作用域为 Mapper(Namespace),默认关闭。

a、在mybatis.xml中配置开启缓存

<setting name="cacheEnabled" value="true"/>

b、在XXXmapper.xml文件中配置缓存:


<mapper namespace="com.yanwei.mapper.UserMapper">
    <!-- Cache 配置 -->
    <cache 
      blocking=""
  eviction ="FIFO" flushInterval ="60000" size ="512" readOnly ="true"
       type =""  /> </ mapper >
  • blocking:true或者false,Blocking=true,那么会调用BlockingCache,则针对同一个CacheKey,拿数据与放数据、删数据是互斥的
  • eviction:缓存回收策略,默认就是LRU.

        LRU – 最近最少使用的:移除最长时间不被使用的对象。
        FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
        SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
        WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

  • flushInterval:刷新间隔。默认无刷新间隔,只有调用语句时刷新。
  • size:缓存的对象数目。默认值是 1024
  • readonly:属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例
  • type:缓存类,可以自定义缓存,实现Cache,默认是"PERPETUAL",也就是org.apache.ibatis.cache.impl.PerpetualCache

c、XXXmapper.xml文件中sql配置缓存

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
  • flushCache:true或false,刷新缓存,也就是清除缓存。
  • useCache:是否使用缓存,true使用cache,false不使用cache。

d、引用其他命名空间的缓存策略:

<cache-ref namespace="com.yanwei.mapper.OtherMapper"/>

二级缓存的实例化过程:

读取mapper.xml中的<cache/>进行实例化,调用XMLMapperBuilder的cacheElement方法:

  private void cacheElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      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);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

这段代码获取xml中的cache元素,并且设置cache元素的属性默认值。后面两行是获取设置的默认值,并进行创建Cache,下面看最后一行调用的userNewCache方法

public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

这里用了创建型模式-生成器,其中调用的properties(pros)是封装属性值,build()进行生成对象。下面看build()方法:

  public Cache build() {
    setDefaultImplementations();
    //我标注的注释:构建基础的缓存,implementation指的是type配置的值,这里是默认的PerpetualCache
    Cache cache = newBaseCacheInstance(implementation, id);
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    //我标注的注释:如果是PerpetualCache,根据eviction进行继续装饰,到这一步,给PerpetualCache加上了LRU的功能
    if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      //我标注的注释:这一步是标准装饰
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }
接下来看标准装饰(setStandardDecorators)的源码实现:

  private Cache setStandardDecorators(Cache cache) {
    try {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      if (size != null && metaCache.hasSetter("size")) {
        metaCache.setValue("size", size);
      }
      if (clearInterval != null) {
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      if (readWrite) {
        cache = new SerializedCache(cache);
      }
      cache = new LoggingCache(cache);
      cache = new SynchronizedCache(cache);
      if (blocking) {
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
  }
这个方法中是根据配置参数进行装饰:

  • 如果配置了flushInterval,那么继续装饰为ScheduledCache,这意味着在调用Cache的getSize、putObject、getObject、removeObject四个方法的时候都会进行一次时间判断,如果到了指定的清理缓存时间间隔,那么就会将当前缓存清空
  • 如果readWrite=true,那么继续装饰为SerializedCache,这意味着缓存中所有存储的内存都必须实现Serializable接口
  • 跟配置无关,将之前装饰好的Cache继续装饰为LoggingCache与SynchronizedCache,前者在getObject的时候会打印缓存命中率,后者将Cache接口中所有的方法都加了Synchronized关键字进行了同步处理
  • 如果blocking=true,那么继续装饰为BlockingCache,这意味着针对同一个CacheKey,拿数据与放数据、删数据是互斥的,即拿数据的时候必须没有在放数据、删数据
装饰完毕后,返回cache,到这里MyBatis的二级缓存生成完成。

二级缓存说明:

    1. 映射语句文件中的所有select语句将会被缓存,内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象

    2. 映射语句文件中的所有insert,update和delete语句会刷新缓存,也就是清空该namespace下的缓存。

public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

    3. 缓存会使用Least Recently Used(LRU,最近最少使用的)算法来收回。

    4. 缓存会根据指定的时间间隔来刷新。

    5. 缓存会存储1024个对象

    6、假如定义了MyBatis二级缓存,那么MyBatis二级缓存读取优先级高于MyBatis一级缓存。

    7、二级缓存的创建生成策略是使用的装饰器模式实现的。

    8、二级缓存必须保证所有的增删查改都在同一个命名空间才可以。

4、自定义MyBatis缓存

实现org.apache.ibatis.cache.Cache接口自定义缓存,或者其他第三方缓存方案创建适配器来完成覆盖缓存行为。

在mapper.xml中引入缓存时,type定义为自己的自定义的缓存类,或者第三方缓存类。

<mapper namespace="dao.userdao">
    <!-- Cache 配置 -->
    <cache 
      type="com.yanwei.utils.MyOwnCache" 
      eviction="FIFO"        
      flushInterval="60000"        
      size="512"        
      readOnly="true">
      <property name="cacheFile" value="/tmp/my-owm-cache.tmp"/>
    </cache>

</
mapper > 配置自己的缓存,通过 cache元素来传递属性,上面代码会在自定义缓存实现中调用一个称为“setCacheFile(String file)”的方法

底层org.apache.ibatis.cache.Cache接口的源码:

package org.apache.ibatis.cache;

import java.util.concurrent.locks.ReadWriteLock;
/**
* SPI缓存提供者
* 为每个名称空间创建一个缓存实例
* 缓存实现必须有一个作为字符串参数接收缓存id的构造函数 
* Mybatis将把名称空间最为id传递给构造函数。
 * 
 * public MyCache(final String id) {
 *  if (id == null) {
 *    throw new IllegalArgumentException("Cache instances require an ID");
 *  }
 *  this.id = id;
 *  initialize();
 * }
 *
 * @author Clinton Begin
 */

public interface Cache {

  String getId();

  void putObject(Object key, Object value);

  Object getObject(Object key);

  Object removeObject(Object key);

  void clear();

  int getSize();
  
  ReadWriteLock getReadWriteLock();

}

自定义的缓存类:

package com.yanwei.utils;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;

public class MyOwnCache implements Cache{
	
	private String id;
	
	private Map<Object, Object> cache = new HashMap<Object, Object>();
	
	//必须要有这个构造方法,详见Cache接口注释
	public MyOwnCache(String id){
		this.id = id;
	}
	
	@Override
	public String getId() {
		return id;
	}

	@Override
	public void putObject(Object key, Object value) {
		cache.put(key, value);
	}

	@Override
	public Object getObject(Object key) {
		return cache.get(cache);
	}

	@Override
	public Object removeObject(Object key) {
		return cache.remove(key);
	}

	@Override
	public void clear() {
		cache.clear();
	}

	@Override
	public int getSize() {
		return cache.size();
	}
	
	public boolean equals(Object o){
		if (getId() == null) {
			throw new CacheException("Cache instances require an ID.");
	    }
		if (this == o) {
			return true;
	    }
		if (!(o instanceof Cache)) {
		    return false;
		}

	    Cache otherCache = (Cache) o;
	    return getId().equals(otherCache.getId());
	}

	@Override
	public ReadWriteLock getReadWriteLock() {
		return null;
	}
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值