mybatis源码学习-1-调试环境

发布时间 2023-09-04 00:16:20作者: JiuYou2020

写在前面,这里会有很多借鉴的内容,有以下三个原因

  1. 本博客只是作为本人学习记录并用以分享,并不是专业的技术型博客
  2. 笔者是位刚刚开始尝试阅读源码的人,对源码的阅读流程乃至整体架构并不熟悉,观看他人博客可以帮助我快速入门
  3. 如果只是笔者自己观看,难免会有很多弄不懂乃至理解错误的地方,观看他人的体会能有效改善这个问题

1. 依赖工具

  1. Maven
  2. Git
  3. Idea

2. 源码拉取

从官方仓库 https://github.com/mybatis/mybatis-3 Fork 出属于自己的仓库。

本文使用的 MyBatis 版本为 3.5.0-SNAPSHOT

3. 调试

这里(下面所有内容)仅仅是用于调试环境,请不用着急于在这里就弄懂调用流程

MyBatis 想要调试,非常方便,只需要打开 org.apache.ibatis.autoconstructor.AutoConstructorTest 单元测试类,任意一个单元测试方法,右键,开始调试即可。

当然,考虑到让大家更好的理解 AutoConstructorTest 这个类,下面,我们还是详细解析下。AutoConstructorTest 所在在 autoconstructor 包下,整体结构如下:

包结构图

4. mybatis-config.xml

mybatis-config.xml ,MyBatis 配置文件。XML 如下:

<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <!-- autoMappingBehavior should be set in each test case -->

    <environments default="development">
        <environment id="development">
            <!-- 配置事务管理 -->
            <transactionManager type="JDBC">
                <property name="" value=""/>
            </transactionManager>
            <!-- 配置数据源  -->
            <dataSource type="UNPOOLED">
                <property name="driver" value="org.hsqldb.jdbcDriver"/>
                <property name="url" value="jdbc:hsqldb:mem:automapping"/>
                <property name="username" value="sa"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 扫描 Mapper 文件  -->
    <mappers>
        <mapper resource="org/apache/ibatis/autoconstructor/AutoConstructorMapper.xml"/>
    </mappers>

</configuration>
  • <environments /> 标签中,配置了事务管理和数据源。考虑到减少外部依赖,所以使用了 HSQLDB
  • <mappers /> 标签中,配置了需要扫描的 Mapper 文件。目前,仅仅扫描 AutoConstructorMapper.xml 文件。

5. AutoConstructorMapper.xml

AutoConstructorMapper.xml ,Mapper 文件。代码如下:

<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.apache.ibatis.autoconstructor.AutoConstructorMapper">
</mapper>
  • 对应的接口为 org.apache.ibatis.autoconstructor.AutoConstructorMapper

5.1 AutoConstructorMapper

public interface AutoConstructorMapper {

    // ========== PrimitiveSubject ==========

    @Select("SELECT * FROM subject WHERE id = #{id}")
    PrimitiveSubject getSubject(final int id);
    @Select("SELECT * FROM subject")
    List<PrimitiveSubject> getSubjects();

    // ========== AnnotatedSubject ==========

    @Select("SELECT * FROM subject")
    List<AnnotatedSubject> getAnnotatedSubjects();

    // ========== BadSubject ==========

    @Select("SELECT * FROM subject")
    List<BadSubject> getBadSubjects();

    // ========== ExtensiveSubject ==========

    @Select("SELECT * FROM extensive_subject")
    List<ExtensiveSubject> getExtensiveSubject();

}
  • 使用注解的方法,编写 SQL 。

6. CreateDB.sql

CreateDB.sql 文件,用于单元测试里,初始化数据库的数据。如下:

DROP TABLE subject
IF EXISTS;

DROP TABLE extensive_subject
IF EXISTS;

CREATE TABLE subject (
  id     INT NOT NULL,
  name   VARCHAR(20),
  age    INT NOT NULL,
  height INT,
  weight INT,
  active BIT,
  dt     TIMESTAMP
);

CREATE TABLE extensive_subject (
  aByte      TINYINT,
  aShort     SMALLINT,
  aChar      CHAR,
  anInt      INT,
  aLong      BIGINT,
  aFloat     FLOAT,
  aDouble    DOUBLE,
  aBoolean   BIT,
  aString    VARCHAR(255),
  anEnum     VARCHAR(50),
  aClob      LONGVARCHAR,
  aBlob      LONGVARBINARY,
  aTimestamp TIMESTAMP
);

INSERT INTO subject VALUES
  (1, 'a', 10, 100, 45, 1, CURRENT_TIMESTAMP),
  (2, 'b', 10, NULL, 45, 1, CURRENT_TIMESTAMP),
  (2, 'c', 10, NULL, NULL, 0, CURRENT_TIMESTAMP);

