01-MyBatis啟動流程分析

来源:https://www.cnblogs.com/54chensongxia/archive/2019/11/13/11850420.html
-Advertisement-
Play Games

[TOC] MyBatis簡單介紹 MyBatis是一個持久層框架,使用簡單,學習成本較低。可以執行自己手寫的SQL語句,比較靈活。但是MyBatis的自動化程度不高,移植性也不高,有時從一個資料庫遷移到另外一個資料庫的時候需要自己修改配置。 一個Mybatis最簡單的使用列子如下: 從配置文件(通 ...


目錄



MyBatis簡單介紹

MyBatis是一個持久層框架,使用簡單,學習成本較低。可以執行自己手寫的SQL語句,比較靈活。但是MyBatis的自動化程度不高,移植性也不高,有時從一個資料庫遷移到另外一個資料庫的時候需要自己修改配置。

一個Mybatis最簡單的使用列子如下:

public class UserDaoTest {

    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void setUp() throws Exception{
        ClassPathResource resource = new ClassPathResource("mybatis-config.xml");
        InputStream inputStream = resource.getInputStream();
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    @Test
    public void selectUserTest(){
        String id = "{0003CCCA-AEA9-4A1E-A3CC-06D884BA3906}";
        SqlSession sqlSession = sqlSessionFactory.openSession();
        CbondissuerMapper cbondissuerMapper = sqlSession.getMapper(CbondissuerMapper.class);
        Cbondissuer cbondissuer = cbondissuerMapper.selectByPrimaryKey(id);
        System.out.println(cbondissuer);
        sqlSession.close();
    }

}
  • 從配置文件(通常是XML文件)得到SessionFactory;
  • 從SessionFactory得到SQLSession;
  • 通過SqlSession進行CRUD和事務的操作;
  • 執行完相關操作之後關閉Session。

啟動流程分析

本博客只涉及創建SessionFactory,以及從SessionFactory獲取SqlSession的流程。具體執行Sql的流程會在其他博客中分析。

ClassPathResource resource = new ClassPathResource("mybatis-config.xml");
InputStream inputStream = resource.getInputStream();
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

通過上面代碼發現,創建SqlSessionFactory的代碼在SqlSessionFactoryBuilder中,進去一探究竟:

//整個過程就是將配置文件解析成Configration對象,然後創建SqlSessionFactory的過程
//Configuration是SqlSessionFactory的一個內部屬性
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
    
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

下麵我們看下解析配置文件過程中的一些細節。

先給出一個配置文件的列子:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--SqlSessionFactoryBuilder中配置的配置文件的優先順序最高;config.properties配置文件的優先順序次之;properties標簽中的配置優先順序最低 -->
    <properties resource="org/mybatis/example/config.properties">
      <property name="username" value="dev_user"/>
      <property name="password" value="F2Fa3!33TYyg"/>
    </properties>

    <!--一些重要的全局配置-->
    <settings>
    <setting name="cacheEnabled" value="true"/>
    <!--<setting name="lazyLoadingEnabled" value="true"/>-->
    <!--<setting name="multipleResultSetsEnabled" value="true"/>-->
    <!--<setting name="useColumnLabel" value="true"/>-->
    <!--<setting name="useGeneratedKeys" value="false"/>-->
    <!--<setting name="autoMappingBehavior" value="PARTIAL"/>-->
    <!--<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>-->
    <!--<setting name="defaultExecutorType" value="SIMPLE"/>-->
    <!--<setting name="defaultStatementTimeout" value="25"/>-->
    <!--<setting name="defaultFetchSize" value="100"/>-->
    <!--<setting name="safeRowBoundsEnabled" value="false"/>-->
    <!--<setting name="mapUnderscoreToCamelCase" value="false"/>-->
    <!--<setting name="localCacheScope" value="STATEMENT"/>-->
    <!--<setting name="jdbcTypeForNull" value="OTHER"/>-->
    <!--<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>-->
    <!--<setting name="logImpl" value="STDOUT_LOGGING" />-->
    </settings>

    <typeAliases>

    </typeAliases>

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!--預設值為 false,當該參數設置為 true 時,如果 pageSize=0 或者 RowBounds.limit = 0 就會查詢出全部的結果-->
            <!--如果某些查詢數據量非常大,不應該允許查出所有數據-->
            <property name="pageSizeZero" value="true"/>
        </plugin>
    </plugins>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://10.59.97.10:3308/windty"/>
                <property name="username" value="windty_opr"/>
                <property name="password" value="windty!234"/>
            </dataSource>
        </environment>
    </environments>

    <databaseIdProvider type="DB_VENDOR">
        <property name="MySQL" value="mysql" />
        <property name="Oracle" value="oracle" />
    </databaseIdProvider>

    <mappers>
        <!--這邊可以使用package和resource兩種方式載入mapper-->
        <!--<package name="包名"/>-->
        <!--<mapper resource="./mappers/SysUserMapper.xml"/>-->
        <mapper resource="./mappers/CbondissuerMapper.xml"/>
    </mappers>

</configuration>

下麵是解析配置文件的核心方法:

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      //解析properties標簽,並set到Configration對象中
      //在properties配置屬性後,在Mybatis的配置文件中就可以使用${key}的形式使用了。
      propertiesElement(root.evalNode("properties"));
      
      //解析setting標簽的配置
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      //添加vfs的自定義實現,這個功能不怎麼用
      loadCustomVfs(settings);
        
      //配置類的別名,配置後就可以用別名來替代全限定名
      //mybatis預設設置了很多別名,參考附錄部分
      typeAliasesElement(root.evalNode("typeAliases"));
        
      //解析攔截器和攔截器的屬性,set到Configration的interceptorChain中
      //MyBatis 允許你在已映射語句執行過程中的某一點進行攔截調用。預設情況下,MyBatis 允許使用插件來攔截的方法調用包括:
      //Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
        //ParameterHandler (getParameterObject, setParameters)
        //ResultSetHandler (handleResultSets, handleOutputParameters)
        //StatementHandler (prepare, parameterize, batch, update, query)
      pluginElement(root.evalNode("plugins"));
      
      //Mybatis創建對象是會使用objectFactory來創建對象,一般情況下不會自己配置這個objectFactory,使用系統預設的objectFactory就好了
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
       
      //設置在setting標簽中配置的配置
      settingsElement(settings);
   
      //解析環境信息,包括事物管理器和數據源,SqlSessionFactoryBuilder在解析時需要指定環境id,如果不指定的話,會選擇預設的環境;
      //最後將這些信息set到Configration的Environment屬性裡面
      environmentsElement(root.evalNode("environments"));
        
      //
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        
      //無論是 MyBatis 在預處理語句(PreparedStatement)中設置一個參數時,還是從結果集中取出一個值時, 都會用類型處理器將獲取的值以合適的方式轉換成 Java 類型。解析typeHandler。
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析Mapper
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

上面解析流程結束後會生成一個Configration對象,包含所有配置信息,然後會創建一個SqlSessionFactory對象,這個對象包含了Configration對象。

下麵是openSession的過程:

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);
      //獲取執行器,這邊獲得的執行器已經代理攔截器的功能(見下麵代碼)
      final Executor executor = configuration.newExecutor(tx, execType);
      //根據獲取的執行器創建SqlSession
      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();
    }
  }
