01-MyBatis啟動流程分析

  • 2019 年 11 月 13 日
  • 筆記


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