MyBatis執行流程的各階段介紹

尋覓beyond發表於2020-07-04

目錄

一.mybatis極簡示例

  1.1 建立mybatis配置檔案

  1.2 建立資料庫表

  1.3 建立javabean

  1.4 建立mapper對映檔案

  1.5 執行測試

二.mybatis的幾大“元件”

  2.1 SqlSessionFactoryBuilder

  2.2 SqlSessionFactory

  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

   

相關文章