MyBatis啟動流程分析
阿新 • • 發佈:2020-12-11
技術標籤:技術
目錄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);