Mybatis原始碼主流程分析
背景介紹
Mybatis 是一個 Data Mapper Framework,屬於 ORM 框架;旨在提供更簡單,更方便地完成操作資料庫功能,減輕開發⼈員的⼯作量,消除程式冗餘程式碼。
功能框架
主要從功能⽅面配合原始碼進行解析 Mybatis 的實現過程和原理,功能框架圖如下:
接⼝層:
-
使用 Mybatis 提供 API 操作資料庫,傳遞 statementId 和引數 map 給 sqlSession;
-
使⽤ mapper 接⼝⽅式操作資料庫,每個接⼝口⽅方法對應⼀一個 mapper 節點
;
處理層:
-
處理傳入引數,同時進行型別轉換 —— ParameterHandler 分析
-
根據傳入引數,使⽤ ognl 動態生成 SQL 語句 —— StatementHandler 分析
-
將 SQL 語句和引數交於執行器進⾏獲取結果 —— Executor 分析
-
處理結果集,型別轉換 —— ResultSetHandler 分析
框架層:
-
資料來源和連線池管理理 —— 資料來源與連線池分析
-
事務管理理 —— 事務分析
-
快取管理理(⼀級快取和二級快取) —— 快取分析
-
SQL 語句配置⽅式管理(XML和註解) —— 配置⽅方式分析
Mybatis 中⽐較核心重要且常⽤的類:
- Configuration MyBatis 所有的配置資訊都維持在 Configuration 物件之中。
- TypeHandler 負責 java 資料型別和 jdbc 資料型別之間的對映和轉換。
- MappedStatement 維護了一條
- StatementHandler 封裝了 JDBC Statement 操作,負責對 JDBC statement 的操作,如設定引數、將 Statement 結果集轉換成 List 集合。
- ParameterHandler 負責對⽤戶傳遞的引數轉換成 JDBC Statement 所需要的引數。
- SqlSource 負責根據使用者傳遞的 parameterObject,動態地生成 SQL 語句,將資訊封裝到 BoundSql 物件中,並返回。
- BoundSql 表示動態生成的 SQL 語句以及相應的引數資訊。
- Executor Mybatis 執行器,是 Mybatis 排程的核心,負責 SQL 語句的生成和查詢快取的維護工作。
- SqlSession 作為 Mybatis 工作的主要頂層 API,表示和資料庫互動的會話,完成必要資料庫增刪改查功能。
- ResultSetHandler 負責將 JDBC 返回的 ResultSet 結果集物件轉換成 List 型別的集合。
Mybatis 使用
java
// 1.初始化
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); inputStream.close();
// 2.獲取sqlSession物件
SqlSession session = sqlSessionFactory.openSession();
// 3.獲取dao代理物件
DspUserDao dspUserDao = session.getMapper(DspUserDao.class);
// 4.執行sql
DspUser user = dspUserDao.selectUserByName("test"); System.out.println(user.getUserName());
原始碼分析
配置檔案初始化流程
1. SqlSessionFactoryBuilder
java
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionFactoryBuilder 為⼊口點進行傳⼊解析 xml。
2. XMLConfigBuilder
java
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
build(parser.parse());
將 XML 進⾏節點解析,封裝為 configuration 物件。
java
private void parseConfiguration(XNode root) {
try {
// 解析properties
this.propertiesElement(root.evalNode("properties"));
// 解析settings,設定預設值
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
// 解析typeAliases,設定別名
this.typeAliasesElement(root.evalNode("typeAliases"));
// plugins,設定攔截器鏈
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); this.reflectorFactoryElement(root.evalNode("reflectorFactory")); this.settingsElement(settings);
// environments,設定資料來源,tx相關資訊
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析typeHandlers,型別處理類
this.typeHandlerElement(root.evalNode("typeHandlers"));
// 解析mapper,處理sql相關資訊
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
3.sql.xml解析⼊口
java
public void parse() {
if (!this.configuration.isResourceLoaded(this.resource)) {
this.configurationElement(this.parser.evalNode("/mapper"));//Map<String, MappedStatement> mappedStatements
this.configuration.addLoadedResource(this.resource);
this.bindMapperForNamespace();//Map<Class<?>, MapperProxyFactory<?>> knownMappers
}
this.parsePendingResultMaps();
this.parsePendingCacheRefs();
this.parsePendingStatements();
}
獲取 sqlSession 流程
1. DefaultSqlSessionFactory
java
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
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;
}
2. configuration建立executor物件
java
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;
}
獲取 dao 代理理物件流程
1. DefaultSqlSession獲取mapper代理理物件
java
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
2. MapperProxyFactory 建立 mapperProxy 的代理理物件
java
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);
}
執行查詢流程
1. mapperProxy 代理理物件調⽤用 invoke
java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 若執⾏的object類的方法,直接調⽤
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
// 快取中獲取MapperMethod
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
2. 建立 MapperMethod 執⾏操作
java
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
}
// 根據sql型別進⾏執行
public Object execute(SqlSession sqlSession, Object[] args){
....省略
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
....省略
}
3. DefaultSqlSession 執⾏ select 操作
java
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
// 從configuration中獲取MappedStatement物件
MappedStatement ms = this.configuration.getMappedStatement(statement);
// 通過executor執行MappedStatement
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}
4. Executor
baseExecutor類
java
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 獲取動態⽣成的SQL語句以及相應的引數資訊
BoundSql boundSql = ms.getBoundSql(parameter);
// 快取當前查詢
CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
....省略
// simpleExecutor執⾏查詢
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
....省略
}
simpleExecutor類
java
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
// 建立PreparedStatementHandler預編譯處理類,包含parameterHandler和resultSetHandler
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 設定引數資訊
stmt = this.prepareStatement(handler, ms.getStatementLog());
// 執⾏行行sql,並處理理結果集
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
5.建立StatementHandler
java
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 建立PreparedStatementHandler預編譯處理類,包含parameterHandler和resultSetHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
6.parameterHandler設定引數資訊
java
public void parameterize(Statement statement) throws SQLException {
this.parameterHandler.setParameters((PreparedStatement)statement);
}
7. PreparedStatementHandler執行sql,並處理結果集
java
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return this.resultSetHandler.handleResultSets(ps);
}
總結
加入我們
我們來自位元組跳動飛書商業應用研發部(Lark Business Applications),目前我們在北京、深圳、上海、武漢、杭州、成都、廣州、三亞都設立了辦公區域。我們關注的產品領域主要在企業經驗管理軟體上,包括飛書 OKR、飛書績效、飛書招聘、飛書人事等 HCM 領域系統,也包括飛書審批、OA、法務、財務、採購、差旅與報銷等系統。歡迎各位加入我們。
掃碼發現職位&投遞簡歷
官網投遞:http://job.toutiao.com/s/FyL7DRg
- 解鎖抖音世界盃的畫質優化實踐
- Kafka 架構、核心機制和場景解讀
- 頭條穩定性治理:ARC 環境中對 Objective-C 物件賦值的 Crash 隱患
- 位元組跳動模型大規模部署實戰
- 「飛書績效」寬表SQL自動生成邏輯淺析
- Mybatis原始碼主流程分析
- 推薦系統的Bias
- 抖音 Android 基礎技術大揭祕!| 位元組跳動技術沙龍第十期
- 基於序列標註模型的主動學習實踐
- 加密技術科普
- 二維碼掃描優化
- 前端監控系列4 | SDK 體積與效能優化實踐
- 特效側使用者體驗優化實戰 —— 包體積篇
- 深入理解 Android Studio Sync 流程
- 選擇 Go 還是 Rust?CloudWeGo-Volo 基於 Rust 語言的探索實踐
- 初探自然語言預訓練技術演進之路
- 高效能 RPC 框架 CloudWeGo-Kitex 內外統一的開源實踐
- 開源 1 週年突破 1w Star - CloudWeGo 開源社群實踐分享
- Go 語言官方依賴注入工具 Wire 使用指北
- prompt 綜述