MyBatis执行流程的各阶段介绍

目录

2.1SqlSessionFactoryBuilder

三.

写这篇博客,是因为一个面试题“能介绍一下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中提供了多种执行器,如下:

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()

MyBatis执行流程的各阶段介绍

上面说了,Executor是对JDBC的封装。当我们使用JDBC来执行sql时,一般会先预处理sql,也就是conn.prepareStatement(sql),获取返回的PreparedStatement对象(实现了Statement接口),再调用statement的executeXxx()来执行sql。

也就是说,Executor在执行sql的时候也是需要创建Statement对象的,下面以SimpleExecutor为例:

MyBatis执行流程的各阶段介绍

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的相关子类如下图所示:

MyBatis执行流程的各阶段介绍

以BaseStatementHandler为例:

MyBatis执行流程的各阶段介绍

2.6 ParameterHandler

ParameterHandler的功能就是sql预处理后,进行设置参数:

public interface ParameterHandler {

    Object getParameterObject();

    void setParameters(PreparedStatement ps) throws SQLException;
}

ParamterHandler有一个DefaultParameterHandler,下面是其重写setParameters的代码:

MyBatis执行流程的各阶段介绍

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方法,如下:

MyBatis执行流程的各阶段介绍

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的具体流程了,于是我花了下面这个流程:

MyBatis执行流程的各阶段介绍

原文地址: https://www.cnblogs.com/-beyond/p/13232624.html

原文 

http://www.cnblogs.com/-beyond/p/13232624.html

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » MyBatis执行流程的各阶段介绍

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址