天天看點

常見設計模式在mybatis中的應用

請你說說,設計模式在mybatis有哪些具體的應用?

@

目錄

  • 工廠模式
  • 單例模式
  • 建造者模式
  • 擴充卡模式
  • 代理模式
  • 模闆方法模式
  • 裝飾模式

設計模式分類

常見設計模式在mybatis中的應用

是不是看着頭大,哈哈;頭大就對了,我們沒必要每個都去深究,我們隻需要結合例子知道部門設計模式是如何使用的就可以了!接下來我們結合mybatis這個架構,探讨下常用設計的使用吧!

關于設計模式的示例及講解可以看C語言中文網中關于設計模式的描述及示例:23種設計模式詳解

那麼mybatis中運用的設計模式有哪些呢?

簡單了解,就是工廠模式就是提供一個工廠類,當用戶端需要調用的時候就可以得到想要的結果,而不需要關注内部的實作!就好像買東西,不需要你關心這些東西怎麼生産的,給對應的錢就可以了!

那麼工廠模式在MyBatis中具體的應用就是SqlSessionFactory, 通過SqlSessionFactory接口類,可以得到對應的SqlSession接口,我們可以通過該接口執行SQL指令,擷取映射器示例和管理事務。

DefaultSqlSessionFactory是SqlSessionFactory下的一個子類,我們來看下其中的openSessionFromDataSource方法的源碼:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  DefaultSqlSession var8;
  try {
​    // 讀取環境配置
​    Environment environment = this.configuration.getEnvironment();
​    TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
​    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
​    // 跟據對應的執行器類型擷取對應的執行器對象
​    Executor executor = this.configuration.newExecutor(tx, execType);
​    var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);

 } catch (Exception var12) {
​    this.closeTransaction(tx);
​    throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);

 } finally {
​    ErrorContext.instance().reset();
 }
  return var8;
}

           

我們可以看到對應的newExecutor方法可以根據executorType類型,建立不同的Executor對象;這就是标準的工廠模式的應用!

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
​    executorType = executorType == null ? this.defaultExecutorType : executorType;
​    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
​    Object 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 (this.cacheEnabled) {
​      executor = new CachingExecutor((Executor)executor);
   }
​    Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
​    return executor;
 }

           

其實單例模式一定程度上違背了“單一職責原則”;為什麼呢,因為單例模式即提供了單一的執行個體,又提供了執行個體的全局通路;但是,這種模式還是有它的優勢和不可替代的地方的,在mybatis架構中的單例的代表實作就是ErrorContext;我們來看下它的源碼:

public class ErrorContext {
  private static final String LINE_SEPARATOR = System.getProperty("line.separator", "\n");
  private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal();
  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 = (ErrorContext)LOCAL.get();
​    if (context == null) {
​      context = new ErrorContext();
​      LOCAL.set(context);
   }
​    return context;
 }
  //...其他代碼省略
}

           

從上面代碼我們可以看到,ErrorContext私有了構造器,并且私有了一個LOCAL 線程來儲存ErrorContext對象,以此來保證每個線程都有一個獨立的ErrorContext對象,在初始化的instance()方法時,将ErrorContext對象 set到LOCAL中!

簡而言之,就是将一個對象的建構過程拆分,通過多個子產品一步步實作,根據子產品的多少進而達到不同級别的實作!舉個例子,你要組裝台電腦,那麼你可以根據你的需求,你可以分為:開發,辦公,影音聊天,打遊戲等這些需求。那麼根據不同的需求,你可以選購不同級别的記憶體,顯示卡,顯示屏,硬碟等。那麼電腦組裝人員,根據你不同的需求組裝對應電腦的過程就是建造者模式!

那麼建造者模式在mybatis是怎麼應用的呢?典型的就是各種builder,我們結合SqlSessionFactoryBuilder類來分析下:

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
  SqlSessionFactory var5;
  try {
​    // 讀取xml配置
​    XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
​    // 建構SqlSessionFactory
​    var5 = this.build(parser.parse());
  } catch (Exception var14) {
​    throw ExceptionFactory.wrapException("Error building SqlSession.", var14);

  } finally {
​    // 将單例的重置放到finally 中
​    ErrorContext.instance().reset();
​    try {
​      reader.close();
​    } catch (IOException var13) {

​    }
  }
  return var5;
}

           

//...省略其他方法

public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}
           

通過源碼的閱讀,我們知道SqlSessionFactoryBuilder類提供了各種build方法,通過讀取解析配置,然後通過反射生成對象,最後将對象放入了緩存,然後一步步建構傳回SqlSessionFactor對象;

擴充卡模式,其實很好了解;它的運用隻為了相容,通過擴充卡模式,我們可以将一個不相容的接口轉換成相容的接口,進而使不相容的類可以協調一起工作!通俗的講,就是一個轉接頭的概念,不論你用啥類型的線,我給你加個轉接頭,你都能玩,沒有什麼是加一層不能的解決的嘛!

例如,mybatis的日志子產品适配了多種日志類型,包括:SLF4J,Log4j2,JDK loggiing等;

我們來看下Log接口:

public interface Log {

  boolean isDebugEnabled();

  boolean isTraceEnabled();

  void error(String var1, Throwable var2);

  void error(String var1);

  void debug(String var1);

  void trace(String var1);

  void warn(String var1);

}
           

我們來看下Log4j2的實作類Log4j2Impl:

import org.apache.ibatis.logging.Log;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.spi.AbstractLogger;

public class Log4j2Impl implements Log {
  private final Log log;
  public Log4j2Impl(String clazz) {
​    Logger logger = LogManager.getLogger(clazz);
​    if (logger instanceof AbstractLogger) {
​      this.log = new Log4j2AbstractLoggerImpl((AbstractLogger)logger);
   } else {
​      this.log = new Log4j2LoggerImpl(logger);
   }
}
  //...其他代碼略
}
           

這樣做的好處是,當你使用log4j2時,mybatis可以直接使用它列印mybatis的日志!就是說,我是總的規範制定者,不管你們下面再怎麼亂, 我把規範制定出來,你們要用的話,就實作我的這些方法就可以了!為了你們使用友善,我把你們都內建到我這裡來,你們後面直接用你的東西但是還是可以調用我這邊提供的方法!

哈哈,這個是個比較經典的模式了!最經典的了解就是火車票代售點,它就是個代理火車站賣票的作用!那麼mybatis中怎麼使用的呢?我們來看下MapperProxyFactory類:

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
 }
  public Class<T> getMapperInterface() {
​    return this.mapperInterface;
 }
  public Map<Method, MapperMethod> getMethodCache() {
​    return this.methodCache;
 }
  // 建立代理執行個體
  protected T newInstance(MapperProxy<T> mapperProxy) {
​    return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
 }
  public T newInstance(SqlSession sqlSession) {
​    MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
​    return this.newInstance(mapperProxy);
 }
}
           

很多時候,我們做事情是有一套固定的流程模闆的,例如:把東西放進冰箱;打開冰箱門->放入東西->關門,唯一不同的就是這個東西是可變的!而模闆方法模式就是,規定一套流程,而降放入東西這一具體的實作放入子類中去實作,使得子類不改變整個算法流程的結構,即可以重新定義一個模闆,重而達到模闆複用的目的!

mybatis中代表的模闆方法的應用是BaseExecutor類,BaseExecutor實作了大部分的SQL的執行邏輯,然後再把方法交給子類來實作,它的繼承關系如下所示:

常見設計模式在mybatis中的應用

比如,doUpdate()方法就是交給子類去實作的,在BaseExecutor中定義如下:

protected abstract int doUpdate(MappedStatement var1, Object var2) throws SQLException;
           

在SimpleExecutor中的實作如下:

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

而在BatchExecutor中實作如下:

public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    Statement stmt;
    if (sql.equals(this.currentSql) && ms.equals(this.currentStatement)) {
        int last = this.statementList.size() - 1;
        stmt = (Statement)this.statementList.get(last);
        this.applyTransactionTimeout(stmt);
        handler.parameterize(stmt);
        BatchResult batchResult = (BatchResult)this.batchResultList.get(last);
        batchResult.addParameterObject(parameterObject);
    } else {
        Connection connection = this.getConnection(ms.getStatementLog());
        stmt = handler.prepare(connection, this.transaction.getTimeout());
        handler.parameterize(stmt);
        this.currentSql = sql;
        this.currentStatement = ms;
        this.statementList.add(stmt);
        this.batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
    handler.batch(stmt);
    return -2147482646;
}
           

在ReuseExecutor中實作如下:

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

可以看出每個子類的實作都不同,SimpleExecutor中使用完Statement後,都在finally中關閉了Statement對象,而BatchExecutor和ReuseExecutor都沒有關閉;并且BatchExecutor中doUpdate傳回了一個最小的int值,而其他兩個則傳回的是實際影響的條數!

裝飾模式又稱作裝飾器模式,它指的是在不改變原有代碼結構情況下,動态給對象添加方法的模式;通俗的來講,即是我們一般擴充對象的功能一般采用繼承的方式,但是繼承具有原生屬性的特點,耦合度高;随着功能增多,子類也會變得越類越大;這個情況我們使用裝飾模式,在不改變原有功能的情況,對原有功能進行一個擴充。

mybatis采用裝飾模式的典型代表就是Cache,Cache除了最基本的存儲和緩存的作用外,還附加了其他的Cache類;

常見設計模式在mybatis中的應用

如圖,我們可以看到防止并發通路的SynchronizedCache、先進先出的FifoCache、最近最少使用的LruCache、定時清空緩存ScheduledCache、阻塞緩存BlockingCache等;其實如果你細心的話通過命名就知道,PerpetualCache是mybatis的基本實作類,而在包

decorator

下的都是其裝飾模式的擴充類。再比較下這些類,你會發現裝飾器的類的在有參構造中進行了方法調用,即原有對象構造時功能進行了擴充!

小結:關于設計模式,我們基本弄清楚這些就可以了;其他的了解即可,因為處在網際網路的時代,個人很難做到面面俱到,有些東西你可以不會但是你不能不知道,或者說你不能不知道學習的路徑或方法!

另外mybatis是一個很經典的架構,特别涉及到緩存方面,面試考點也很多;例如涉及到一級緩存,二級緩存,cachekey的算法實作,BlockingCache解決了緩存穿透和雪崩等;這就必須要求我們去讀它的源碼了:mybati源碼帶中文注釋閱讀位址:https://github.com/tuguangquan/mybatis

餘路那麼長,還是得帶着虔誠上路...

繼續閱讀