MyBatis 的前身是 IBatis,IBatis 是由 Internet 和 Abatis 組合而成,其目的是想當做互聯網的籬笆墻,圍繞著數據庫提供持久化服務的一個框架,2010 年正式改名為 MyBatis。它是一款優秀的持久層框架,支持自定義 SQL、存儲過程及高級映射。MyBatis 免除了幾乎所有的 JDBC 代碼以及設置參數和獲取結果集的工作,還可以通過簡單的 XML 或注解來配置和映射原始類型、接口和 Java POJO(Plain Ordinary Java Object,普通 Java 對象)為數據庫中的記錄。
關于 MyBatis 的介紹與使用,官方已經提供了比較詳盡的中文參考文檔,可點擊這里查看,而本課時則以面試的角度出發,聊一聊不一樣的知識點,它也是 MyBatis 比較熱門的面試題之一,MyBatis 使用了哪些設計模式?在源碼中是如何體現的?
注意:本課時使用的 MyBatis 源碼為 3.5.5。
#### 典型回答
* [ ] 1.工廠模式
工廠模式想必都比較熟悉,它是 Java 中最常用的設計模式之一。工廠模式就是提供一個工廠類,當有客戶端需要調用的時候,只調用這個工廠類就可以得到自己想要的結果,從而無需關注某類的具體實現過程。這就好比你去餐館吃飯,可以直接點菜,而不用考慮廚師是怎么做的。
工廠模式在 MyBatis 中的典型代表是 SqlSessionFactory。
SqlSession 是 MyBatis 中的重要 Java 接口,可以通過該接口來執行 SQL 命令、獲取映射器示例和管理事務,而 SqlSessionFactory 正是用來產生 SqlSession 對象的,所以它在 MyBatis 中是比較核心的接口之一。
工廠模式應用解析:SqlSessionFactory 是一個接口類,它的子類 DefaultSqlSessionFactorys 有一個 openSession(ExecutorType execType) 的方法,其中使用了工廠模式,源碼如下:
```
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);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
```
從該方法我們可以看出它會 configuration.newExecutor(tx, execType) 讀取對應的環境配置,而此方法的源碼如下:
```
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;
}
```
可以看出 newExecutor() 方法為標準的工廠模式,它會根據傳遞 ExecutorType 值生成相應的對象然后進行返回。
* [ ] 2.建造者模式(Builder)
建造者模式指的是將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。也就是說建造者模式是通過多個模塊一步步實現了對象的構建,相同的構建過程可以創建不同的產品。
例如,組裝電腦,最終的產品就是一臺主機,然而不同的人對它的要求是不同的,比如設計人員需要顯卡配置高的;而影片愛好者則需要硬盤足夠大的(能把視頻都保存起來),但對于顯卡卻沒有太大的要求,我們的裝機人員根據每個人不同的要求,組裝相應電腦的過程就是建造者模式。
建造者模式在 MyBatis 中的典型代表是 SqlSessionFactoryBuilder。
普通的對象都是通過 new 關鍵字直接創建的,但是如果創建對象需要的構造參數很多,且不能保證每個參數都是正確的或者不能一次性得到構建所需的所有參數,那么就需要將構建邏輯從對象本身抽離出來,讓對象只關注功能,把構建交給構建類,這樣可以簡化對象的構建,也可以達到分步構建對象的目的,而 SqlSessionFactoryBuilder 的構建過程正是如此。
在 SqlSessionFactoryBuilder 中構建 SqlSessionFactory 對象的過程是這樣的,首先需要通過 XMLConfigBuilder 對象讀取并解析 XML 的配置文件,然后再將讀取到的配置信息存入到 Configuration 類中,然后再通過 build 方法生成我們需要的 DefaultSqlSessionFactory 對象,實現源碼如下(在 SqlSessionFactoryBuilder 類中):
```
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);
}
```
SqlSessionFactoryBuilder 類相當于一個建造工廠,先讀取文件或者配置信息、再解析配置、然后通過反射生成對象,最后再把結果存入緩存,這樣就一步步構建造出一個 SqlSessionFactory 對象。
* [ ] 3.單例模式
單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一,此模式保證某個類在運行期間,只有一個實例對外提供服務,而這個類被稱為單例類。
單例模式也比較好理解,比如一個人一生當中只能有一個真實的身份證號,每個收費站的窗口都只能一輛車子一輛車子的經過,類似的場景都是屬于單例模式。
單例模式在 MyBatis 中的典型代表是 ErrorContext。
ErrorContext 是線程級別的的單例,每個線程中有一個此對象的單例,用于記錄該線程的執行環境的錯誤信息。
ErrorContext 的實現源碼如下:
```
public class ErrorContext {
private static final String LINE_SEPARATOR = System.lineSeparator();
// 每個線程存儲的容器
private static final ThreadLocal<ErrorContext> LOCAL = ThreadLocal.withInitial(ErrorContext::new);
public static ErrorContext instance() {
return LOCAL.get();
}
// 忽略其他
}
```
可以看出 ErrorContext 使用 private 修飾的 ThreadLocal 來保證每個線程擁有一個 ErrorContext 對象,在調用 instance() 方法時再從 ThreadLocal 中獲取此單例對象。
* [ ] 4.適配器模式
適配器模式是指將一個不兼容的接口轉換成另一個可以兼容的接口,這樣就可以使那些不兼容的類可以一起工作。
例如,最早之前我們用的耳機都是圓形的,而現在大多數的耳機和電源都統一成了方形的 typec 接口,那之前的圓形耳機就不能使用了,只能買一個適配器把圓形接口轉化成方形的,如下圖所示:

