MyBatis執行流程的各階段介紹
目錄
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是一個介面,定義了很多操作資料庫的方法宣告:
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