MyBatis与设计模式的激情碰撞

MyBatis与设计模式的激情碰撞

最近一直在研究MyBatis的源码,MyBatis做为国内最为常用的持久层框架,其内部代码的设计也是极其优秀的!咱们学习源码的目的是什么呢?web

  • 一方面是对该框架有一个很深刻的认识,以便在开发过程当中有能力对框架进行深度的定制化开发或者在解决BUG的时候更加驾轻就熟!
  • 一方面是学习代码里面优秀的设计,看看这些成名多年的框架,他们的开发者是如何设计出一个高扩展性、低耦合性的代码呢?而后在本身的开发场景中应用。

今天咱们就来讨论一下,在MyBatis内部,为了提升代码的可读性究竟作了哪些设计呢?固然,若是你对MyBatis的代码特别熟悉,做者在文中有错误的地方欢迎指出来,由于做者尚未完整的通读MyBatis的源码,大概看了70%左右,后续看完以后,做者会考虑出一期关于MyBatis源码的解读,一方面是增强做者对于MyBatis源码的理解,一方面是让你们更好的学习MyBatis,话很少说,进入正题吧!sql

1、外观模式

外观模式,有些开发者也会把它叫作门面模式,他多用于接口的设计防,面,目的是封装系统的底层实现,隐藏系统的复杂性,提供一组更加简单易用、更高层的接口。咱们将多个接口的Api替换为一个接口,以减小程序调用的复杂性,增长程序的易用性!数据库

咱们先看一段代码:apache

@Test
public void SqlSessionTest(){  //构建会话对象  SqlSession sqlSession = sqlSessionFactory.openSession();  UserMapper mapper = sqlSession.getMapper(UserMapper.class);  System.out.println(mapper.findUserByName("张三")); } 复制代码

熟悉Mybatis代码的同窗应该对这个代码无比熟悉,利用会话工厂构建会话对象SqlSession,基于会话对象调用Mapper方法,可是凭什么咱们只须要构建一个SqlSession对象就可以彻底操做我们的MyBatis呢?这里MyBatis的开发者使用了外观设计模式,将全部的操做Api都封装进了SqlSession内部,让使用者无需关心内部的底层实现就可以使用是否是很完美,那么内部他是如何操做的呢?因为本章内容的目的并非为了分析源码,因此咱们只须要知道如何实现的就行!咱们进入到SqlSession内部设计模式

public class DefaultSqlSession implements SqlSession {
 //忽略没必要要代码  private final Executor executor;  //忽略没必要要代码  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {  //...  this.executor = executor;  //...  } } 复制代码

咱们能够里看到SqlSession内部封装了一个Executor对象,也就是MyBatis的执行器,而后经过构造方法传递过来!后续全部的查询逻辑都是调用Executor内的方法来完成的实现,而SqlSession自己不作任何操做,因此就能仅仅经过一个对象,来构建起整个Mybatis框架的使用!缓存

2、装饰者模式

装饰者模式:动态地给一个对象增长一些额外的职责,增长对象功能来讲,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。装饰者设计模式的目的是为了给某一些没有办法或者不方便变更的方法动态的增长一些额外的功能!多线程

鸟语说完了,转换成大白话就是,有些类没有办法常常改代码,可是有要求他在不一样的场景下展现不一样的功能,又想要女友,又想和其余美女撩骚!典型的渣男,可是装饰者模式真正为这一操做提供了可能!好比将美女装饰成本身的姐姐妹妹,阿姨大妈,那不就能痛快的撩骚了!开个玩笑,我对我家的绝对忠贞不二!那么MyBatis是如何使用这一设计模式呢?并发

众所周知,MyBatis存在二级缓存,可是咱们有时候须要二级缓存,有时候又不须要,这个时候怎么办呢?所以MyBatis单独抽象出来了一个Executor的实现类CachingExecutor专门来作缓存相关的操做,它自己不作任何的查询逻辑,只实现本身的混村逻辑,从而能够动态的插拔MyBatis的缓存逻辑!具体的实现思路以下:app

public class CachingExecutor implements Executor {
  private final Executor delegate;  //.....忽略多余代码   public CachingExecutor(Executor delegate) {  this.delegate = delegate;  //.....忽略多余代码  }   //咱们以二级缓存下的查询为例  @Override  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, boundSql);  @SuppressWarnings("unchecked")  //查询缓存是否存在  List<E> list = (List<E>) tcm.getObject(cache, key);  if (list == null) {  //不存在就调用其余执行器的 query 方法  list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);  //将查出来的对象放置到缓存中  tcm.putObject(cache, key, list); // issue #578 and #116  }  return list;  }  }  //调用其余执行器的 query 方法  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);  }  //.....忽略多余代码 } 复制代码

咱们能够看到,CachingExecutor经过构造方法传入一个真正的执行器,也就是一个真正可以查询的执行器,而后处理完缓存操做后,调用可以真正执行查询的执行器进行数据的查询,待数据查询到以后,再将数据放置到缓存内部,从而完成整个缓存的逻辑!这就是装饰者模式!框架

3、责任链模式

责任链设计模式:责任链模式(Chain of Responsibility)是多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有对象可以处理。

可是接下来介绍的这种事实上并无彻底遵照以上的概念,获取咱们能够将这种设计模式叫作 责任链的变种:功能链,它的设计思想是分而治之,符合七大设计原则的 迪米特法则 合成复用原则 单一职责原则 ,它经过实现组合一种功能实现,链条上的每个节点都可以处理一部分特有的操做,一直向下传递,最终完成整个操做!

咱们仍是基于MyBatis的二级缓存来讲话,先看一张图:

图片来源于源码阅读网http://www.coderead.cn/
图片来源于源码阅读网http://www.coderead.cn/

若是不懂责任链设计模式,就会懵逼,仅仅是一个缓存而已,弄这么多getObject()干吗?事实上即便咱们进入到源码中也会发现好多相似这样的逻辑:

咱们先获取一个缓存对象,而后设置缓存:

@Test
public void CacheTest (){  Cache cache = configuration.getCache(UserMapper.class.getName());  cache.putObject("666","你好");  cache.getObject("666"); } 复制代码

按照常规理解,他应该会把这个k-v值放置到 Map中或者执行一些逻辑操做在放到Map中,可是咱们却发现下面这一段:

org.apache.ibatis.cache.decorators.SynchronizedCache#putObject

@Override
public synchronized void putObject(Object key, Object object) {  delegate.putObject(key, object); } 复制代码

你心态崩不崩,行继续往下跟:

org.apache.ibatis.cache.decorators.LoggingCache#putObject

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

你又会发现这样一段逻辑,继续往下也同样,那么MyBatis为何会搞这么多空方法呢?显得代码牛逼?固然不是,他这么设计确定是有必定的用意的,什么用意呢?

事实上咱们发现org.apache.ibatis.cache.decorators.SynchronizedCache#putObject这个方法上增长了synchronized属性,他是为了解决多线程的并发问题的,org.apache.ibatis.cache.decorators.LoggingCache#putObject这个方法自己没作什么,可是咱们看getObject方法:

@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; } 复制代码

这个类是为了统计二级缓存的命中率的,诸如此类,往下还有org.apache.ibatis.cache.decorators.SerializedCache#putObject作二级缓存序列化的、org.apache.ibatis.cache.decorators.LruCache#putObject最少使用缓存淘汰策略的以及org.apache.ibatis.cache.impl.PerpetualCache#putObject真正的缓存方法,这是一个功能链条,其实这个例子与使用了必定的装饰模式,经过构造函数:

public SynchronizedCache(Cache delegate) {
 this.delegate = delegate; } 复制代码

设置本次处理完成后的下一个处理节点,从而完成整个链条的调用,那么在哪里构建链条的呢?咱们看一段代码,这里因为篇幅缘由,做者不作太多的讲解,你们看一下就行:

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);  } } 复制代码

能够看到,以上代码经过各类条件的判断往里面放置调用链节点,从而构建出一整个链条,可是事实上,Mybatis中对链条的构建远不止那么简单,这个咱们之后再议!

4、动态代理模式

代理模式:为其它对象提供一种代理以控制对这个对象的访问。当没法或不想直接访问某个对象存在困难时能够经过一个代理对象来间接访问,为了保证客户端使用的透明性,委托对象与代理对象须要实现相同的接口。

MyBatis中是在哪里使用的动态代理的设计模式呢?众所周知,咱们在使用MyBatis的时候,只须要将对应的Dao层抽象出一个接口,后续的调用逻辑就可以完整的调用数据库实现各类逻辑,可是你是否疑惑过,MyBatis的Mapper咱们明明没有设置实现类啊,他是如何操做数据库的呢?这里就使用了动态代理设计模式!

咱们先看一段代码:

@Test
public void SqlSessionTest(){  //构建会话对象  SqlSession sqlSession = sqlSessionFactory.openSession();  UserMapper mapper = sqlSession.getMapper(UserMapper.class);  System.out.println(mapper.findUserByName("张三")); } 复制代码

UserMapper对象是一个接口,只须要将他交给Mybatis就可以本身完成对应的逻辑,咱们经过断点一步步跟下去,会发现这样一段逻辑:

protected T newInstance(MapperProxy<T> mapperProxy) {
 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } 复制代码

看到这里,熟悉jdk动态代理的同窗可能会恍然大悟,原来它使用的是动态代理来实现的对应实现类,mapperInterface.getClassLoader()是类加载器,mapperInterface是要代理的接口,mapperProxy是真正的实现操做,他是InvocationHandler的子类,目的就是完成代理类的自定义的代码操做!它事实上会构建这么个东西:

public class MapperProxy<T> implements InvocationHandler, Serializable {  
 //........  @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  try {  if (Object.class.equals(method.getDeclaringClass())) {  return method.invoke(this, args);  } else {  return cachedInvoker(method).invoke(proxy, method, args, sqlSession);  }  } catch (Throwable t) {  throw ExceptionUtil.unwrapThrowable(t);  }  }  //........ } 复制代码

最终会调用mapperMethod.execute(sqlSession, args)方法来构建与底层数据库的交互操做,在使用中,你获取的事实上不是接口的实现类,而是接口的代理对象,由生成的代理对象,完成了后续的全部操做!

5、总结

本篇文章事实上不少的代码细节都是一律而过,并无深刻讲解,固然这也不是我写本篇文章的一个目的,本篇文章的目的仅仅是想要让使用者可以了解一些MyBatis的大体细节,从而对MyBatis有一个总体的认知,方便再本身调试源码的时候,不至于那么懵逼!


才疏学浅,若是文章中理解有误,欢迎大佬们私聊指正!欢迎关注做者的公众号,一块儿进步,一块儿学习!

相关文章
相关标签/搜索