MyBatis二级缓存巧妙使用装饰器模式与builder模式

MyBatis二级缓存巧妙使用装饰器模式与builder模式


mybatis在查询数据时使用一级缓存和二级缓存来提交查询效率,并减少数据库压力。

  • 一级缓存也称为本地缓存,是默认开启的,它的生效范围为sqlSession,在实际的生产项目中,由于数据库连接是从连接池中获取,几乎每次使用的session都不同,因此很少能使用到一级缓存。
  • 二级缓存也称为全书缓存,它的生效范围在一个namespace中,如果需要使用二级缓存需要我们手动开启,第一步:在mybatis全书配置文件加入setting设置,第二步:在mapper.xml配置文件中加入cache标签,如果不需要自定义缓存,则不用指定type,此时会使用默认的缓存实现类org.apache.ibatis.cache.impl.PerpetualCache来实现二级缓存,它的底层就是用HashMap来实现的。

在实际开发中,二级缓存还是用的比较多的,使用mybatis结合redis实现二级缓存非常方便,它也能适用于分布式场景。在阅读mybatis源码过程中,发现mybatis生成缓存类的时候,使用了装饰器模式和builder模式,结合的非常巧妙,故写此文章与各位分享。装饰器模式和builder模式不是本次主要讨论的主题,只作简要概述。


装饰器模式主要用于增强对象功能,
MyBatis二级缓存巧妙使用装饰器模式与builder模式_第1张图片 装饰器模式的核心在于给原始对象加装饰器是在构造方法中进行的,而原始对象是作为代理存在的,被内层装饰器装饰后的对象又作为外层装饰器的代理,使用时,外层装饰器执行方法时,核心方法由代理对象完成,同时可以加上装饰器的特有功能,例如日志记录。下面来看看mybatis中缓存类是如何使用装饰器模式的:MyBatis二级缓存巧妙使用装饰器模式与builder模式_第2张图片mybatis中的缓存装饰器有很多,分别实现不同的功能,而它提供的缓存实现类只有一个即PerpetualCache,它也是我们前面所提到的默认缓存实现类,每个装饰器都实现了Cache接口

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();
}

这个接口规定了缓存类需要有的功能,缓存实现类PerpetualCache以及装饰器decorators都实现了这个接口,这个就规定了它们的类型都是一个Cache。前面说过 ***PerpetualCache***底层是由一个HashMap实现的,我们来看它的代码

public class PerpetualCache implements Cache {
  private final String id;
  private Map cache = new HashMap<>();//存储缓存数据的容器

  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();
  }

}

它的功能比较单一,但是用于二级缓存也足够了。于是mybatis在此基础上,拓展了很多功能,都是装饰器实现,我们随便看一个org.apache.ibatis.cache.decorators.LoggingCache,这个装饰器是记录缓存命中次数并打印日志。

public class LoggingCache implements Cache {

  private final Log log;
  private final Cache delegate;//被装饰的对象,也是执行方法的代理
  protected int requests = 0;
  protected int hits = 0;

  public LoggingCache(Cache delegate) {
    this.delegate = delegate;
    this.log = LogFactory.getLog(getId());
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

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

  @Override
  public void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }

  @Override
  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;
  }

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

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

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

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

  private double getHitRatio() {
    return (double) hits / (double) requests;
  }

}

可以看到,每个装饰器都持有一个 delegate ,它就是 Cache,也就是被装饰的对象,在装饰器的构造方法里传入。装饰器有多少个都可以,被装饰过的对象就可以作为 delegate 再由下一个装饰器使用。那么mybatis是怎样构造这些装饰器的呢?接下来就看builder模式


builder 模式是一种对象构建模式,它跟工厂模式有些类似,工厂模式是提供一个工厂方法用于构造对象,而不需要用户手动在调用构造方法,而 builder 模式也是如此,但是 builder 模式的另一种变形是使用一个静态内部类来提供构造对象,这种方式可以完全对外部无影响地为对象增加builder模式的使用,在实际开发中用得比较多的,下面两种方式都会作介绍。
要给 PerpetualCache 加装饰器,就会有很多属性需要我们设置,一会可以从源码中看到,设置属性我们一般用set方法,而加装饰器的话,肯定要创建对象,前面说过,被装饰的对象是在装饰器的构造方法中设置进去的,使用传统方法就无可避免地要new很多对象,并且拓展性很差,后期如果需要再加一个装饰器的话,就要改动很多代码,于是这个时候builder模式就派上用场了。mybatis用一个***CacheBuilder***类完成以上工作(注意看注释):

