MyBatis執行流程的各階段介紹


目錄

一.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之后,如果不執行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

   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM