Mybatis原始碼主流程分析

語言: CN / TW / HK

背景介紹

Mybatis 是一個 Data Mapper Framework,屬於 ORM 框架;旨在提供更簡單,更方便地完成操作資料庫功能,減輕開發⼈員的⼯作量,消除程式冗餘程式碼。

功能框架

主要從功能⽅面配合原始碼進行解析 Mybatis 的實現過程和原理,功能框架圖如下:

接⼝層:

  1. 使用 Mybatis 提供 API 操作資料庫,傳遞 statementId 和引數 map 給 sqlSession;

  2. 使⽤ mapper 接⼝⽅式操作資料庫,每個接⼝口⽅方法對應⼀一個 mapper 節點

處理層:

  1. 處理傳入引數,同時進行型別轉換 —— ParameterHandler 分析

  2. 根據傳入引數,使⽤ ognl 動態生成 SQL 語句 —— StatementHandler 分析

  3. 將 SQL 語句和引數交於執行器進⾏獲取結果 —— Executor 分析

  4. 處理結果集,型別轉換 —— ResultSetHandler 分析

框架層:

  1. 資料來源和連線池管理理 —— 資料來源與連線池分析

  2. 事務管理理 —— 事務分析

  3. 快取管理理(⼀級快取和二級快取) —— 快取分析

  4. 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、法務、財務、採購、差旅與報銷等系統。歡迎各位加入我們。

掃碼發現職位&投遞簡歷

官網投遞:https://job.toutiao.com/s/FyL7DRg