上一篇文章《Mybatis源碼之SqlSession》聊到了SqlSession其實是一個包工頭兒,攬了活自己不干都安排給了執行器;而且在《Mybatis源碼之SQL執行過程》中已經了解到,執行器通過對StatementHandler生命周期的調度與管理,最終完成SQL命令執行。
今天我們就來認識一下Mybatis的四大組件之一:Executor-執行器。提前說明一下,由于Executor涉及到Mybatis的一級緩存、二級緩存,這部分內容后面會單獨分析,本文先不展開。
### Executor簡介
Executor位于executor包,Mybatis中所有的SQL命令都由它來調度執行。首先通過UML類圖認識一下Executor家族:

* 最頂層是Executor接口,定義了查詢、更新、事務、緩存操作相關的接口方法,Executor接口對外暴露,由SqlSession依賴,并受其調度與管理。
* 第二層左側為BaseExecutor,它是一個抽象類,實現了大部分Executor的接口。它有三個子類,分別是SimpleExecutor、ReuseExecutor、BatchExecutor。BaseExecutor及其子類完成了一級緩存管理和與數據庫交互有關的操作。
* 第二層右側為CachingExecutor,緩存執行器,Mybatis二級緩存的核心處理類。CachingExecutor持有一個BaseExecutor的實現類(SimpleExecutor、ReuseExecutor或BatchExecutor)實例作為委托執行器。它主要完成Mybatis二級緩存處理邏輯,當緩存查詢中不存在或查詢不到結果時,會通過委托執行器查詢數據庫。
* 第三層就是BaseExecutor的三個子類。簡單執行器為默認執行器,具備執行器的所有能力;可重用執行器,是相對簡單執行器而言的,它具備MappedStatement的緩存與復用能力,即在一個SqlSession會話內重復執行同一個命令,可以直接復用已緩存的MappedStatement;批量執行器,即一次可以執行多個命令。
Executor的核心功能是調度執行SQL,參與了全過程;為了提高查詢性能,Mybatis在Executor中設計了一級緩存和二級緩存,一級緩存由BaseExecutor及其子類實現,二級緩存由CachingExecutor實現。一級緩存是默認開啟的,二級緩存需要啟用配置。由于CachingExecutor負責緩存管理,真正的數據庫查詢是由BaseExecutor完成的,所以對外來看,Executor有三種類型SIMPLE、REUSE、BATCH,默認是SIMPLE,我們可以在配置文件或創建SqlSession時指定參數修改默認執行器類型。以下為Mybatis中ExecutorType定義:
~~~java
public enum ExecutorType {
// 簡單執行器
SIMPLE,
// 可重用的執行器
REUSE,
// 批處理執行器
BATCH
}
復制代碼
~~~
### 創建過程分析
前面已經說到,SqlSession依賴Executor,Executor接受SqlSession的請求并執行,而且Executor的創建是隨著SqlSession的創建而創建的。Executor的創建流程始于DefaultSqlSessionFactory#openSession()及其重載方法,代碼如下:
~~~java
@Override
public SqlSession openSession() {
//這里使用默認的執行器類型:SIMPLE
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
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);
//通過configuration.newExecutor方法創建執行器
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();
}
}
復制代碼
~~~
簡單分析一下這段代碼:這里采用了無參openSession方法,其內部使用Configuration#defaultExecutorType作為執行器類型調用openSessionFromDataSource方法;后者對execType未做處理調用了Configuration#newExecutor()執行Executor創建流程。
Configuration#defaultExecutorType默認值為SIMPLE,它會在Mybatis解析配置文件時被修改。若配置文件未涉及executorType的配置,默認值不會改變。另外,可以調用openSession的重載方法指定執行器類型。兩種設置方式如下所示:
* Mybatis默認(缺省)的執行器類型是SIMPLE,我們可以在配置文件中進行修改設置:
~~~xml
<settings>
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
復制代碼
~~~
* 或者通過代碼進行設置:
~~~java
// 指定執行器類型為REUSE
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE)) {
//……
}
復制代碼
~~~
了解了ExecutorType的設置方式,接著看Configuration#newExecutor()方法:
~~~java
//Executor執行器創建方法
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//executorType為null:使用默認執行器,否則按照入參類型
//看Configuration代碼,其實有: protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
executorType = executorType == null ? defaultExecutorType : executorType;
//executorType和defaultExecutorType都為null,默認使用SIMPLE
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//按照executorType創建不同的執行器。
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);
}
//這里判斷是否開啟了二級緩存,默認值是true。
//所以如果沒有修改配置文件的話,這個if語句是會進入執行的
if (cacheEnabled) {
//創建緩存執行器,同時把上面創建的executor作為其委托執行器
executor = new CachingExecutor(executor);
}
//這里是加載與Executor有關的插件。
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
復制代碼
~~~
總結一下執行過程:
* 首先:處理執行器類型參數,確保其不為空,最終以SIMPLE兜底;
* 其次:根據執行器類型調用對應的執行器實現類做初始化,執行器可以為SimpleExecutor、ReuseExecutor、BatchExecutor中的一個;
* 然后:如果啟用了二級緩存(cacheEnabled默認是true,但是如果不設置cache的相關參數,二級緩存是不起作用的),創建緩存執行器,同時把上面創建的executor作為緩存執行器的委托執行器;
* 最后:加載與Executor有關的插件。
默認情況下,會返回一個CachingExecutor對象,其內部包裝了BaseExecutor的某個子類對象。

### query流程
Executor接口中query方法有兩個重載,我們從參數少的這個開始入手,它內部也會調用到另外一個。默認情況下會使用CachingExecutor,所以我們從CachingExecutor#query()開始:
~~~java
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 獲取BoundSql
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 創建緩存key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 調用重載query方法
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 獲取緩存對象,啟用二級緩存才會有
Cache cache = ms.getCache();
// 緩存不空
if (cache != null) {
// 刷新緩存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 從緩存中查詢
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 緩存中沒有,通過委托查詢
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//默認情況沒有開啟二級緩存,會直接走到這里
//delegate即BaseExecutor三個子類的其中一個
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
復制代碼
~~~
進入CachingExecutor#query,首先通過MappedStatement獲取BoundSql,創建緩存key,然后調用了重載的query方法。重載query查詢在不考慮緩存的情況下,會直接通過委托執行器的query方法進行查詢。
這里的委托執行器為BaseExecutor的子類,而BaseExecutor實現了query方法,所以我們先進入BaseExecutor#query()(同樣先忽略一級緩存部分的邏輯):
~~~java
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//從一級緩存(本地緩存)中查詢
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//緩存中不存在,從數據庫中查詢
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
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;
}
復制代碼
~~~
兩個方法比較長,大部分是緩存處理。忽略緩存的情況下(直接看我注釋的部分),從query方法調用了數據庫查詢方法queryFromDatabase,但是,真正查詢的邏輯是在抽象方法doQuery中實現的,doQuery由BaseExecutor子類實現。我們依次看下子類實現邏輯:
#### SimpleExecutor#doQuery
~~~java
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
//獲取配置對象
Configuration configuration = ms.getConfiguration();
//創建StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//準備Statement
stmt = prepareStatement(handler, ms.getStatementLog());
//執行查詢
return handler.query(stmt, resultHandler);
} finally {
//關閉Statement
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
復制代碼
~~~
SimpleExecutor#doQuery方法代碼比較簡單,過程清晰明了,簡單說明一下:
* 從MappedStatement對象獲取全局Configuration配置對象;
* 調用Configuration#newStatementHandler創建StatementHandler對象;
* 創建并初始化Statement對象;
* 調用StatementHandler#query執行Statement,并使用resultHandler解析返回值;
* 最后關閉Statement。
從上述流程可知,doQuery方法調度StatementHandler完成了對Statement的初始化、參數設置、執行、結果處理與關閉,是對Statement整個生命周期的管理與控制,與前文所說的Executor參與了SQL語句執行的全過程名副其實。
#### ReuseExecutor#doQuery
~~~java
//Statement緩存
private final Map<String, Statement> statementMap = new HashMap<>();
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
//檢查緩存中是否存在當前sql
if (hasStatementFor(sql)) {
//如果有,就直接拿出來用
stmt = getStatement(sql);
applyTransactionTimeout(stmt);
} else {
//如果沒有,就新建一個
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
//然后,緩存起來。
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
復制代碼
~~~
ReuseExecutor#doQuery與SimpleExecutor#doQuery的邏輯基本一致,不同點在于prepareStatement方法的實現邏輯。prepareStatement使用statementMap對執行過的sql進行緩存,只有statementMap中不存在當前sql的時候才會執行創建流程,對性能有一定的提升。需要注意的是,Executor對象是SqlSession的組成部分,所以這個緩存與SqlSession的生命周期一致。
#### BatchExecutor#doQuery
~~~java
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException {
Statement stmt = null;
try {
//批量執行,目前我理解是為了把之前批量更新的語句執行掉
flushStatements();
//獲取Configuration對象
Configuration configuration = ms.getConfiguration();
//創建StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
//創建Statement
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
//設置Statement參數
handler.parameterize(stmt);
//執行并返回結果
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
復制代碼
~~~
BatchExecutor#doQuery方法除了多執行flushStatements方法外,與SimpleExecutor基本一致,不再展開。
### update()流程
update()方法的對應了insert、update、delete等不同的命令,其執行流程與query()方法流程類似。同樣是經過CachingExecutor->BaseExecutor->SimpleExecutor/ReuseExecutor/BatchExecutor。把整個過程的代碼一起貼出來分析(注釋):
~~~java
//org.apache.ibatis.executor.CachingExecutor#update
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
// 刷新緩存:開啟緩存時,update命令默認情況是需要刷新緩存的
flushCacheIfRequired(ms);
//調用委托執行器進行update
return delegate.update(ms, parameterObject);
}
//org.apache.ibatis.executor.BaseExecutor#update
@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.");
}
// 清空緩存,然后調用子類的doUpdate方法
clearLocalCache();
//調用抽象方法執行數據庫更新操作
return doUpdate(ms, parameter);
}
復制代碼
~~~
Mybatis的緩存機制僅對查詢有效,所以Executor能做的就是:使緩存失效、請求中轉,最終調用doUpdate執行數據庫操作,所以:
* CachingExecutor#update首先使二級緩存失效,然后調用委托執行器執行update操作。
* BaseExecutor#update也是首先使得一級緩存失效,然后調用抽象方法doUpdate執行數據庫的更新操作。
#### SimpleExecutor#doUpdate
SimpleExecutor#doUpdate與doQuery完全一致,不再說明了。
#### ReuseExecutor#doUpdate
ReuseExecutor#doUpdate與doQuery完全一致,不再說明了。
#### BatchExecutor#doUpdate
BatchExecutor的執行與前兩個不一樣,它用于執行批量的sql命令,所以多了一些批量準備工作。為了減少與數據庫的交互次數,BatchExecutor會批量執行sql命令。代碼如下:
~~~java
@Override
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
//獲取Configuration對象
final Configuration configuration = ms.getConfiguration();
//創建StatementHandler
final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
//獲取sql對象
final BoundSql boundSql = handler.getBoundSql();
//獲取sql與酒
final String sql = boundSql.getSql();
final Statement stmt;
//如果當前命令與上一次執行一樣,就不再重復創建Statement,性能提升
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
//取出最后一條的索引
int last = statementList.size() - 1;
//取出最后一個Statement對象
stmt = statementList.get(last);
applyTransactionTimeout(stmt);
//設置Statement參數
handler.parameterize(stmt);//fix Issues 322
//獲取BatchResult
BatchResult batchResult = batchResultList.get(last);
//設置BatchResult參數對象
batchResult.addParameterObject(parameterObject);
} else {
//如果當前命令與上一次執行不一樣,重新創建
Connection connection = getConnection(ms.getStatementLog());
//初始化、準備Statement
stmt = handler.prepare(connection, transaction.getTimeout());
//設置參數
handler.parameterize(stmt); //fix Issues 322
//設置當前執行的sql命令信息
currentSql = sql;
currentStatement = ms;
//存起來
statementList.add(stmt);
//保存結果對象
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
//批量處理
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
復制代碼
~~~
BatchExecutor#doUpdate方法完成了Statement執行前的準備工作,在準備Statement時與上一次要執行的Statement進行對比,如果一致則不再執行重新創建Statement的流程。所以,使用BatchExecutor時應該盡量執行相同的sql命令。
但是,BatchExecutor#doUpdate并未進行數據庫的執行操作,它需要通過SqlSession#flushStatements進行觸發,然后調用到BatchExecutor#doFlushStatements執行最終的操作,這里就不再展開了。
### Executor總結
本文從源碼角度對Executor的創建流程、query流程、update流程進行了詳細梳理,了解了Executor執行update/query的執行流程,重點說明了其與數據庫交互的過程,即Statement的調度管理,Executor的緩存機制我們并沒有展開。Executor是Mybatis四大組件之一,雖然還沒有研究其他三個,但是已經對Mybatis的SQL執行過程有了整體的了解,由此可見Executor的作用舉足輕重。
作者:碼路印記
鏈接:https://juejin.cn/post/6883840514861301773
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
- 一.JVM
- 1.1 java代碼是怎么運行的
- 1.2 JVM的內存區域
- 1.3 JVM運行時內存
- 1.4 JVM內存分配策略
- 1.5 JVM類加載機制與對象的生命周期
- 1.6 常用的垃圾回收算法
- 1.7 JVM垃圾收集器
- 1.8 CMS垃圾收集器
- 1.9 G1垃圾收集器
- 2.面試相關文章
- 2.1 可能是把Java內存區域講得最清楚的一篇文章
- 2.0 GC調優參數
- 2.1GC排查系列
- 2.2 內存泄漏和內存溢出
- 2.2.3 深入理解JVM-hotspot虛擬機對象探秘
- 1.10 并發的可達性分析相關問題
- 二.Java集合架構
- 1.ArrayList深入源碼分析
- 2.Vector深入源碼分析
- 3.LinkedList深入源碼分析
- 4.HashMap深入源碼分析
- 5.ConcurrentHashMap深入源碼分析
- 6.HashSet,LinkedHashSet 和 LinkedHashMap
- 7.容器中的設計模式
- 8.集合架構之面試指南
- 9.TreeSet和TreeMap
- 三.Java基礎
- 1.基礎概念
- 1.1 Java程序初始化的順序是怎么樣的
- 1.2 Java和C++的區別
- 1.3 反射
- 1.4 注解
- 1.5 泛型
- 1.6 字節與字符的區別以及訪問修飾符
- 1.7 深拷貝與淺拷貝
- 1.8 字符串常量池
- 2.面向對象
- 3.關鍵字
- 4.基本數據類型與運算
- 5.字符串與數組
- 6.異常處理
- 7.Object 通用方法
- 8.Java8
- 8.1 Java 8 Tutorial
- 8.2 Java 8 數據流(Stream)
- 8.3 Java 8 并發教程:線程和執行器
- 8.4 Java 8 并發教程:同步和鎖
- 8.5 Java 8 并發教程:原子變量和 ConcurrentMap
- 8.6 Java 8 API 示例:字符串、數值、算術和文件
- 8.7 在 Java 8 中避免 Null 檢查
- 8.8 使用 Intellij IDEA 解決 Java 8 的數據流問題
- 四.Java 并發編程
- 1.線程的實現/創建
- 2.線程生命周期/狀態轉換
- 3.線程池
- 4.線程中的協作、中斷
- 5.Java鎖
- 5.1 樂觀鎖、悲觀鎖和自旋鎖
- 5.2 Synchronized
- 5.3 ReentrantLock
- 5.4 公平鎖和非公平鎖
- 5.3.1 說說ReentrantLock的實現原理,以及ReentrantLock的核心源碼是如何實現的?
- 5.5 鎖優化和升級
- 6.多線程的上下文切換
- 7.死鎖的產生和解決
- 8.J.U.C(java.util.concurrent)
- 0.簡化版(快速復習用)
- 9.鎖優化
- 10.Java 內存模型(JMM)
- 11.ThreadLocal詳解
- 12 CAS
- 13.AQS
- 0.ArrayBlockingQueue和LinkedBlockingQueue的實現原理
- 1.DelayQueue的實現原理
- 14.Thread.join()實現原理
- 15.PriorityQueue 的特性和原理
- 16.CyclicBarrier的實際使用場景
- 五.Java I/O NIO
- 1.I/O模型簡述
- 2.Java NIO之緩沖區
- 3.JAVA NIO之文件通道
- 4.Java NIO之套接字通道
- 5.Java NIO之選擇器
- 6.基于 Java NIO 實現簡單的 HTTP 服務器
- 7.BIO-NIO-AIO
- 8.netty(一)
- 9.NIO面試題
- 六.Java設計模式
- 1.單例模式
- 2.策略模式
- 3.模板方法
- 4.適配器模式
- 5.簡單工廠
- 6.門面模式
- 7.代理模式
- 七.數據結構和算法
- 1.什么是紅黑樹
- 2.二叉樹
- 2.1 二叉樹的前序、中序、后序遍歷
- 3.排序算法匯總
- 4.java實現鏈表及鏈表的重用操作
- 4.1算法題-鏈表反轉
- 5.圖的概述
- 6.常見的幾道字符串算法題
- 7.幾道常見的鏈表算法題
- 8.leetcode常見算法題1
- 9.LRU緩存策略
- 10.二進制及位運算
- 10.1.二進制和十進制轉換
- 10.2.位運算
- 11.常見鏈表算法題
- 12.算法好文推薦
- 13.跳表
- 八.Spring 全家桶
- 1.Spring IOC
- 2.Spring AOP
- 3.Spring 事務管理
- 4.SpringMVC 運行流程和手動實現
- 0.Spring 核心技術
- 5.spring如何解決循環依賴問題
- 6.springboot自動裝配原理
- 7.Spring中的循環依賴解決機制中,為什么要三級緩存,用二級緩存不夠嗎
- 8.beanFactory和factoryBean有什么區別
- 九.數據庫
- 1.mybatis
- 1.1 MyBatis-# 與 $ 區別以及 sql 預編譯
- Mybatis系列1-Configuration
- Mybatis系列2-SQL執行過程
- Mybatis系列3-之SqlSession
- Mybatis系列4-之Executor
- Mybatis系列5-StatementHandler
- Mybatis系列6-MappedStatement
- Mybatis系列7-參數設置揭秘(ParameterHandler)
- Mybatis系列8-緩存機制
- 2.淺談聚簇索引和非聚簇索引的區別
- 3.mysql 證明為什么用limit時,offset很大會影響性能
- 4.MySQL中的索引
- 5.數據庫索引2
- 6.面試題收集
- 7.MySQL行鎖、表鎖、間隙鎖詳解
- 8.數據庫MVCC詳解
- 9.一條SQL查詢語句是如何執行的
- 10.MySQL 的 crash-safe 原理解析
- 11.MySQL 性能優化神器 Explain 使用分析
- 12.mysql中,一條update語句執行的過程是怎么樣的?期間用到了mysql的哪些log,分別有什么作用
- 十.Redis
- 0.快速復習回顧Redis
- 1.通俗易懂的Redis數據結構基礎教程
- 2.分布式鎖(一)
- 3.分布式鎖(二)
- 4.延時隊列
- 5.位圖Bitmaps
- 6.Bitmaps(位圖)的使用
- 7.Scan
- 8.redis緩存雪崩、緩存擊穿、緩存穿透
- 9.Redis為什么是單線程、及高并發快的3大原因詳解
- 10.布隆過濾器你值得擁有的開發利器
- 11.Redis哨兵、復制、集群的設計原理與區別
- 12.redis的IO多路復用
- 13.相關redis面試題
- 14.redis集群
- 十一.中間件
- 1.RabbitMQ
- 1.1 RabbitMQ實戰,hello world
- 1.2 RabbitMQ 實戰,工作隊列
- 1.3 RabbitMQ 實戰, 發布訂閱
- 1.4 RabbitMQ 實戰,路由
- 1.5 RabbitMQ 實戰,主題
- 1.6 Spring AMQP 的 AMQP 抽象
- 1.7 Spring AMQP 實戰 – 整合 RabbitMQ 發送郵件
- 1.8 RabbitMQ 的消息持久化與 Spring AMQP 的實現剖析
- 1.9 RabbitMQ必備核心知識
- 2.RocketMQ 的幾個簡單問題與答案
- 2.Kafka
- 2.1 kafka 基礎概念和術語
- 2.2 Kafka的重平衡(Rebalance)
- 2.3.kafka日志機制
- 2.4 kafka是pull還是push的方式傳遞消息的?
- 2.5 Kafka的數據處理流程
- 2.6 Kafka的腦裂預防和處理機制
- 2.7 Kafka中partition副本的Leader選舉機制
- 2.8 如果Leader掛了的時候,follower沒來得及同步,是否會出現數據不一致
- 2.9 kafka的partition副本是否會出現腦裂情況
- 十二.Zookeeper
- 0.什么是Zookeeper(漫畫)
- 1.使用docker安裝Zookeeper偽集群
- 3.ZooKeeper-Plus
- 4.zk實現分布式鎖
- 5.ZooKeeper之Watcher機制
- 6.Zookeeper之選舉及數據一致性
- 十三.計算機網絡
- 1.進制轉換:二進制、八進制、十六進制、十進制之間的轉換
- 2.位運算
- 3.計算機網絡面試題匯總1
- 十四.Docker
- 100.面試題收集合集
- 1.美團面試常見問題總結
- 2.b站部分面試題
- 3.比心面試題
- 4.騰訊面試題
- 5.哈羅部分面試
- 6.筆記
- 十五.Storm
- 1.Storm和流處理簡介
- 2.Storm 核心概念詳解
- 3.Storm 單機版本環境搭建
- 4.Storm 集群環境搭建
- 5.Storm 編程模型詳解
- 6.Storm 項目三種打包方式對比分析
- 7.Storm 集成 Redis 詳解
- 8.Storm 集成 HDFS 和 HBase
- 9.Storm 集成 Kafka
- 十六.Elasticsearch
- 1.初識ElasticSearch
- 2.文檔基本CRUD、集群健康檢查
- 3.shard&replica
- 4.document核心元數據解析及ES的并發控制
- 5.document的批量操作及數據路由原理
- 6.倒排索引
- 十七.分布式相關
- 1.分布式事務解決方案一網打盡
- 2.關于xxx怎么保證高可用的問題
- 3.一致性hash原理與實現
- 4.微服務注冊中心 Nacos 比 Eureka的優勢
- 5.Raft 協議算法
- 6.為什么微服務架構中需要網關
- 0.CAP與BASE理論
- 十八.Dubbo
- 1.快速掌握Dubbo常規應用
- 2.Dubbo應用進階
- 3.Dubbo調用模塊詳解
- 4.Dubbo調用模塊源碼分析
- 6.Dubbo協議模塊