public class CacheBuilder {
  private final String id;
  private Class implementation;
  //所有的装饰器存放在一个数组中
  private final List> decorators;
  private Integer size;
  private Long clearInterval;
  private boolean readWrite;
  private Properties properties;
  private boolean blocking;
  
  // CacheBuilder的构造方法,id是提前指定的
  public CacheBuilder(String id) {
    this.id = id;
    this.decorators = new ArrayList<>();
  }

  public CacheBuilder implementation(Class implementation) {
    this.implementation = implementation;
    return this;
  }
 // 为缓存实现类增加装饰器,最后返回this,这样就可以使用链式编程,它也是
 // builder模式的核心思想,在程序中体现就是
 // new CacheBuilder(id).addDecorator(decorator1).addDecorator(decorator2)........
 // 加入了这些装饰器,在后面构造对象的时候就可以加入进去了
  public CacheBuilder addDecorator(Class decorator) {
    if (decorator != null) {
      this.decorators.add(decorator);
    }
    return this;
  }
 // builder模式给属性设置值的方式,不需要使用getter方法,并且返回this,方便链式编程
  public CacheBuilder size(Integer size) {
    this.size = size;
    return this;
  }

  public CacheBuilder clearInterval(Long clearInterval) {
    this.clearInterval = clearInterval;
    return this;
  }

  public CacheBuilder readWrite(boolean readWrite) {
    this.readWrite = readWrite;
    return this;
  }

  public CacheBuilder blocking(boolean blocking) {
    this.blocking = blocking;
    return this;
  }
  
  public CacheBuilder properties(Properties properties) {
    this.properties = properties;
    return this;
  }
  // builder模式的最后一个方法,它会返回客户需要的对象。在这个方法中,将之前设置的属性全部加
  // 入到对象中,同时也可进行属性校验。
  public Cache build() {
    setDefaultImplementations();
    Cache cache = newBaseCacheInstance(implementation, id);
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    if (PerpetualCache.class.equals(cache.getClass())) {
     // 将前面加入的装饰器装饰到对象上。
      for (Class decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }

  private void setDefaultImplementations() {
    if (implementation == null) {
      implementation = PerpetualCache.class;
      if (decorators.isEmpty()) {
        decorators.add(LruCache.class);
      }
    }
  }

  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);
    }
  }

  private void setCacheProperties(Cache cache) {
    if (properties != null) {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      for (Map.Entry entry : properties.entrySet()) {
        String name = (String) entry.getKey();
        String value = (String) entry.getValue();
        if (metaCache.hasSetter(name)) {
          Class type = metaCache.getSetterType(name);
          if (String.class == type) {
            metaCache.setValue(name, value);
          } else if (int.class == type
              || Integer.class == type) {
            metaCache.setValue(name, Integer.valueOf(value));
          } else if (long.class == type
              || Long.class == type) {
            metaCache.setValue(name, Long.valueOf(value));
          } else if (short.class == type
              || Short.class == type) {
            metaCache.setValue(name, Short.valueOf(value));
          } else if (byte.class == type
              || Byte.class == type) {
            metaCache.setValue(name, Byte.valueOf(value));
          } else if (float.class == type
              || Float.class == type) {
            metaCache.setValue(name, Float.valueOf(value));
          } else if (boolean.class == type
              || Boolean.class == type) {
            metaCache.setValue(name, Boolean.valueOf(value));
          } else if (double.class == type
              || Double.class == type) {
            metaCache.setValue(name, Double.valueOf(value));
          } else {
            throw new CacheException("Unsupported property type for cache: '" + name + "' of type " + type);
          }
        }
      }
    }
    if (InitializingObject.class.isAssignableFrom(cache.getClass())){
      try {
        ((InitializingObject) cache).initialize();
      } catch (Exception e) {
        throw new CacheException("Failed cache initialization for '" +
            cache.getId() + "' on '" + cache.getClass().getName() + "'", e);
      }
    }
  }

  private Cache newBaseCacheInstance(Class cacheClass, String id) {
    Constructor cacheConstructor = getBaseCacheConstructor(cacheClass);
    try {
      return cacheConstructor.newInstance(id);
    } catch (Exception e) {
      throw new CacheException("Could not instantiate cache implementation (" + cacheClass + "). Cause: " + e, e);
    }
  }

  private Constructor getBaseCacheConstructor(Class cacheClass) {
    try {
      return cacheClass.getConstructor(String.class);
    } catch (Exception e) {
      throw new CacheException("Invalid base cache implementation (" + cacheClass + ").  " +
          "Base cache implementations must have a constructor that takes a String id as a parameter.  Cause: " + e, e);
    }
  }

  private Cache newCacheDecoratorInstance(Class cacheClass, Cache base) {
    Constructor cacheConstructor = getCacheDecoratorConstructor(cacheClass);
    try {
      return cacheConstructor.newInstance(base);
    } catch (Exception e) {
      throw new CacheException("Could not instantiate cache decorator (" + cacheClass + "). Cause: " + e, e);
    }
  }

  private Constructor getCacheDecoratorConstructor(Class cacheClass) {
    try {
      return cacheClass.getConstructor(Cache.class);
    } catch (Exception e) {
      throw new CacheException("Invalid cache decorator (" + cacheClass + ").  " +
          "Cache decorators must have a constructor that takes a Cache instance as a parameter.  Cause: " + e, e);
    }
  }
}

使用 CacheBuilder 创建对象,在程序中就是这样:

new CacheBuilder(id)
		.addDecorator(decorator1)
		.addDecorator(decorator2)
		.size(size)
		.clearInterval(clearInterval)
		.readWrite(false)
		.blocking(true)
		.build();

当然设置这些属性是没有先后顺序的,通过这些操作,可以创建我们想要的对象,它的属性设置好了,装饰器也都装饰上了。

我们可以在开发过程中模仿mybatis的这种方式创建对象,新建一个XXXBuilder类来完成对象的创建工作,灵活控。然而,在工作中用的更多的是builder的另一种变形。举个例子:

public class Bean1 {
    private String field1;
    
    private int field2;
    
    private boolean field3;

    public Bean1() {
    }

    public Bean1(String field1) {
        this.field1 = field1;
    }

    public Bean1(String field1, int field2) {
        this(field1);
        this.field2 = field2;
    }

    public Bean1(String field1, int field2, boolean field3) {
        this(field1,field2);
        this.field3 = field3;
    }

    public String getField1() {
        return field1;
    }

    public void setField1(String field1) {
        this.field1 = field1;
    }

    public int getField2() {
        return field2;
    }

    public void setField2(int field2) {
        this.field2 = field2;
    }

    public boolean isField3() {
        return field3;
    }

    public void setField3(boolean field3) {
        this.field3 = field3;
    }
    
}

我们在工作中应该经常会写如上的代码,也就是一个javabean,以上我只是简单地例出了三个属性,当然在实际生产中,肯定不只三个属性,如果有很多个属性,构造方法就令人沮丧的了。所以这个时候,builder模式又派上用场了。 我们在一个javabean内部创建一个静态内部类buider,用于创建javabean的工作,而不需要显示地调用构造方法,这时不改动原先的任何代码,符合“对修改关闭,对拓展开放”的开闭原则,改进如下:

public class Bean1 {
    private String field1;

    private int field2;

    private boolean field3;

    public Bean1() {

    }

    public Bean1(String field1) {
        this.field1 = field1;
    }

    public Bean1(String field1, int field2) {
        this(field1);
        this.field2 = field2;
    }

    public Bean1(String field1, int field2, boolean field3) {
        this(field1,field2);
        this.field3 = field3;
    }

    public String getField1() {
        return field1;
    }

    public void setField1(String field1) {
        this.field1 = field1;
    }

    public int getField2() {
        return field2;
    }

    public void setField2(int field2) {
        this.field2 = field2;
    }

    public boolean isField3() {
        return field3;
    }

    public void setField3(boolean field3) {
        this.field3 = field3;
    }
    
    /*-------------------------- 下面是新加代码,使用builder模式 ------------------*/
    public static class builder {
        private Bean1 bean = new Bean1();

        public builder field1(String field1) {
            bean.setField1(field1);
            return this;
        }
        
        public builder field2(int field2) {
            bean.setField2(field2);
            return this;
        }

        public builder field3(boolean field3) {
            bean.setField3(field3);
            return this;
        }

        public Bean1 build() {
            // 可以在此处加入一些必要的校验
            if (bean.field1 == null || bean.field2 < 0)
                throw new IllegalArgumentException("参数设置有误");
            return bean;
        }
    }

}

在代码中加入builder这个内部类,自然不影响之前发布的代码,而现在创建对象就是这个样子Bean1 aaa = new builder().field1("aaa").field2(2).field3(true).build();有没有觉得这种方式非常巧妙。现在你可以考虑在你的javabean中加入builder模式了。

你可能感兴趣的:(学习设计模式,MyBatis源码学习)