从源码角度分析 MyBatis 工作原理

一、MyBatis 完整示例

这里,我将以一个入门级的示例来演示 MyBatis 是如何工作的。

注:本文后面章节中的原理、源码部分也将基于这个示例来进行讲解。完整示例源码地址

1.1. 数据库准备

在本示例中,需要针对一张用户表进行 CRUD 操作。其数据模型如下:

CREATE TABLE IF NOT EXISTS user (
    id      BIGINT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Id',
    name    VARCHAR(10)         NOT NULL DEFAULT '' COMMENT '用户名',
    age     INT(3)              NOT NULL DEFAULT 0 COMMENT '年龄',
    address VARCHAR(32)         NOT NULL DEFAULT '' COMMENT '地址',
    email   VARCHAR(32)         NOT NULL DEFAULT '' COMMENT '邮件',
    PRIMARY KEY (id)
) COMMENT = '用户表';

INSERT INTO user (name, age, address, email)
VALUES ('张三', 18, '北京', 'xxx@163.com');
INSERT INTO user (name, age, address, email)
VALUES ('李四', 19, '上海', 'xxx@163.com');

1.2. 添加 MyBatis

如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:

<dependency>
  <groupId>org.Mybatis</groupId>
  <artifactId>Mybatis</artifactId>
  <version>x.x.x</version>
</dependency>

1.3. MyBatis 配置

XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)。

本示例中只是给出最简化的配置。【示例】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.cj.jdbc.Driver" />
        <property name="url"
                  value="jdbc:mysql://127.0.0.1:3306/spring_tutorial?serverTimezone=UTC" />
        <property name="username" value="root" />
        <property name="password" value="root" />
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="Mybatis/mapper/UserMapper.xml" />
  </mappers>
</configuration>

说明:上面的配置文件中仅仅指定了数据源连接方式和 User 表的映射配置文件。

1.4 Mapper

1.4.1 Mapper.xml

个人理解,Mapper.xml 文件可以看做是 MyBatis 的 JDBC SQL 模板。【示例】UserMapper.xml 文件。

下面是一个通过 MyBatis Generator 自动生成的完整的 Mapper 文件。

<?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="io.github.dunwu.spring.orm.mapper.UserMapper">
  <resultMap id="BaseResultMap" type="io.github.dunwu.spring.orm.entity.User">
    <id column="id" jdbcType="BIGINT" property="id" />
    <result column="name" jdbcType="VARCHAR" property="name" />
    <result column="age" jdbcType="INTEGER" property="age" />
    <result column="address" jdbcType="VARCHAR" property="address" />
    <result column="email" jdbcType="VARCHAR" property="email" />
  </resultMap>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
    delete from user
    where id = #{id,jdbcType=BIGINT}
  </delete>
  <insert id="insert" parameterType="io.github.dunwu.spring.orm.entity.User">
    insert into user (id, name, age,
      address, email)
    values (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER},
      #{address,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR})
  </insert>
  <update id="updateByPrimaryKey" parameterType="io.github.dunwu.spring.orm.entity.User">
    update user
    set name = #{name,jdbcType=VARCHAR},
      age = #{age,jdbcType=INTEGER},
      address = #{address,jdbcType=VARCHAR},
      email = #{email,jdbcType=VARCHAR}
    where id = #{id,jdbcType=BIGINT}
  </update>
  <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
    select id, name, age, address, email
    from user
    where id = #{id,jdbcType=BIGINT}
  </select>
  <select id="selectAll" resultMap="BaseResultMap">
    select id, name, age, address, email
    from user
  </select>
</mapper>

1.4.2 Mapper.java

Mapper.java 文件是 Mapper.xml 对应的 Java 对象。【示例】UserMapper.java 文件

public interface UserMapper {

    int deleteByPrimaryKey(Long id);

    int insert(User record);

    User selectByPrimaryKey(Long id);

    List<User> selectAll();

    int updateByPrimaryKey(User record);

}