而這個轉換頭就相當于程序中的適配器模式,適配器模式在 MyBatis 中的典型代表是 Log。
MyBatis 中的日志模塊適配了以下多種日志類型:
* SLF4J
* Apache Commons Logging
* Log4j 2
* Log4j
* JDK logging
首先 MyBatis 定義了一個 Log 的接口,用于統一和規范接口的行為,源碼如下:
```
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);
}
```
然后 MyBatis 定義了多個適配接口,例如 Log4j2 實現源碼如下
```
public class Log4j2Impl implements Log {
private final Log log;
public Log4j2Impl(String clazz) {
Logger logger = LogManager.getLogger(clazz);
if (logger instanceof AbstractLogger) {
log = new Log4j2AbstractLoggerImpl((AbstractLogger) logger);
} else {
log = new Log4j2LoggerImpl(logger);
}
}
@Override
public boolean isDebugEnabled() {
return log.isDebugEnabled();
}
@Override
public boolean isTraceEnabled() {
return log.isTraceEnabled();
}
@Override
public void error(String s, Throwable e) {
log.error(s, e);
}
@Override
public void error(String s) {
log.error(s);
}
@Override
public void debug(String s) {
log.debug(s);
}
@Override
public void trace(String s) {
log.trace(s);
}
@Override
public void warn(String s) {
log.warn(s);
}
}
```
這樣當你項目中添加了 Log4j2 時,MyBatis 就可以直接使用它打印 MyBatis 的日志信息了。Log 的所有子類如下圖所示:

* [ ] 5.代理模式
代理模式指的是給某一個對象提供一個代理對象,并由代理對象控制原對象的調用。
代理模式在生活中也比較常見,比如我們常見的超市、小賣店其實都是一個個“代理”,他們的最上游是一個個生產廠家,他們這些代理負責把廠家生產出來的產品賣出去。
代理模式在 MyBatis 中的典型代表是 MapperProxyFactory。
MapperProxyFactory 的 newInstance() 方法就是生成一個具體的代理來實現功能的,源碼如下:
```
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}
// 創建代理類
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
```
* [ ] 6.模板方法模式
模板方法模式是最常用的設計模式之一,它是指定義一個操作算法的骨架,而將一些步驟的實現延遲到子類中去實現,使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。此模式是基于繼承的思想實現代碼復用的。
例如,我們喝茶的一般步驟都是這樣的:
* 把熱水燒開
* 把茶葉放入壺中
* 等待一分鐘左右
* 把茶倒入杯子中
* 喝茶
整個過程都是固定的,唯一變的就是泡入茶葉種類的不同,比如今天喝的是綠茶,明天可能喝的是紅茶,那么我們就可以把流程定義為一個模板,而把茶葉的種類延伸到子類中去實現,這就是模板方法的實現思路。
模板方法在 MyBatis 中的典型代表是 BaseExecutor。
在 MyBatis 中 BaseExecutor 實現了大部分 SQL 執行的邏輯,然后再把幾個方法交給子類來實現,它的繼承關系如下圖所示:

