1. mybatis的幾大“組件”
我這里說的“組件”,可以理解為Mybatis執行過程中的很重要的幾個模塊。
1.1 SqlSessionFactoryBuilder
從名稱長可以看出來使用的建造者設計模式(Builder),用於構建SqlSessionFactory對象
1.解析mybatis的xml配置文件,然后創建Configuration對象(對應<configuration>標簽);
2.根據創建的Configuration對象,創建SqlSessionFactory(默認使用DefaultSqlSessionFactory);
1.2 SqlSessionFactory
從名稱上可以看出使用的工廠模式(Factory),用於創建並初始化SqlSession對象(數據庫會話)
1.調用openSession方法,創建SqlSession對象,可以將SqlSession理解為數據庫連接(會話);
2.openSession方法有多個重載,可以創建SqlSession關閉自動提交、指定ExecutorType、指定數據庫事務隔離級別….
package org.apache.ibatis.session; import java.sql.Connection; public interface SqlSessionFactory { /** * 使用默認配置 * 1.默認不開啟自動提交 * 2.執行器Executor默認使用SIMPLE * 3.使用數據庫默認的事務隔離級別 */ SqlSession openSession(); /** * 指定是否開啟自動提交 * @param autoCommit 是否開啟自動提交 */ SqlSession openSession(boolean autoCommit); /** * 根據已有的數據庫連接創建會話(事務) * @param connection 數據庫連接 */ SqlSession openSession(Connection connection); /** * 創建連接時,指定數據庫事務隔離級別 * @param level 事務隔離界別 */ SqlSession openSession(TransactionIsolationLevel level); /** * 創建連接時,指定執行器類型 * @param execType 執行器 */ SqlSession openSession(ExecutorType execType); SqlSession openSession(ExecutorType execType, boolean autoCommit); SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType, Connection connection); /** * 獲取Configuration對象,也就是解析xml配置文件中的<configuration>標簽后的數據 */ Configuration getConfiguration(); }
1.3 SqlSession
如果了解web開發,就應該知道cookie和session吧,SqlSession的session和web開發中的session概念類似。
session,譯為“會話、會議”,數據的有效時間范圍是在會話期間(會議期間),會話(會議)結束后,數據就清除了。
也可以將SqlSession理解為一個數據庫連接(但也不完全正確,因為創建SqlSession之后,如果不執行sql,那么這個連接是無意義的,所以數據庫連接在執行sql的時候才建立的)。
SqlSession是一個接口,定義了很多操作數據庫的方法聲明:
public interface SqlSession extends Closeable { /* 獲取數據庫連接 */ Connection getConnection(); /* 數據庫的增刪改查 */ <T> T selectOne(String statement); <E> List<E> selectList(String statement); int update(String statement, Object parameter); int delete(String statement, Object parameter); /* 事務回滾與提交 */ void rollback(); void commit(); /* 清除一級緩存 */ void clearCache(); // 此處省略了很多方法 }
SqlSession只是定義了執行sql的一些方法,而具體的實現由子類來完成,比如SqlSession有一個接口實現類DefaultSqlSession。
MyBatis中通過Executor來執行sql的,在創建SqlSession的時候(openSession),同時會創建一個Executor對象,如下:
@Override public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } 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); // 利用傳入的參數,創建executor對象 final Executor executor = configuration.newExecutor(tx, execType); 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(); } }
1.4 Executor
Executor(人稱“執行器”)是一個接口,定義了對JDBC的封裝;
MyBatis中提供了多種執行器,如下:
上面的圖中,雖然列出了5個Executor(BaseExecutor是抽象類),其實Executor只有三種:
public enum ExecutorType { SIMPLE, // 簡單 REUSE, // 復用 BATCH; // 批量 }
CacheExecutor其實是一個Executor代理類,包含一個delegate,需要創建時手動傳入(要入simple、reuse、batch三者之一);
ClosedExecutor,所有接口都會拋出異常,表示一個已經關閉的Executor;
創建SqlSession時,默認使用的是SimpleExecutor;
下面是創建Executor的代碼:org.apache.ibatis.session.Configuration#newExecutor()
上面說了,Executor是對JDBC的封裝。當我們使用JDBC來執行sql時,一般會先預處理sql,也就是conn.prepareStatement(sql),獲取返回的
PreparedStatement對象(實現了Statement接口),再調用statement的executeXxx()來執行sql。
也就是說,Executor在執行sql的時候也是需要創建Statement對象的,下面以SimpleExecutor為例
1.5 StatementHandler
在JDBC中,是調用Statement.executeXxx()來執行sql;
在MyBatis,也是調用Statement.executeXxx()來執行sql,此時就不得不提StatementHandler,可以將其理解為一個工人,他的工作包括
1.對sql進行預處理;
2.調用statement.executeXxx()執行sql;
3.將數據庫返回的結果集進行對象轉換(ORM);
public interface StatementHandler { /** * 獲取預處理對象 */ Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException; /** * 進行預處理 */ void parameterize(Statement statement) throws SQLException; /** * 批量sql(內部調用statement.addBatch) */ void batch(Statement statement) throws SQLException; /** * 執行更新操作 * @return 修改的記錄數 */ int update(Statement statement) throws SQLException; /** * 執行查詢操作 * @param statement sql生成的statement * @param resultHandler 結果集的處理邏輯 * @return 查詢結果 */ <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException; <E> Cursor<E> queryCursor(Statement statement) throws SQLException; BoundSql getBoundSql(); ParameterHandler getParameterHandler(); }
StatementHandler的相關子類如下圖所示:
以BaseStatementHandler為例:
1.6 ParameterHandler
ParameterHandler的功能就是sql預處理后,進行設置參數:
public interface ParameterHandler { Object getParameterObject(); void setParameters(PreparedStatement ps) throws SQLException; }
1.7 ResultSetHandler
當執行statement.execute()后,就可以通過statement.getResultSet()來獲取結果集,
獲取到結果集之后,MyBatis會使用ResultSetHandler來將結果集的數據轉換為Java對象(ORM映射)
public interface ResultSetHandler { /** * 從statement中獲取結果集,並將結果集的數據庫屬性字段映射到Java對象屬性 * @param stmt 已經execute的statement,調用state.getResultSet()獲取結果集 * @return 轉換后的數據 */ <E> List<E> handleResultSets(Statement stmt) throws SQLException;
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException; void handleOutputParameters(CallableStatement cs) throws SQLException; }
ResultSetHandler有一個實現類,DefaultResultHandler,其重寫handlerResultSets方法,如下:
1.8 TypeHandler
TypeHandler主要用在兩個地方:
1.參數綁定,發生在ParameterHandler.setParamenters()中。
MyBatis中,可以使用<resultMap>來定義結果的映射關系,包括每一個字段的類型,比如下面這樣:
<resultMap id="baseMap" type="cn.ganlixin.model.User"> <id column="uid" property="id" jdbcType="INTEGER" /> <result column="uname" property="name" jdbcType="VARCHAR" /> </resultMap>
2.獲取結果集中的字段值,發生在ResultSetHandler處理結果集的過程中。
TypeHandler的定義如下
public interface TypeHandler<T> { /** * 設置預處理參數 * * @param ps 預處理statement * @param i 參數位置 * @param parameter 參數值 * @param jdbcType 參數的jdbc類型 */ void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; /** * 獲取一條結果集的某個字段值 * * @param rs 一條結果集 * @param columnName 列名(字段名稱) * @return 字段值 */ T getResult(ResultSet rs, String columnName) throws SQLException; /** * 獲取一條結果集的某個字段值(按照字段的順序獲取) * * @param rs 一條結果集 * @param columnIndex 字段列的順序 * @return 字段值 */ T getResult(ResultSet rs, int columnIndex) throws SQLException; T getResult(CallableStatement cs, int columnIndex) throws SQLException; }
2. 總結
把執行的流程總結一下: