Mybatis框架及原理實例分析
摘要
本篇文章只是個人閱讀mybatis源碼總結(jié)的經(jīng)驗或者個人理解mybatis的基本輪廓,作為拋磚引玉的功能,希望對你有幫助,如果需要深入了解細(xì)節(jié)還需親自去閱讀源碼。
mybatis基本架構(gòu)
mybatis的源碼應(yīng)該算是比較容易閱讀的,首先mybatis核心功能就是執(zhí)行Sql語句,但在其基礎(chǔ)上又有許多增強(qiáng)的地方(動態(tài)Sql,ORM等)。看一個框架的時候,第一步是對整個框架有一個大體的了解。例如mybatis,我們可以從初始化到完成一個sql請求為主線,看一下涉及了哪些類。我個人總結(jié)了一下,mybatis的框架主要的核心類有4個

Configuration
Configuration就是用于解析、保存、處理Mybatis的配置內(nèi)容,包括了
- mybatis基本配置,例如支持?jǐn)?shù)據(jù)庫中的字段支持下標(biāo)轉(zhuǎn)駝峰mapUnderscoreToCamelCase=true等等,參看Mybatis配置說明
- SqlMapper管理,也就是通過xml或者注解寫的一些sql映射。相關(guān)的類可以查看源碼中MappedStatement類。
- 創(chuàng)建類,Configuration還有一些創(chuàng)建類的功能,例如Executor、StatementHandler。這個2個類后面還會說到
小節(jié)Configuration
總結(jié)Configuration的功能,當(dāng)然,如何讀取和解析相關(guān)文件是Configuration中大部分代碼做的事。這些都是為了準(zhǔn)備后面mybatis運(yùn)行的基本條件。Configuration中創(chuàng)建類是因為創(chuàng)建的這些類都依賴于Configuration(但這樣做數(shù)據(jù)和邏輯沒有做到分離)。
SqlSession
SqlSession可能是mybatis中我們最常用的類,其實他是一個門面類,直接對外提供服務(wù)
public interface SqlSession extends Closeable {
<T> T selectOne(String statement);
<E> List<E> selectList(String statement, Object parameter);
int delete(String statement);
void rollback();
void commit();
...
}
這些方法都是直接提供給外部調(diào)用的??吹竭@些方法是不是很親切。(我個人在看源碼的時候看到一些自己用過的一些類或方法的時候都有種莫名的親近感。感覺終于和我的認(rèn)知世界有交集了)
SqlSession的創(chuàng)建
SqlSessionFactor是用于創(chuàng)建SqlSession建造者,提供給外部快速創(chuàng)建一個SqlSession。是一個工廠類,而SqlSessionFactor的創(chuàng)建則是由SqlSessionFactorBuilder。

Executor
前面說了SqlSession只是一個門面類,Executor才是負(fù)責(zé)Sql語句執(zhí)行的。因此Executor才是整個mybatis核心。Executor的實現(xiàn)類有