比如 doUpdate() 就是交給子類自己去實現的,它在 BaseExecutor 中的定義如下:
```
protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
```
在 SimpleExecutor 中的實現如下:
```
public class SimpleExecutor extends BaseExecutor {
// 構造方法
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
// 更新方法
@Override
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);
}
}
// 忽略其他代碼...
}
```
可以看出 SimpleExecutor 每次使用完 Statement 對象之后,都會把它關閉掉,而 ReuseExecutor 中的實現源碼如下:
```
public class ReuseExecutor extends BaseExecutor {
private final Map<String, Statement> statementMap = new HashMap<>();
public ReuseExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
// 更新方法
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
}
// 忽略其他代碼...
}
```
可以看出,ReuseExecutor 每次使用完 Statement 對象之后不會把它關閉掉。
* [ ] 7.裝飾器模式
裝飾器模式允許向一個現有的對象添加新的功能,同時又不改變其結構,這種類型的設計模式屬于結構型模式,它是作為現有類的一個包裝。
裝飾器模式在生活中很常見,比如裝修房子,我們在不改變房子結構的同時,給房子添加了很多的點綴;比如安裝了天然氣報警器,增加了熱水器等附加的功能都屬于裝飾器模式。
裝飾器模式在 MyBatis 中的典型代表是 Cache。
Cache 除了有數據存儲和緩存的基本功能外(由 PerpetualCache 永久緩存實現),還有其他附加的 Cache 類,比如先進先出的 FifoCache、最近最少使用的 LruCache、防止多線程并發訪問的 SynchronizedCache 等眾多附加功能的緩存類,Cache 所有實現子類如下圖所示:

#### 小結
本課時我們重點講了 MyBatis 源碼中的幾個主要設計模式,即工廠模式、建造者模式、單例模式、適配器模式、代理模式、模板方法模式等,希望本課時的內容能起到拋磚引玉的作用,對你理解設計模式和 MyBatis 提供一些幫助,如果想要閱讀全部的 MyBatis 源碼可以訪問:https://github.com/mybatis/mybatis-3 。
- 前言
- 開篇詞
- 開篇詞:大廠技術面試“潛規則”
- 模塊一:Java 基礎
- 第01講:String 的特點是什么?它有哪些重要的方法?
- 第02講:HashMap 底層實現原理是什么?JDK8 做了哪些優化?
- 第03講:線程的狀態有哪些?它是如何工作的?
- 第04講:詳解 ThreadPoolExecutor 的參數含義及源碼執行流程?
- 第05講:synchronized 和 ReentrantLock 的實現原理是什么?它們有什么區別?
- 第06講:談談你對鎖的理解?如何手動模擬一個死鎖?
- 第07講:深克隆和淺克隆有什么區別?它的實現方式有哪些?
- 第08講:動態代理是如何實現的?JDK Proxy 和 CGLib 有什么區別?
- 第09講:如何實現本地緩存和分布式緩存?
- 第10講:如何手寫一個消息隊列和延遲消息隊列?
- 模塊二:熱門框架
- 第11講:底層源碼分析 Spring 的核心功能和執行流程?(上)
- 第12講:底層源碼分析 Spring 的核心功能和執行流程?(下)
- 第13講:MyBatis 使用了哪些設計模式?在源碼中是如何體現的?
- 第14講:SpringBoot 有哪些優點?它和 Spring 有什么區別?
- 第15講:MQ 有什么作用?你都用過哪些 MQ 中間件?
- 模塊三:數據庫相關
- 第16講:MySQL 的運行機制是什么?它有哪些引擎?
- 第17講:MySQL 的優化方案有哪些?
- 第18講:關系型數據和文檔型數據庫有什么區別?
- 第19講:Redis 的過期策略和內存淘汰機制有什么區別?
- 第20講:Redis 怎樣實現的分布式鎖?
- 第21講:Redis 中如何實現的消息隊列?實現的方式有幾種?
- 第22講:Redis 是如何實現高可用的?
- 模塊四:Java 進階
- 第23講:說一下 JVM 的內存布局和運行原理?
- 第24講:垃圾回收算法有哪些?
- 第25講:你用過哪些垃圾回收器?它們有什么區別?
- 第26講:生產環境如何排除和優化 JVM?
- 第27講:單例的實現方式有幾種?它們有什么優缺點?
- 第28講:你知道哪些設計模式?分別對應的應用場景有哪些?
- 第29講:紅黑樹和平衡二叉樹有什么區別?
- 第30講:你知道哪些算法?講一下它的內部實現過程?
- 模塊五:加分項
- 第31講:如何保證接口的冪等性?常見的實現方案有哪些?
- 第32講:TCP 為什么需要三次握手?
- 第33講:Nginx 的負載均衡模式有哪些?它的實現原理是什么?
- 第34講:Docker 有什么優點?使用時需要注意什么問題?
- 彩蛋
- 彩蛋:如何提高面試成功率?