我们说了职责链加上动态代理实现的MyBatis Plugin,现在我们对MyBatis用到设计模式做一个总结,其用到的设计模式不少,不下十几种,简单的学习下

SqlSessionFactoryBuilder,为什么要用建造者来创建SqlSessionFactory?

public class MyBatisDemo {

  public static void main(String[] args) throws IOException {

    Reader reader = Resources.getResourceAsReader(“mybatis.xml”);

    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);

    SqlSession session = sessionFactory.openSession();

    UserMapper userMapper = session.getMapper(UserMapper.class);

    UserDo userDo = userMapper.selectById(8);

    //…

  }

}

上面中,使用SqlSessionFactoryBuilder来创建对象,但是往常的Builder都是级联一组setXXX()来设置属性,最后掉用build来创建最后的对象,但是,在上面的代码中,使用SqlSessionFactoryBuiler来创建SqlSessionFactory中,我们的build需要直接传入参数,那么,为什么还要使用SqlSessionFatroyBuilder呢,直接使用构造函数不行吗?

我们接下来看了下SqlSessionFactoryBuilder

public class SqlSessionFactoryBuilder {

  public SqlSessionFactory build(Reader reader) {

    return build(reader, null, null);

  }

  public SqlSessionFactory build(Reader reader, String environment) {

    return build(reader, environment, null);

  }

  public SqlSessionFactory build(Reader reader, Properties properties) {

    return build(reader, null, properties);

  }

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {

    try {

      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);

      return build(parser.parse());

    } catch (Exception e) {

      throw ExceptionFactory.wrapException(“Error building SqlSession.”, e);

    } finally {

      ErrorContext.instance().reset();

      try {

        reader.close();

      } catch (IOException e) {

        // Intentionally ignore. Prefer previous error.

      }

    }

  }

  public SqlSessionFactory build(InputStream inputStream) {

    return build(inputStream, null, null);

  }

  public SqlSessionFactory build(InputStream inputStream, String environment) {

    return build(inputStream, environment, null);

  }

  public SqlSessionFactory build(InputStream inputStream, Properties properties) {

    return build(inputStream, null, properties);

  }

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {

    try {

      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

      return build(parser.parse());

    } catch (Exception e) {

      throw ExceptionFactory.wrapException(“Error building SqlSession.”, e);

    } finally {

      ErrorContext.instance().reset();

      try {

        inputStream.close();

      } catch (IOException e) {

        // Intentionally ignore. Prefer previous error.

      }

    }

  }

    

  public SqlSessionFactory build(Configuration config) {

    return new DefaultSqlSessionFactory(config);

  }

}

在其中有着大量重载函数,我们之前说过,如果有多重重载的构造函数,同时为了避免构造函数过多,参数列表过长,我们一般使用构造者模式来解决的

但是,MyBatis中的建造者模式并不是标准的建造者模式,而且其内部也定义了足够多的构造函数,这个设计的初衷是为了简化开发,最后也足够的简化了开发工作

SqlSessionFactory

SqlSessionFactoryBuilder创建了SqlSessionFactory,并且利用这个工厂类创建了对应的SqlSession,在这个类上,看起来是融合了工厂模式,但是这并不是一个合格的工厂模式

首先是对应的源码

public class DefaultSqlSessionFactory implements SqlSessionFactory {

  private final Configuration configuration;

  public DefaultSqlSessionFactory(Configuration configuration) {

    this.configuration = configuration;

  }

  @Override

  public SqlSession openSession() {

    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);

  }

  @Override

  public SqlSession openSession(boolean autoCommit) {

    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);

  }

  @Override

  public SqlSession openSession(ExecutorType execType) {

    return openSessionFromDataSource(execType, null, false);

  }

  @Override

  public SqlSession openSession(TransactionIsolationLevel level) {

    return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);

  }

  @Override

  public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {

    return openSessionFromDataSource(execType, level, false);

  }

  @Override

  public SqlSession openSession(ExecutorType execType, boolean autoCommit) {

    return openSessionFromDataSource(execType, null, autoCommit);

  }

  @Override

  public SqlSession openSession(Connection connection) {

    return openSessionFromConnection(configuration.getDefaultExecutorType(), connection);

  }

  @Override

  public SqlSession openSession(ExecutorType execType, Connection connection) {

    return openSessionFromConnection(execType, connection);

  }

  @Override

  public Configuration getConfiguration() {

    return configuration;

  }

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {

    Transaction tx = null;

    try {

      final Environment environment = configuration.getEnvironment();

      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);

      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

      final Executor executor = configuration.newExecutor(tx, execType);

      return new DefaultSqlSession(configuration, executor, autoCommit);

    } catch (Exception e) {

      closeTransaction(tx); // may have fetched a connection so lets call close()

      throw ExceptionFactory.wrapException(“Error opening session.  Cause: ” + e, e);

    } finally {

      ErrorContext.instance().reset();

    }

  }

  private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {

    try {

      boolean autoCommit;

      try {

        autoCommit = connection.getAutoCommit();

      } catch (SQLException e) {

        // Failover to true, as most poor drivers

        // or databases won’t support transactions

        autoCommit = true;

      }      

      final Environment environment = configuration.getEnvironment();

      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);

      final Transaction tx = transactionFactory.newTransaction(connection);

      final Executor executor = configuration.newExecutor(tx, execType);

      return new DefaultSqlSession(configuration, executor, autoCommit);

    } catch (Exception e) {

      throw ExceptionFactory.wrapException(“Error opening session.  Cause: ” + e, e);

    } finally {

      ErrorContext.instance().reset();

    }

  }

  //…省略部分代码…

}