- BaseExecutor:看名字知道是最基礎(chǔ)Executor,其他的Executor都和這個類有一定的關(guān)系
- CachingExecutor:每次查詢的時候會先從緩存中獲取,每次有增刪改的時候會讓緩存失效。CachingExecutor其實是一個代理內(nèi),內(nèi)部代理了BaseExecutor(或其子類)。在BaseExecutor基礎(chǔ)上增加了緩存操作。
相關(guān)類
我們看一個Executor參數(shù)最多的一個方法
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
這些類都對執(zhí)行Sql有一定關(guān)系
MappedStatement
具體點(diǎn)來理解就是我們定義的Sql映射語句,例如我們xml定義的:
<select id="selectCountByPath" parameterType="java.lang.String" resultType="java.lang.Long">
select count(1) FROM config
WHERE path = #{path}
</select>
paramter
這個就是傳遞給sql映射的參數(shù),用于生成和填充動態(tài)sql語句
RowBound
限定一次查詢數(shù)據(jù)量,類很簡單,看代碼就明白,不多說
public class RowBounds {
public static final int NO_ROW_OFFSET = 0;
public static final int NO_ROW_LIMIT = Integer.MAX_VALUE;
public static final RowBounds DEFAULT = new RowBounds();
private int offset;
private int limit;
public RowBounds() {
this.offset = NO_ROW_OFFSET;
this.limit = NO_ROW_LIMIT;
}
public RowBounds(int offset, int limit) {
this.offset = offset;
this.limit = limit;
}
}
ResultHandler
這個和本地緩存有關(guān),用于保存一個查詢語句的緩存對象,下次有相同的查詢語句的時候就會先嘗試從本地緩存中獲取。 注意:
,mybatis有2級緩存,第一級是CachingExecutor,第二級緩存就是mybatis的本地緩存,也就是和ResultHandler
緩存失效策略是和一級緩存一樣,任何增刪改都會清空本地緩存
CacheKey
一個查詢語句的在本地緩存中的key,根據(jù)sql語句,參數(shù)等等組成
BoundSql
這個對象就是本次實際需要執(zhí)行的sql語句有關(guān)的信息,
public class BoundSql {
private String sql;
private List<ParameterMapping> parameterMappings;
private Object parameterObject;
private Map<String, Object> additionalParameters;
private MetaObject metaParameters;
...
如果說parameter參數(shù)是實際傳入的參數(shù),那么BoundSql就是根據(jù)傳入?yún)?shù)進(jìn)行相關(guān)解析后的結(jié)果。他的創(chuàng)建在MappedStatement中,根據(jù)parameter和當(dāng)前執(zhí)行MappedStatement生成
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
Interceptor
Mybatis提供了Interceptor用于在執(zhí)行Executor之前進(jìn)行一些操作,mybatis是怎么使用Interceptor。其實就是在創(chuàng)建Executor時候,會
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;
}
這里主要是通過jdk動態(tài)代理實現(xiàn)的
public class Plugin implements InvocationHandler {
...
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
...
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
這樣在調(diào)用Executor的時候就會先判斷是否滿足Interceptor的執(zhí)行條件,滿足則會先執(zhí)行Intercepter#intercept()方法
最底層的Handler
要說直接和Jdbc打交道的就是各種Handler類,例如
- StatementHandler: 處理java.sql.Statement
- ParameterHandler: 向PreparedStatement中設(shè)置參數(shù)
- ResultSetHandler:處理sql執(zhí)行結(jié)果,并轉(zhuǎn)換成指定的類對象 上面的這些其實都不復(fù)雜,所以代碼還是比較好理解的
Transaction
每個Executor生成的時候都會把Transaction傳入,在BaseExecutor中Transaction是其成員變量,那Transaction的作用是什么呢?
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
Integer getTimeout() throws SQLException;
}
其實之前一直都沒提到過Connect誰來管理,這里可以看出來,Transaction負(fù)責(zé)了Connection的獲取,以及對這次Connect的提交和回滾等操作。這個類也是比較好理解的。Executor的commit或者rollback最后都是調(diào)用Transaction的
總結(jié)
可以看出,mybatis的源碼是比較容易閱讀的(相對于Spring等)。上面介紹了框架中的一些核心類,但是很多細(xì)節(jié)的地方值得我們?nèi)ド钔?。這個就需要我們能沉下來好好閱讀代碼。
相關(guān)文章
Java中短路運(yùn)算符與邏輯運(yùn)算符示例詳解
這篇文章主要給大家介紹了關(guān)于Java中短路運(yùn)算符與邏輯運(yùn)算符的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
Springboot中@Value注解的場景用法及可能遇到的問題詳解
這篇文章主要給大家介紹了關(guān)于Springboot中@Value注解的場景用法及可能遇到問題的相關(guān)資料, @Value通常用于注入外部化屬性,即外部配置屬性的注入,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11
使用ObjectMapper把Json轉(zhuǎn)換為復(fù)雜的實體類
這篇文章主要介紹了使用ObjectMapper把Json轉(zhuǎn)換為復(fù)雜的實體類操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
java.lang.NullPointerException出現(xiàn)的幾種原因及解決方案
這篇文章主要介紹了java.lang.NullPointerException出現(xiàn)的幾種原因及解決方案,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-05-05