INSERT INTO extensive_subject
VALUES
  (1, 1, 'a', 1, 1, 1, 1.0, 1, 'a', 'AVALUE', 'ACLOB', 'aaaaaabbbbbb', CURRENT_TIMESTAMP),
  (2, 2, 'b', 2, 2, 2, 2.0, 2, 'b', 'BVALUE', 'BCLOB', '010101010101', CURRENT_TIMESTAMP),
  (3, 3, 'c', 3, 3, 3, 3.0, 3, 'c', 'CVALUE', 'CCLOB', '777d010078da', CURRENT_TIMESTAMP);
  • 创建了 subject 表,并初始化三条数据。
  • 创建了 extensive_subject 表,并初始化三条数据。

7. POJO

在 AutoConstructorMapper 中,我们可以看到有四个 POJO 类。但是,从 CreateDB.sql 中,实际只有两个表。这个是为什么呢?继续往下看噢。

7.1 AnnotatedSubject

package org.apache.ibatis.autoconstructor;

import org.apache.ibatis.annotations.AutomapConstructor;

public class AnnotatedSubject {

    private final int id;
    private final String name;
    private final int age;
    private final int height;
    private final int weight;

    public AnnotatedSubject(final int id, final String name, final int age, final int height, final int weight) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.height = height;
        this.weight = weight;
    }

    @AutomapConstructor
    public AnnotatedSubject(final int id, final String name, final int age, final Integer height, final Integer weight) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.height = height == null ? 0 : height;
        this.weight = weight == null ? 0 : weight;
    }

}
  • 对应 subject 表。
  • @AutomapConstructor注解,表示 MyBatis 查询后,在创建 AnnotatedSubject 对象,使用该构造方法。
    • ? 实际场景下,非常少使用这个注解,甚至 Google 上资料也不多。

7.1.1 PrimitiveSubject

package org.apache.ibatis.autoconstructor;

import java.util.Date;

public class PrimitiveSubject {

    private final int id;
    private final String name;
    private final int age;
    private final int height;
    private final int weight;
    private final boolean active;
    private final Date dt;

    public PrimitiveSubject(final int id, final String name, final int age, final int height, final int weight, final boolean active, final Date dt) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.height = height;
        this.weight = weight;
        this.active = active;
        this.dt = dt;
    }

}
  • 对应的也是 subject 表。
  • 和 AnnotatedSubject 不同,在其构造方法上,weightheight 方法参数的类型是 int ,而不是 Integer 。那么,如果 subject 表中的记录,这两个字段为 NULL 时,会创建 PrimitiveSubject 对象报错。

7.1.2 BadSubject

package org.apache.ibatis.autoconstructor;

public class BadSubject {

    private final int id;
    private final String name;
    private final int age;
    private final Height height;
    private final Double weight;

    public BadSubject(final int id, final String name, final int age, final Height height, final Double weight) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.height = height;
        this.weight = weight == null ? 0 : weight;
    }

    private class Height {

    }

}
  • 对应的也是 subject 表。
  • 和 AnnotatedSubject 不同,在其构造方法上,height 方法参数的类型是 Height ,而不是 Integer 。因为 MyBatis 无法识别 Height 类,所以会创建 BadSubject 对象报错。

老艿艿:一般情况下,POJO 对象里,不使用基本类型。

7.2 ExtensiveSubject

package org.apache.ibatis.autoconstructor;

public class ExtensiveSubject {
    private final byte aByte;
    private final short aShort;
    private final char aChar;
    private final int anInt;
    private final long aLong;
    private final float aFloat;
    private final double aDouble;
    private final boolean aBoolean;
    private final String aString;

    // enum types
    private final TestEnum anEnum;

    // array types

    // string to lob types:
    private final String aClob;
    private final String aBlob;

    public ExtensiveSubject(final byte aByte,
                            final short aShort,
                            final char aChar,
                            final int anInt,
                            final long aLong,
                            final float aFloat,
                            final double aDouble,
                            final boolean aBoolean,
                            final String aString,
                            final TestEnum anEnum,
                            final String aClob,
                            final String aBlob) {
        this.aByte = aByte;
        this.aShort = aShort;
        this.aChar = aChar;
        this.anInt = anInt;
        this.aLong = aLong;
        this.aFloat = aFloat;
        this.aDouble = aDouble;
        this.aBoolean = aBoolean;
        this.aString = aString;
        this.anEnum = anEnum;
        this.aClob = aClob;
        this.aBlob = aBlob;
    }

    public enum TestEnum {
        AVALUE, BVALUE, CVALUE;
    }

}
  • 对应的也是 extensive_subject 表。
  • 这是个复杂对象,基本涵盖了各种类型的数据。

8. AutoConstructorTest

org.apache.ibatis.autoconstructor.AutoConstructorTest ,单元测试类。

8.1 setUp

private static SqlSessionFactory sqlSessionFactory;

@BeforeClass
public static void setUp() throws Exception {
    // create a SqlSessionFactory
    // 创建 SqlSessionFactory 对象,基于 mybatis-config.xml 配置文件。
    try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml")) {
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    }

    // populate in-memory database
    // 初始化数据到内存数据库,基于 CreateDB.sql SQL 文件。
    BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
            "org/apache/ibatis/autoconstructor/CreateDB.sql");
}
  • 创建 SqlSessionFactory 对象,基于 mybatis-config.xml 配置文件。
  • 初始化数据到内存数据库,基于 CreateDB.sql SQL 文件。