//interceptorChain生成代理類,具體參見Plugin這個類的方法
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;
  }

到此為止,我們已經獲得了SqlSession,拿到SqlSession就可以執行各種CRUD方法了。

簡單總結

對於MyBatis啟動的流程(獲取SqlSession的過程)這邊簡單總結下:

  • SqlSessionFactoryBuilder解析配置文件,包括屬性配置、別名配置、攔截器配置、環境(數據源和事務管理器)、Mapper配置等;解析完這些配置後會生成一個Configration對象,這個對象中包含了MyBatis需要的所有配置,然後會用這個Configration對象創建一個SqlSessionFactory對象,這個對象中包含了Configration對象;
  • 拿到SqlSessionFactory對象後,會調用SqlSessionFactory的openSesison方法,這個方法會創建一個Sql執行器(Executor),這個Sql執行器會代理你配置的攔截器方法
  • 獲得上面的Sql執行器後,會創建一個SqlSession(預設使用DefaultSqlSession),這個SqlSession中也包含了Configration對象,所以通過SqlSession也能拿到全局配置;
  • 獲得SqlSession對象後就能執行各種CRUD方法了。

SQL的具體執行流程見後續博客。

一些重要類總結:

  • SqlSessionFactory
  • SqlSessionFactoryBuilder
  • SqlSession(預設使用DefaultSqlSession)
  • Plugin、InterceptorChain的pluginAll方法

附錄

MyBatis內置別名轉換

//TypeAliasRegistry
registerAlias("string", String.class);

registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);

registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class);

registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);

registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);

registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);

registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);

registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);

registerAlias("ResultSet", ResultSet.class);

參考

https://blog.csdn.net/luanlouis/article/details/40422941


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • SQL Server 中 的使用 Intro SQL Server 從 2016 開始支持了一些 JSON操作,最近的項目里也是好多地方欄位直接存成了 JSON,需要瞭解一下怎麼在SQL Server 中操作 JSON. JSON支持適用於 SQL Server 2016 及以上版本 和 Azure ...
  • 第一範式(1NF):欄位具有原子性,不可再分。所有關係型資料庫系統都滿足第一範式) 資料庫表中的欄位都是單一屬性的,不可再分。例如,姓名欄位,其中的姓和名必須作為一個整體,無法區分哪部分是姓,哪部分是名,如果要區分出姓和名,必須設計成兩個獨立的欄位。 第二範式(2NF)要求實體的屬性完全依賴於主關鍵 ...
  • 一.存儲引擎簡介 1、文件系統: 1.1 操作系統組織和存取數據的一種機制。 1.2 文件系統是一種軟體。 2、文件系統類型:ext2 3 4 ,xfs 數據(centos6 ext 4 centos7 xfs) 2.1 不管使用什麼文件系統,數據內容不會變化 2.2 不同的是,存儲空間、大小、速度 ...
  • 今天在做定時任務的時候,遇到了一個比較難搞的問題,這個問題也比較有意思,現在給大家分享一下,這個定時任務的調用入口是這樣的。 // 進行 ldap同步 public void runLdapSyncJob(){ try { ldapSyncService.syncLdap(); } catch (E ...
  • RMAN命令詳解和常用彙總轉摘彙集,日後使用本文鏈接:https://blog.csdn.net/EVISWANG/article/details/50448370http://blog.itpub.net/25904686/viewspace-742701/https://blog.csdn.ne ...
  • 根據.frm和.ibd文件恢復表結構和數據 ...
  • 解決方案:(參考以下命令) 1、打開cmd,切換到mysql的bin目錄下 2、 D:\Program Files\MySQL5.1\bin mysqld.exe install Service successfully installed. 3、 D:\Program Files\MySQL5.1 ...
  • 1. GTID優缺點 MySQL傳統點位複製在5.7版本前是主要的主從複製模式,而隨著MySQL5.6版本引入GTID,並且MySQL5.7進行各方面的優化以後,在mySQL5.7(尤其是MySQL5.7.6)版本後GTID模式的主從複製方式成為一個新的選擇方式。要使用GTID模式,首先也需知其優缺 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...