常見設計模式在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的執行邏輯,然後再把方法交給子類來實現,它的繼承關係如下所示:
比如,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類;
如圖,我們可以看到防止併發訪問的SynchronizedCache、先進先出的FifoCache、最近最少使用的LruCache、定時清空快取ScheduledCache、阻塞快取BlockingCache等;其實如果你細心的話通過命名就知道,PerpetualCache是mybatis的基本實現類,而在包decorator
下的都是其裝飾模式的擴充套件類。再比較下這些類,你會發現裝飾器的類的在有參構造中進行了方法呼叫,即原有物件構造時功能進行了擴充套件!
小結:關於設計模式,我們基本弄清楚這些就可以了;其他的瞭解即可,因為處在網際網路的時代,個人很難做到面面俱到,有些東西你可以不會但是你不能不知道,或者說你不能不知道學習的路徑或方法!
另外mybatis是一個很經典的框架,特別涉及到快取方面,面試考點也很多;例如涉及到一級快取,二級快取,cachekey的演算法實現,BlockingCache解決了快取穿透和雪崩等;這就必須要求我們去讀它的原始碼了:mybati原始碼帶中文註釋閱讀地址:https://github.com/tuguangquan/mybatis