天天看點

MyBatis架構中的設計模式

前言:

    前幾篇文章從源碼角度分析了Mybatis架構,當然,作為一個優秀的架構,設計模式的使用也是必不可少的。

    本文,作者便簡單介紹下Mybatis設計模式的使用場景

    關于設計模式的相關知識,讀者可自行檢視網上相關資料。推薦下菜鳥教程 http://www.runoob.com/design-pattern/design-pattern-tutorial.html  

1.工廠模式

    網上好多說SqlSessionFactory是工廠模式,但是感覺跟工廠模式還是差了點。個人更覺得以下的模式更像

    Configuration.newExecutor()

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
           

總結:根據使用者輸入的executorType來生成具體的Executor,符合工廠模式

2.單例模式

    單例模式是大家都很熟悉的設計模式

public final class LogFactory {
    ...
  private LogFactory() {
    // disable construction
  }
    
    public static Log getLog(Class<?> aClass) {
    return getLog(aClass.getName());
  }  
           

總結:跟經典的單例模式還是有點差别,LogFactory沒有實作擷取自身的方式,隻是當成一個工具類來用

3.建造模式

    我們先來看下下面一段優雅的代碼

    XMLConfigBuilder.environmentsElement()

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        if (isSpecifiedEnvironment(id)) {
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
            
          // 看這裡
          // Builder是Environment裡的static class ,
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          // environmentBuilder.build()方法直接傳回一個Environment
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }
           

下面讓我們來看下Environment是如何建構的

public final class Environment {
  private final String id;
  private final TransactionFactory transactionFactory;
  private final DataSource dataSource;

  ...

  public static class Builder {
      private String id;
      private TransactionFactory transactionFactory;
      private DataSource dataSource;

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

    public Builder transactionFactory(TransactionFactory transactionFactory) {
      this.transactionFactory = transactionFactory;
      return this;
    }

    public Builder dataSource(DataSource dataSource) {
      this.dataSource = dataSource;
      return this;
    }

    public Environment build() {
      return new Environment(this.id, this.transactionFactory, this.dataSource);
    }

  }
...
           

總結:我們在實際的代碼中可以用這種方式來構造一個對象,代碼看起來會非常優雅

4.裝飾器模式

    類似于JDK中的InputStream相關實作類,通過裝飾器模式可以動态增加輸入流的功能

    org.apache.ibatis.cache.Cache有衆多實作類,我們看一下org.apache.ibatis.cache.decorators.TransactionalCache

public class TransactionalCache implements Cache {

  private Cache delegate;//重點在這裡
  private boolean clearOnCommit;
  private Map<Object, AddEntry> entriesToAddOnCommit;
  private Map<Object, RemoveEntry> entriesToRemoveOnCommit;
    
    @Override
  public Object getObject(Object key) {
    if (clearOnCommit) return null; // issue #146
    return delegate.getObject(key);
  }
    
  public void commit() {
    if (clearOnCommit) {
      delegate.clear();
    } else {
      for (RemoveEntry entry : entriesToRemoveOnCommit.values()) {
        entry.commit();
      }
    }
    for (AddEntry entry : entriesToAddOnCommit.values()) {
      entry.commit();
    }
    reset();
  }
  ...
           

總結:通過對delegate屬性的代理,在實作delegate自有功能之前,增加自己的需求

5.模闆模式

    模闆模式應該是使用的比較廣泛的一個模式,我們在父類中定義算法骨架,将具體實作交由子類去實作

    Mybatis中org.apache.ibatis.executor.BaseExecutor就是一個标準的模闆模式

public abstract class BaseExecutor implements Executor {
    
  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);
  }
    
   protected abstract int doUpdate(MappedStatement ms, Object parameter)
      throws SQLException;
    
   ...
           

    比如update()方法,定義了update的骨架方法,真正的執行doUpdate()則由子類(如SimpleExecutor)去實作

// SimpleExecutor.doUpdate
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }
           

總結:我們在寫一個多種實作方式的方法時,可參考該模式

6.動态代理模式

    動态代理可以說是Mybatis中最重要的設計模式了,其使用了動态代理優雅的實作了很多功能

    我們分析下下面一句代碼:

IUser mapper = session.getMapper(IUser.class);
User user = mapper.getUser(3);
           

    這是我們常用的一種方式,我們來跟蹤一下源碼,來看下其是如何實作的,如何擷取對應的Mapper接口的

// DefaultSqlSession.getMapper()
public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

//  Configuration.getMapper() 
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

// MapperRegistry.getMapper()
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null)
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    try {
      // 我們繼續追蹤該段代碼
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

// mapperProxyFactory.newInstance()
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

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

    可知,我們最終擷取的是MapperInterface(也就是本例中的IUser接口)的一個代理類MapperProxy,為什麼要費盡心機的得到這麼一個MapperProxy呢?

    我們知道,IUser隻是一個接口而已,定義了一系列方法,本身并沒有任何實作,那麼應該如何調用呢?我們接着看MapperProxy類

public class MapperProxy<T> implements InvocationHandler, Serializable {
  ...
  // MapperProxy方法調用的時候會直接調用其invoke方法,
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 針對于Object裡的方法
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
      
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 正真實作在這裡
    return mapperMethod.execute(sqlSession, args);
  }
    
// mapperMethod.execute()   
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        // 最終還是使用sqlSession去處理
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }
           

    為什麼要使用MapperProxy?

    主要是想在這裡進行統一處理,所有的關于Mapper接口的操作統一交由MapperProxy來處理,MapperProxy最終也是通過sqlSession來處理

參考: http://xpenxpen.iteye.com/blog/1508749 

繼續閱讀