对比 UserMapper.java 和 UserMapper.xml 文件,不难发现:UserMapper.java 中的方法和 UserMapper.xml 的 CRUD 语句元素(  的 parameterType 属性以及  的 type 属性都可能会绑定到数据实体。这样就可以把 JDBC 操作的输入输出和 JavaBean 结合起来,更加方便、易于理解。

1.5. 测试程序

【示例】MyBatisDemo.java 文件

public class MyBatisDemo {

    public static void main(String[] args) throws Exception {
        // 1. 加载 MyBatis 配置文件,创建 SqlSessionFactory
        // 注:在实际的应用中,SqlSessionFactory 应该是单例
        InputStream inputStream = Resources.getResourceAsStream("MyBatis/MyBatis-config.xml");
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(inputStream);

        // 2. 创建一个 SqlSession 实例,进行数据库操作
        SqlSession sqlSession = factory.openSession();

        // 3. Mapper 映射并执行
        Long params = 1L;
        List<User> list = sqlSession.selectList("io.github.dunwu.spring.orm.mapper.UserMapper.selectByPrimaryKey", params);
        for (User user : list) {
            System.out.println("user name: " + user.getName());
        }
        // 输出:user name: 张三
    }

}

说明:SqlSession 接口是 MyBatis API 核心中的核心,它代表 MyBatis 和数据库一次完整会话。

  • MyBatis 会解析配置,并根据配置创建 SqlSession 。

  • 然后,MyBatis 将 Mapper 映射为 SqlSession,然后传递参数,执行 SQL 语句并获取结果。

二、MyBatis 生命周期

从源码角度分析 MyBatis 工作原理

2.1. SqlSessionFactoryBuilder

2.1.1 SqlSessionFactoryBuilder 的职责

SqlSessionFactoryBuilder 负责创建 SqlSessionFactory 实例。

SqlSessionFactoryBuilder 可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。

Configuration 类包含了对一个 SqlSessionFactory 实例你可能关心的所有内容。

从源码角度分析 MyBatis 工作原理

SqlSessionFactoryBuilder 应用了建造者设计模式,它有五个 build 方法,允许你通过不同的资源创建 SqlSessionFactory 实例。

SqlSessionFactory build(InputStream inputStream)
SqlSessionFactory build(InputStream inputStream, String environment)
SqlSessionFactory build(InputStream inputStream, Properties properties)
SqlSessionFactory build(InputStream inputStream, String env, Properties props)
SqlSessionFactory build(Configuration config)

2.1.2 SqlSessionFactoryBuilder 的生命周期

SqlSessionFactoryBuilder 可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

2.2. SqlSessionFactory

2.2.1 SqlSessionFactory 职责

SqlSessionFactory 负责创建 SqlSession 实例。

从源码角度分析 MyBatis 工作原理

SqlSessionFactory 应用了工厂设计模式,它提供了一组方法,用于创建 SqlSession 实例。

SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();

方法说明:

默认的 openSession() 方法没有参数,它会创建具备如下特性的 SqlSession:

1)事务作用域将会开启(也就是不自动提交)。

  • 将由当前环境配置的 DataSource 实例中获取 Connection 对象。

  • 事务隔离级别将会使用驱动或数据源的默认设置。

  • 预处理语句不会被复用,也不会批量处理更新。

2)TransactionIsolationLevel 表示事务隔离级别,它对应着 JDBC 的五个事务隔离级别。

3)ExecutorType 枚举类型定义了三个值:

  • ExecutorType.SIMPLE:该类型的执行器没有特别的行为。它为每个语句的执行创建一个新的预处理语句。

  • ExecutorType.REUSE:该类型的执行器会复用预处理语句。

  • ExecutorType.BATCH:该类型的执行器会批量执行所有更新语句,如果 SELECT 在多个更新中间执行,将在必要时将多条更新语句分隔开来,以方便理解。

2.2.2 SqlSessionFactory 生命周期

SQLSessionFactory 应该以单例形式在应用的运行期间一直存在。

2.3. SqlSession

2.3.1 SqlSession 职责

MyBatis 的主要 Java 接口就是 SqlSession。它包含了所有执行语句,获取映射器和管理事务等方法。详细内容可以参考:「 MyBatis 官方文档之 SqlSessions 」 。

SQLSession 类的方法可按照下图进行大致分类:

从源码角度分析 MyBatis 工作原理

2.3.2 SqlSession 生命周期

SqlSessions 是由 SqlSessionFactory 实例创建的;而 SqlSessionFactory 是由 SqlSessionFactoryBuilder 创建的。

上一篇:关于MyBatis2全程笔记


下一篇:建立简单Mybatis程序