目錄
1.1 建立mybatis配置檔案
1.2 建立資料庫表
1.3 建立javabean
1.4 建立mapper對映檔案
1.5 執行測試
2.3 SqlSession
2.4 Executor
2.5 StatementHandler
2.6 ParameterHandler
2.7 ResultSetHandler
2.8 TypeHandler
三.總結
寫這篇部落格,是因為一個面試題“能介紹一下MyBatis執行sql的整個流程嗎?”
之前也看過一下部落格,知道大概的流程,無非就是:啟動->解析配置檔案->建立executor->繫結引數->執行sql->結果集對映,因為沒有看過原始碼,聽別人解釋,自己心裡還是有點虛的,畢竟也不知道別人講的是否正確,使用MyBatis也快一年了,所以寫這篇部落格總結一下。
原文地址:https://www.cnblogs.com/-beyond/p/13232624.html
一.mybatis極簡示例
1.1 建立mybatis配置檔案
檔名隨意,這裡命名為為mybatis-config.xml,內容如下:
<?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> <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://localhost:3306/test"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <mapper resource="cn/ganlixin/mappers/UserMapper.xml"/> </mappers> </configuration>
1.2 建立資料表
在test資料庫中建立user表:
create table user ( id int not null primary key auto_increment, name char(20) not null, age int default 0, addr char(20) default '' ) engine=innodb default charset=utf8mb4; insert into user value (1, 'aaa', 18, '北京');
1.3 建立javabean
建立User類,包含4個欄位:
package cn.ganlixin.model; public class User { private Integer id; private String name; private Integer age; private String addr; // 省略getter、setter、toString }
1.4 建立mapper檔案
建立UserMapper.xml檔案:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.ganlixin.mappers.UserMapper"> <select id="selectUserById" parameterType="int" resultType="cn.ganlixin.model.User"> select * from user where id=#{id} </select> <delete id="deleteUserById" parameterType="int"> delete from user where id=#{id} </delete> </mapper>
1.5 執行測試
public class TestMyBatis { public static void main(String[] args) throws IOException { String config = "resources/mybatis-config.xml"; InputStream resourceAsStream = Resources.getResourceAsStream(config); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); User user = (User) sqlSession.selectOne("cn.ganlixin.mappers.UserMapper.selectUserById", 1); System.out.println(user); // User{id=1, name='aaa', age=18, addr='北京'} // 下面的delete不會生效,因為mybatis預設沒有開啟自動提交 int delete = sqlSession.delete("cn.ganlixin.mappers.UserMapper.deleteUserById", 1); System.out.println(delete); // 1 } }
到這裡,一個簡單的mybatis使用示例就完成了,後面的分析根據上面這個TestMyBatis的執行流程進行分析的。
二.mybatis的幾大“元件”
我這裡說的“元件”,可以理解為Mybatis執行過程中的很重要的幾個模組。
2.1 SqlSessionFactoryBuilder
從名稱長可以看出來使用的建造者設計模式(Builder),用於構建SqlSessionFactory物件
1.解析mybatis的xml配置檔案,然後建立Configuration物件(對應<configuration>標籤);
2.根據建立的Configuration物件,建立SqlSessionFactory(預設使用DefaultSqlSessionFactory);
2.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(); }
2.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(); } }
2.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為例:
2.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為例:
2.6 ParameterHandler
ParameterHandler的功能就是sql預處理後,進行設定引數:
public interface ParameterHandler { Object getParameterObject(); void setParameters(PreparedStatement ps) throws SQLException; }
ParamterHandler有一個DefaultParameterHandler,下面是其重寫setParameters的程式碼:
2.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方法,如下:
2.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>
TypeHandler,可以對某個欄位按照xml中配置的型別進行設定值,比如設定sql的uid引數時,型別為INTEGER(jdbcType)。
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; }
三.總結
對於上面的一些元件進行介紹後,這裡將其串聯起來,那麼就能知道mybatis執行sql的具體流程了,於是我花了下面這個流程:
原文地址:https://www.cnblogs.com/-beyond/p/13232624.html