从SqlSessionFactory和DefaultSqlSessionFatory两者的源码来看,它的设计非常类似SqlSessionFactoryBuilder,通过重载了多个openSession()函数,,来组合创建SqlSession的对象,只是对象的属性不同,于是,可以认为比起工厂模式,更加像是建造者模式

这两者一个建造者,一个工厂,但都不是严格意义上的工厂类,而是一种类似的设计思路,我们可以参考这种设计思路,将其利用容器进行统一化管理

BaseExecutor:模板模式的使用

我们看出,Executor是一个接口,而具体的执行者BatchExecutor并没有直接实现这个接口,而是先由一个BaseExecutor抽象化实现了,然后具体的执行者去继承这个BaseExecutor

在BseExecutor中,对于Executor的接口进行了简单的实现,称为默认方法,但是在默认方法中,会调用抽象方法doXXX方法().从而实现了模板模式

public abstract class BaseExecutor implements Executor {

  //…省略其他无关代码…

  

  @Override

  public int update(MappedStatement ms, Object parameter) throws SQLException {

    ErrorContext.instance().resource(ms.getResource()).activity(“executing an update”).object(ms.getId());

    if (closed) {

      throw new ExecutorException(“Executor was closed.”);

    }

    clearLocalCache();

    return doUpdate(ms, parameter);

  }

  public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {

    if (closed) {

      throw new ExecutorException(“Executor was closed.”);

    }

    return doFlushStatements(isRollBack);

  }

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {

    List<E> list;

    localCache.putObject(key, EXECUTION_PLACEHOLDER);

    try {

      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

    } finally {

      localCache.removeObject(key);

    }

    localCache.putObject(key, list);

    if (ms.getStatementType() == StatementType.CALLABLE) {

      localOutputParameterCache.putObject(key, parameter);

    }

    return list;

  }

  @Override

  public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {

    BoundSql boundSql = ms.getBoundSql(parameter);

    return doQueryCursor(ms, parameter, rowBounds, boundSql);

  }

  protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

  protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;

  protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException;

}

SqlNode:利用解释器来解析动态SQL

MyBatis一个重要特性就是动态SQL,MyBatis中的动态sql就是利用判断你的条件来动态的增加Sql条件

<update id=”update” parameterType=”com.xzg.cd.a89.User”

   UPDATE user

   <trim prefix=”SET” prefixOverrides=”,”>

       <if test=”name != null and name != ””>

           name = #{name}

       </if>

       <if test=”age != null and age != ””>

           , age = #{age}

       </if>

       <if test=”birthday != null and birthday != ””>

           , birthday = #{birthday}

       </if>

   </trim>

   where id = ${id}

</update>

根据mybatis给出的sqEL来动态的,生成真正执行的SQL语句,这就是解释器模式

解释器模式在解释语法的时候,会把规则划分为最小的单元,然后对应的每个单元进行解析,然后拼接在一起,mybatis会把每个语法的小单元叫做sqlNode,sqlNode的定义如下

public interface SqlNode {

 boolean apply(DynamicContext context);

}

图片

整个解释器的入口在DynamicSqlSource的getBoundSql,其中的SqlNode是一个MixedSqlNode,整体调用结构和SqlNode的类似

ErrorContext:线程内唯一的单例模式

单例模式是进程唯一的,同时可以有线程唯一的,ErrorContext就是标准的线程唯一的单例.利用了ThreadLocal来实现

public class ErrorContext {

  private static final String LINE_SEPARATOR = System.getProperty(“line.separator”,”\n”);

  private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();

  private ErrorContext stored;

  private String resource;

  private String activity;

  private String object;

  private String message;

  private String sql;

  private Throwable cause;

  private ErrorContext() {

  }

  public static ErrorContext instance() {

    ErrorContext context = LOCAL.get();

    if (context == null) {

      context = new ErrorContext();

      LOCAL.set(context);

    }

    return context;

  }

}

Cache,使用了装饰器模式

Mybatis是一个ORM框架,其中还提供了很多诸如Cache的实现

在Mybatis中,缓存功能由接口Cache定义,PerpetualCache是基础的缓存类,在此基础上,Mybatis还设计了其他的方式,实现了功能增强,分别是FifoCache,LoggingCache,LruCache,ScheduledCache,SerializedCache,SoftCache,SynchronizedCache,WeakCache,TransactionalCache

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

}

public class PerpetualCache implements Cache {

  private final 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;

  }

  //省略部分代码…

}

里面是一个标准的装饰器类,标准的装饰器的实现

其中,我们拿出LruCache的源码,来仔细看下装饰器类的实现方式

public class LruCache implements Cache {

  private final Cache delegate;

  private Map<Object, Object> keyMap;

  private Object eldestKey;

  public LruCache(Cache delegate) {

    this.delegate = delegate;

    setSize(1024);

  }

  @Override

  public String getId() {

    return delegate.getId();

  }

  @Override

  public int getSize() {

    return delegate.getSize();

  }

  public void setSize(final int size) {

    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {

      private static final long serialVersionUID = 4267176411845948333L;

      @Override

      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {

        boolean tooBig = size() > size;

        if (tooBig) {

          eldestKey = eldest.getKey();

        }

        return tooBig;

      }

    };

  }

  @Override

  public void putObject(Object key, Object value) {

    delegate.putObject(key, value);

    cycleKeyList(key);

  }

  @Override

  public Object getObject(Object key) {

    keyMap.get(key); //touch

    return delegate.getObject(key);

  }

  @Override

  public Object removeObject(Object key) {

    return delegate.removeObject(key);

  }

  @Override

  public void clear() {

    delegate.clear();

    keyMap.clear();

  }

  @Override

  public ReadWriteLock getReadWriteLock() {

    return null;

  }

  private void cycleKeyList(Object key) {

    keyMap.put(key, key);

    if (eldestKey != null) {

      delegate.removeObject(eldestKey);

      eldestKey = null;

    }

  }

}

之所以Mybatis使用了装饰器模式实现缓存,是因为装饰器模式基于组合,而非继承,更加的灵活,有效的避免继承关系的组合爆炸

PropertyTokenizer:迭代器实现的属性解析器

我们说的,迭代器模式常用语替代For来循环遍历集合的元素,Mybatis的propertyTokenizer实现了Java的iterator接口,是一个迭代器,用来对配置属性来进行解析

// person[0].birthdate.year 会被分解为3个PropertyTokenizer对象。其中,第一个PropertyTokenizer对象的各个属性值如注释所示。?

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {

  private String name; // person

  private final String indexedName; // person[0]

  private String index; // 0

  private final String children; // birthdate.year

  public PropertyTokenizer(String fullname) {

    int delim = fullname.indexOf(‘.’);

    if (delim > -1) {

      name = fullname.substring(0, delim);

      children = fullname.substring(delim + 1);

    } else {

      name = fullname;

      children = null;

    }

    indexedName = name;

    delim = name.indexOf(‘[‘);

    if (delim > -1) {

      index = name.substring(delim + 1, name.length() – 1);

      name = name.substring(0, delim);

    }

  }

  public String getName() {

    return name;

  }

  public String getIndex() {

    return index;

  }

  public String getIndexedName() {

    return indexedName;

  }

  public String getChildren() {

    return children;

  }

  @Override

  public boolean hasNext() {

    return children != null;

  }

  @Override

  public PropertyTokenizer next() {

    return new PropertyTokenizer(children);

  }

  @Override

  public void remove() {

    throw new UnsupportedOperationException(“Remove is not supported, as it has no meaning in the context of properties.”);

  }

}

将配置的解析,解析后的元素,迭代器,这三个部分放在三个类的代码进行了集合,集合在了一个类中,这样可以惰性解析,只有我们调用next()函数的时候,才会解析部分配置

Log:使用适配器模式来适配不同的日志框架

Slf4j框架为了统一不同的日志框架,提供了统一的日志接口,不过,Mybatis并没有提供使用Slf4j提供统一的日志规范,而是,自己重复造了轮子,定义了同一的访问接口

public interface Log {

  boolean isDebugEnabled();

  boolean isTraceEnabled();

  void error(String s, Throwable e);

  void error(String s);

  void debug(String s);

  void trace(String s);

  void warn(String s);

}

针对Log接口,Mybatis提供了不同的实现类,对不同的日志框架实现了Log接口

图片

在适配器模式中,传递给适配器构造函数的是被适配的类的对象,然后在这里进行了二次封装,进行了重新的适配作用

本章重点

我们说了Mybatis还涉及的8种设计模式,分别是建造者模式,工厂模式,模板模式,解释器模式,单例模式,装饰器模式,迭代器模式,适配器模式

我们需要对这些涉及的模式好好的看一下,钻研一下

课后讨论

我们说,SqlSessionFactoryBuildler和SqlSessionFacotry都是为了隐藏SqlSession的创建细节而存在的,不过都不是标准的模板实现

设计思想比设计模式更重要,只要符合其设计的本意,没什么大不了的

发表评论

邮箱地址不会被公开。 必填项已用*标注