Mybatis框架及原理例項分析
摘要
本篇文章只是個人閱讀mybatis原始碼總結的經驗或者個人理解mybatis的基本輪廓,作為拋磚引玉的功能,希望對你有幫助,如果需要深入瞭解細節還需親自去閱讀原始碼。
mybatis基本架構
mybatis的原始碼應該算是比較容易閱讀的,首先mybatis核心功能就是執行Sql語句,但在其基礎上又有許多增強的地方(動態Sql,ORM等)。看一個框架的時候,第一步是對整個框架有一個大體的瞭解。例如mybatis,我們可以從初始化到完成一個sql請求為主線,看一下涉及了哪些類。我個人總結了一下,mybatis的框架主要的核心類有4個
Configuration
Configuration就是用於解析、儲存、處理Mybatis的配置內容,包括了
- mybatis基本配置,例如支援資料庫中的欄位支援下標轉駝峰mapUnderscoreToCamelCase=true等等,參看Mybatis配置說明
- SqlMapper管理,也就是通過xml或者註解寫的一些sql對映。相關的類可以檢視原始碼中MappedStatement類。
- 建立類,Configuration還有一些建立類的功能,例如Executor、StatementHandler。這個2個類後面還會說到
小節Configuration
總結Configuration的功能,當然,如何讀取和解析相關檔案是Configuration中大部分程式碼做的事。這些都是為了準備後面mybatis執行的基本條件。Configuration中建立類是因為建立的這些類都依賴於Configuration(但這樣做資料和邏輯沒有做到分離)。
SqlSession
SqlSession可能是mybatis中我們最常用的類,其實他是一個門面類,直接對外提供服務
1 2 3 4 5 6 7 8 9 |
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();
...
}
|
這些方法都是直接提供給外部呼叫的。看到這些方法是不是很親切。(我個人在看原始碼的時候看到一些自己用過的一些類或方法的時候都有種莫名的親近感。感覺終於和我的認知世界有交集了)
SqlSession的建立
SqlSessionFactor是用於建立SqlSession建造者,提供給外部快速建立一個SqlSession。是一個工廠類,而SqlSessionFactor的建立則是由SqlSessionFactorBuilder。
Executor
前面說了SqlSession只是一個門面類,Executor才是負責Sql語句執行的。因此Executor才是整個mybatis核心。Executor的實現類有
- BaseExecutor:看名字知道是最基礎Executor,其他的Executor都和這個類有一定的關係
- CachingExecutor:每次查詢的時候會先從快取中獲取,每次有增刪改的時候會讓快取失效。CachingExecutor其實是一個代理內,內部代理了BaseExecutor(或其子類)。在BaseExecutor基礎上增加了快取操作。
相關類
我們看一個Executor引數最多的一個方法
1 |
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
|
這些類都對執行Sql有一定關係
MappedStatement
具體點來理解就是我們定義的Sql對映語句,例如我們xml定義的:
1 2 3 4 |
<select id= "selectCountByPath" parameterType= "java.lang.String" resultType= "java.lang.Long" >
select count( 1 ) FROM config
WHERE path = #{path}
</select>
|
paramter
這個就是傳遞給sql對映的引數,用於生成和填充動態sql語句
RowBound
限定一次查詢資料量,類很簡單,看程式碼就明白,不多說
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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
這個和本地快取有關,用於儲存一個查詢語句的快取物件,下次有相同的查詢語句的時候就會先嚐試從本地快取中獲取。 注意:
,mybatis有2級快取,第一級是CachingExecutor,第二級快取就是mybatis的本地快取,也就是和ResultHandler
快取失效策略是和一級快取一樣,任何增刪改都會清空本地快取
CacheKey
一個查詢語句的在本地快取中的key,根據sql語句,引數等等組成
BoundSql
這個物件就是本次實際需要執行的sql語句有關的資訊,
1 2 3 4 5 6 7 |
public class BoundSql {
private String sql;
private List<ParameterMapping> parameterMappings;
private Object parameterObject;
private Map<String, Object> additionalParameters;
private MetaObject metaParameters;
...
|
如果說parameter引數是實際傳入的引數,那麼BoundSql就是根據傳入引數進行相關解析後的結果。他的建立在MappedStatement中,根據parameter和當前執行MappedStatement生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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用於在執行Executor之前進行一些操作,mybatis是怎麼使用Interceptor。其實就是在建立Executor時候,會
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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動態代理實現的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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);
}
}
|
這樣在呼叫Executor的時候就會先判斷是否滿足Interceptor的執行條件,滿足則會先執行Intercepter#intercept()方法
最底層的Handler
要說直接和Jdbc打交道的就是各種Handler類,例如
- StatementHandler: 處理java.sql.Statement
- ParameterHandler: 向PreparedStatement中設定引數
- ResultSetHandler:處理sql執行結果,並轉換成指定的類物件 上面的這些其實都不復雜,所以程式碼還是比較好理解的
Transaction
每個Executor生成的時候都會把Transaction傳入,在BaseExecutor中Transaction是其成員變數,那Transaction的作用是什麼呢?
1 2 3 4 5 6 7 |
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負責了Connection的獲取,以及對這次Connect的提交和回滾等操作。這個類也是比較好理解的。Executor的commit或者rollback最後都是呼叫Transaction的
總結
可以看出,mybatis的原始碼是比較容易閱讀的(相對於Spring等)。上面介紹了框架中的一些核心類,但是很多細節的地方值得我們去深挖。這個就需要我們能沉下來好好閱讀程式碼。