• MyBatis-3.4.2 源码学习


    持续更新…

    参考资料:【MyBatis 3 源码深度解析】

    下一篇:MyBatis整合Spring-1.3.1 源码学习

    使用 MyBatis 访问数据库大体分为四步:创建 SqlSessionFactory 对象、创建 SqlSession 对象、创建 Mapper 接口的代理对象、通过代理对象调用方法执行 SQL,分别对应第四、五、六、七部分。

    一、环境搭建

    MyBatis 的主要作者是 Clinton Begin,框架中大部分的代码都是他写的;
    MyBatis 封装了 JDBC 的操作,使用户专注于 SQL 语句的编写;
    MyBatis 属于半自动的 ORM 框架(对象关系映射),可以通过 Mapper.xml 文件完成请求参数、查询结果中数据库数据类型与 Java 数据类型的映射;

    1、仓库地址

    GITEE仓库地址

    2、搭建步骤

    准备工作:IDEA-2021、maven-3.3.9、JDK-8

    ①、pom.xml

    创建普通的 maven 工程,修改 pom.xml 文件:

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
    
        <groupId>com.micozonegroupId>
        <artifactId>mybatis3.4.2artifactId>
        <version>1.0.0version>
    
        <properties>
            <maven.compiler.source>8maven.compiler.source>
            <maven.compiler.target>8maven.compiler.target>
        properties>
    
    
        <dependencies>
    
            
            <dependency>
                <groupId>org.mybatisgroupId>
                <artifactId>mybatisartifactId>
                <version>3.4.2version>
            dependency>
    
            
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <version>8.0.15version>
            dependency>
    
            
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <version>1.18.28version>
            dependency>
    
        dependencies>
    
        <build>
            <resources>
                <resource>
                    <directory>src/main/javadirectory>
                    <includes>
                        <include>**/*.xmlinclude>
                    includes>
                resource>
                <resource>
                    <directory>src/main/resourcesdirectory>
                resource>
            resources>
        build>
    
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    ②、建表、entity、mapper

    建库:mybatis,建表:mybatis_table

    CREATE TABLE `mybatis_table` (
      `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
      `user_name` varchar(100) DEFAULT NULL,
      `user_age` int DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    MybatisTable 类

    package com.micozone.mybatis.entity;
    
    import lombok.Data;
    
    @Data
    public class MybatisTable {
        private Long id;
        private String userName;
        private int userAge;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    MybatisTableMapper 接口

    package com.micozone.mybatis.mapper;
    
    public interface MybatisTableMapper {
    
        @Select("select id,user_name as userName,user_age as userAge from mybatis_table where id = #{arg0} and user_name = #{arg1}")
        MybatisTable selectByIdAndName(Long id, String userName);
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    MybatisTableMapper.xml

     DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.micozone.mybatis.mapper.MybatisTableMapper">
    
        <resultMap id="baseResultMap" type="mybatisTable">
            <id property="id" column="id" jdbcType="BIGINT"/>
            <result property="userName" column="user_name" jdbcType="VARCHAR"/>
            <result property="userAge" column="user_age" jdbcType="INTEGER"/>
        resultMap>
    
        <sql id="Base_Column_List">
            id,user_name,user_age
        sql>
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    ③、mybatis 配置文件

    resources/mybatis.xml

    
    DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
        <properties resource="test/jdbc.properties">
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        properties>
    
        <settings>
            <setting name="logImpl" value="STDOUT_LOGGING"/>
            
            
            <setting name="aggressiveLazyLoading" value="false"/>
        settings>
    
        <typeAliases>
            <package name="com.micozone.mybatis.entity"/>
        typeAliases>
    
        <objectFactory type="com.micozone.mybatis.mybatis.MyObjectFactory"/>
    
        <plugins>
            <plugin interceptor="com.micozone.mybatis.mybatis.MyPageInterceptor">
                <property name="name" value="zs"/>
                <property name="age" value="15"/>
            plugin>
        plugins>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="${driver}"/>
                    <property name="url"
                              value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8"/>
                    <property name="username" value="${username}"/>
                    <property name="password" value="jrbjrb811"/>
                dataSource>
            environment>
        environments>
    
        <mappers>
            
            <mapper class="com.micozone.mybatis.mapper.BuyerMapper"/>
            <mapper class="com.micozone.mybatis.mapper.OrdersMapper"/>
        mappers>
    
    configuration>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    resources/test/jdbc.properties

    username=root
    
    • 1

    ④、测试

    测试类

    Reader reader = Resources.getResourceAsReader("mybatis.xml");
    // 第一步、创建SqlSessionFactory
    SqlSessionFactory defaultSqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    // 第二步、创建SqlSession
    SqlSession defaultSqlSession = defaultSqlSessionFactory.openSession();
    // 第三步、获取mapper接口的动态代理对象
    MybatisTableMapper mybatisTableMapperProxy = defaultSqlSession.getMapper(MybatisTableMapper.class);
    // 第四步、调用方法访问数据库
    MybatisTable mybatisTable = mybatisTableMapperProxy.selectByIdAndName(1L, "micozone");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    二、JDBC

    1、概念

    JDBC 是 Java 提供的访问数据库(数据源)的接口(规范),数据库厂商根据 JDBC 规范完成 JDBC 的驱动程序,从而实现 Java 对数据库的访问;

    2、原理

    1、java.sql.Driver 接口、java.sql.DriverManager 类

    所有的 JDBC 驱动程序都必须提供一个实现 java.sql.Driver 接口的实现类,并且必须提供一个静态代码块,代码块中向 DriverManager 注册自己的实例。以 mysql-connector-java-8.0.15为例:

    public class Driver extends NonRegisteringDriver implements java.sql.Driver {
        // Register ourselves with the DriverManager
        static {
            try {
                java.sql.DriverManager.registerDriver(new Driver());
            } catch (SQLException E) {
                throw new RuntimeException("Can't register driver!");
            }
        }
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    只有成功的向 DriverManager 中注册驱动,才可以通过 DriverManager.getConnection(…) 方法获取 Connection 对象,所以一般情况下,需要显式的加载驱动实现类,如:Class.forName(“com.mysql.cj.jdbc.Driver”); 由于类加载到内存中会执行静态代码块,从而完成向 DriverManager 中注册驱动。但 JDK 提供了一种 SPI(Service Provider Interface) 机制,使我们不用显式的加载驱动实现类,直接通过 DriverManager.getConnection(…); 也能加载驱动实现类。

    2、SPI

    SPI 机制需要配合 java.util.ServiceLoader 类使用;
    第一步:服务提供者,提供 java.sql.Driver 接口的实现类 com.mysql.cj.jdbc.Driver
    第二步:在 classpath(resources) 目录下新建 META-INF/services 目录,创建文件名为 java.sql.Driver,文件内容为 com.mysql.cj.jdbc.Driver
    第三步:java.sql.DriverManager 的静态代码块中,使用 ServiceLoader.load(java.sql.Driver.class); 方法,加载驱动实现类 com.mysql.cj.jdbc.Driver,从而执行驱动实现类的静态代码块:向 DriverManager 中注册自己的实例。
    在这里插入图片描述
    java.sql.DriverManager

    public class DriverManager {
        ...... 
        /**
         * Load the initial JDBC drivers by checking the System property
         * jdbc.properties and then use the {@code ServiceLoader} mechanism
         */
        static {
            loadInitialDrivers();
            println("JDBC DriverManager initialized");
        }
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3、java.sql.Connection 接口

    成功创建 Connection 接口对象,代表与数据库成功建立连接;
    创建方式一:使用 DriverManager

    Connection connection = DriverManager.getConnection(
       "jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8",
       "root",
       "password");
    
    • 1
    • 2
    • 3
    • 4

    创建方式二:使用 DataSource(推荐)

    // 一个 DataSource 代表一个数据源
    // UnpooledDataSource 是 mybatis 提供的 DataSource 接口的实现类
    DataSource dataSource = new UnpooledDataSource(
       "com.mysql.cj.jdbc.Driver",
       "jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8",
       "root",
       "password");
    Connection connection = dataSource.getConnection();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    最后要调用 close(); 方法关闭 Connection 对象。

    4、java.sql.PreparedStatement 接口

    通过 connection.prepareStatement(String sql); 方法获取 PreparedStatement 对象;
    PreparedStatement 是 Statement 接口的实现类,Statement 接口为 SQL 语句的执行器,通过调用方法从而执行 SQL,常用方法有:execute();、executeQuery();、executeUpdate();
    PreparedStatement 相比于 Statement 的优点是,PreparedStatement 实例可以表示预编译的 SQL 语句,占位符使用 “?” 代替,不会出现 SQL 注入的风险。
    在使用 setXXX(); 方法为占位符设置值时,存在将 Java 数据类型转换为 JDBC 类型的过程,这一过程由 JDBC 驱动程序实现,如:

    JDBC 类型Java 类型
    INTEGERint
    BIGINTLong
    最后要调用 close(); 方法关闭 preparedStatement 对象。
    PS:调用存储过程使用 CallableStatement 接口;

    5、java.sql.ResultSet 接口

    用于解析 SQL 执行结果。

    6、JDBC 事务

    事务是为了保证多个 SQL 语句同时执行成功或者同时执行失败;
    创建 Connection 对象时,默认事务是自动提交的。当调用 Connection 对象的 void setAutoCommit(boolean autoCommit); 方法,参数传递 false 时,禁用事务自动提交,此时必须显式的调用 connection.commit(); 方法提交事务或者 connection.rollback(); 方法回滚事务

    7、事务隔离级别

    事务隔离级别代表一个事务对数据的操作对另一个事务的“可见性”。

    级别解析
    读未提交脏读,第一个事务未提交的数据,第二个事务可以读取到
    读已提交Oracle 默认级别,不可重复读,第一个事务读到到某行数据,第二个事务修改改行数据并提交,第一个事务再读取该行数据,此时会与第一次读取的结果不同
    可重复读MySQL 默认级别,幻读,某行数据无论另一个事务是否修改过都不会出现不可重复读现象,但是如果查询结果是多行数据,此时会出现幻读现象
    序列化读事务串行执行,但是并发率低

    3、使用

    在这里插入图片描述

    package com.micozone.mybatis.test;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    
    /**
     * 使用 JDBC 访问数据库流程
     *
     * @author Mico Zone
     */
    public class JDBC {
        public static void main(String[] args) throws Exception {
            Connection connection = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8",
                    "root",
                    "password");
            String sql = "select * from mybatis_table where id = ? and user_name = ?";
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setLong(1, 1L);
            preparedStatement.setString(2, "micozone");
            ResultSet resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                System.out.println(resultSet.getLong(1)); // 1
                System.out.println(resultSet.getString(2)); // micozone
                System.out.println(resultSet.getInt(3)); // 27
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    三、MyBatis中的工具类

    1、SQL 类—拼接 SQL

    【MyBatis 3 源码深度解析】P59

    Connection connection = getConnection();
    String sql = new SQL()
            .SELECT("id,user_name,user_age")
            .FROM("mybatis_table")
            .WHERE("id = ?")
            .toString();
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    preparedStatement.setLong(1, 1L);
    ResultSet resultSet = preparedStatement.executeQuery();
    while (resultSet.next()) {
        System.out.println(resultSet.getLong(1));
        System.out.println(resultSet.getString(2));
        System.out.println(resultSet.getInt(3));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2、ScriptRunner 类—执行 SQL 脚本

    Connection connection = getConnection();
    ScriptRunner scriptRunner = new ScriptRunner(connection);
    scriptRunner.runScript(Resources.getResourceAsReader("test/sqlScript.sql"));
    
    • 1
    • 2
    • 3

    resources/test/sqlScript.sql

    INSERT INTO mybatis_table
    (id, user_name, user_age)
    VALUES(504, 'zs', 21);
    
    INSERT INTO mybatis_table
    (id, user_name, user_age)
    VALUES(505, 'ls', 34);
    
    INSERT INTO mybatis_table
    (id, user_name, user_age)
    VALUES(600, 'ls', 34),(601, 'ls', 34);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3、SqlRunner 类—执行 SQL 语句

    Connection connection = getConnection();
    SqlRunner sqlRunner = new SqlRunner(connection);
    String selectSql = new SQL()
            .SELECT("id,user_name as userName,user_age as userAge")
            .FROM("mybatis_table")
            .WHERE("id = ?").toString();
    Map<String, Object> selectResult = sqlRunner.selectOne(selectSql, 1L);
    // selectResult: {USERAGE=27, USERNAME=micozone, ID=1}
    System.out.println("selectResult: " + selectResult);
    
    String deleteSql = new SQL()
            .DELETE_FROM("mybatis_table")
            .WHERE("id = ?")
            .toString();
    int deleteResult = sqlRunner.delete(deleteSql, 500L);
    // deleteResult: 1
    System.out.println("deleteResult: " + deleteResult);
    
    String updateSql = new SQL()
            .UPDATE("mybatis_table")
            .SET("user_name = ?")
            .SET("user_age = ?")
            .WHERE("id = ?")
            .toString();
    int updateResult = sqlRunner.update(updateSql, "12", 34, 501L);
    // updateResult: 1
    System.out.println("updateResult: " + updateResult);
    
    String insertSql = new SQL()
            .INSERT_INTO("mybatis_table")
            .INTO_COLUMNS("id,user_name,user_age")
            .INTO_VALUES("?,?,?")
            .toString();
    int insertResult = sqlRunner.insert(insertSql, 700L, "qwe", 14);
    // insertResult: -2147482647
    System.out.println("insertResult: " + insertResult);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    4、MetaObject—通过反射获取/设置属性值

    List<Book> bookList = new ArrayList<>();
    bookList.add(new Book("水浒传"));
    bookList.add(new Book("红楼梦"));
    BookShop bookShop = new BookShop("新华书店", bookList);
    MetaObject metaObject = SystemMetaObject.forObject(bookShop);
    System.out.println("第一本书:" + metaObject.getValue("bookList[0].bookName"));
    metaObject.setValue("bookList[1].bookName", "三国演义");
    System.out.println("第二本书:" + metaObject.getValue("bookList[1].bookName"));
    System.out.println("BookShop是否有bookList属性且有getter方法:" + metaObject.hasGetter("bookList"));
    System.out.println("BookShop是否有shopName属性且有setter方法:" + metaObject.hasSetter("shopName"));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Book.java

    @Data
    @AllArgsConstructor
    class Book {
        private String bookName;
    
        public Book() {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    BookShop.java

    @Data
    @AllArgsConstructor
    class BookShop {
        private String shopName;
        private List<Book> bookList;
    
        public BookShop() {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    5、MetaClass—通过反射获取类相关信息

    MetaClass metaClass = MetaClass.forClass(BookShop.class, new DefaultReflectorFactory());
    // 是否有shopName属性:shopName
    System.out.println("是否有shopName属性:" + metaClass.findProperty("shopName"));
    // 是否有abc属性:null
    System.out.println("是否有abc属性:" + metaClass.findProperty("abc"));
    // 所有有getter方法的属性名:[shopName, bookList]
    System.out.println("所有有getter方法的属性名:" + Arrays.toString(metaClass.getGetterNames()));
    // 所有有setter方法的属性名:[shopName, bookList]
    System.out.println("所有有setter方法的属性名:" + Arrays.toString(metaClass.getSetterNames()));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    6、ObjectFactory—通过反射创建对象

    ObjectFactory objectFactory = new DefaultObjectFactory();
    BookShop noParamBookShop = objectFactory.create(BookShop.class);
    // noParamBookShop: BookShop(shopName=null, bookList=null)
    System.out.println("noParamBookShop: " + noParamBookShop);
    List<Book> bookList = new ArrayList<>();
    bookList.add(new Book("水浒传"));
    bookList.add(new Book("红楼梦"));
    List<Class<?>> constructorArgTypes = new ArrayList<>();
    constructorArgTypes.add(String.class);
    constructorArgTypes.add(List.class);
    List<Object> constructorArgs = new ArrayList<>();
    constructorArgs.add("新华书店");
    constructorArgs.add(bookList);
    BookShop bookShop = objectFactory.create(BookShop.class, constructorArgTypes, constructorArgs);
    // bookShop: BookShop(shopName=新华书店, bookList=[Book(bookName=水浒传), Book(bookName=红楼梦)])
    System.out.println("bookShop: " + bookShop);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    7、XPathParser—解析 xml 文件

    XPathParser 封装了 XPath,从而完成对 xml 文件的解析。

    Reader reader = Resources.getResourceAsReader("test/pens.xml");
    XPathParser xPathParser = new XPathParser(reader);
    List<Pen> penList = new ArrayList<>();
    List<XNode> pensXNodeList = xPathParser.evalNodes("/pens/*");
    for (XNode penXNode : pensXNodeList) {
        Pen pen = new Pen();
        Long id = penXNode.getLongAttribute("id");
        pen.setId(id);
        List<XNode> penChildrenXNodeList = penXNode.getChildren();
        for (XNode childrenXNode : penChildrenXNodeList) {
            String name = childrenXNode.getName();
            if ("name".equals(name)) {
                pen.setName(childrenXNode.getStringBody());
            } else if ("price".equals(name)) {
                pen.setPrice(childrenXNode.getIntBody());
            }
        }
        penList.add(pen);
    
    }
    System.out.println(penList);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    resources/test/pens.xml

    
    <pens>
        <pen id="1">
            <name>晨光name>
            <price>3price>
        pen>
        <pen id="2">
            <name>三菱name>
            <price>8price>
        pen>
    pens>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    四、创建 SqlSessionFactory 对象

    Reader reader = Resources.getResourceAsReader("mybatis.xml");
    // 第一步、创建SqlSessionFactory
    SqlSessionFactory defaultSqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    
    • 1
    • 2
    • 3

    大体分为两步,第一步:解析 mybatis 配置文件,将内容封装到 Configuration 对象中;第二步:创建 DefaultSqlSessionFactory 对象,将第一步创建的 Configuration 对象赋给 DefaultSqlSessionFactory 对象的 Configuration 属性。

    1、解析 mybatis 配置文件

    sqlSessionFactoryBuilder.build();

    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
      ......
      // XMLConfigBuilder 底层通过 XPathParser 对象解析 mybatis 配置文件
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      // Configuration config = parser.parse();
      return build(parser.parse());
      ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    parser.parse();

    public Configuration parse() {
      ......
      parseConfiguration(parser.evalNode("/configuration"));
      return configuration;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    parser.parseConfiguration();

    private void parseConfiguration(XNode root) {
        /*
        一、解析  标签
        
           
        
        会将jdbc.properties文件中的键值对、以及所有的标签的键值对封装到 Configuration 对象的 variables 属性中,其中 Properties variables = new Properties();
        */
        propertiesElement(root.evalNode("properties"));
        /*
        二、解析  标签
        
           
           
        
        会将所有的标签的键值对封装到 Configuration 对象的对应属性中,如:
         ===> configuration.logImpl = StdOutImpl.class
         ===> configuration.cacheEnabled = true
        */
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        loadCustomVfs(settings);
        /*
        三、解析  标签
        
           
        
        会将标签对应的包名下的所有实体类以及对应别名存放到 configuration.typeAliasRegistry.TYPE_ALIASES 中,
        其中 Map> TYPE_ALIASES = new HashMap>();
        */
        typeAliasesElement(root.evalNode("typeAliases"));
        /*
        四、解析  标签
        
           
              
              
           
        
        MyPageInterceptor 类要实现 Interceptor 接口,并且要有 @Intercepts 注解,插件原理放在第 八 部分。
        这里会将 MyPageInterceptor 类的对象放到 configuration.interceptorChain.interceptors 中,
        其中 List interceptors = new ArrayList();
        */
        pluginElement(root.evalNode("plugins"));
        /*
        五、解析  标签
        
           
        
        MyObjectFactory 类最好继承 DefaultObjectFactory 类,DefaultObjectFactory 对象的作用是创建 Mapper 映射结果对象,
        这里会对 configuration 对象的属性赋值:configuration.objectFactory = new MyObjectFactory(); 
        其中 ObjectFactory objectFactory = new DefaultObjectFactory();
        
        @Data
        public class MyObjectFactory extends DefaultObjectFactory {
           private Properties properties;
           
           @Override
           public  T create(Class type, List> constructorArgTypes, List constructorArgs) {
              if (type.equals(MybatisTable.class)) {
                 System.out.println("MyObjectFactory.create() execute 正在创建 MybatisTable 对象");
              }
              return super.create(type, constructorArgTypes, constructorArgs);
           }
        }
        */
        objectFactoryElement(root.evalNode("objectFactory"));
        /*
        六、objectWrapperFactory TODO
        */
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        /*
        七、reflectorFactory TODO
        */
        reflectorFactoryElement(root.evalNode("reflectorFactory"));
        settingsElement(settings);
        /*
        八、解析  标签
        
           
              
              
                 
                 
                 
                 
              
           
        
        这里会对 configuration 对象的属性赋值:configuration.environment = new Environment();
        根据标签创建 Environment 对象:
         ===> environment.transactionFactory = new JdbcTransactionFactory();
        ... ===> environment.dataSource = new PooledDataSource();
        */
        environmentsElement(root.evalNode("environments"));
        /*
        九、databaseIdProvider TODO
        */
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        /*
        十、typeHandlers TODO
        */
        typeHandlerElement(root.evalNode("typeHandlers"));
        /*
        十一、解析  标签
        
           
           
        
        解析  标签步骤较多,放到 七-1 部分
        */
        mapperElement(root.evalNode("mappers"));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113

    2、创建 DefaultSqlSessionFactory 对象

    sqlSessionFactoryBuilder.build();

    public SqlSessionFactory build(Configuration config) {
      // defaultSqlSessionFactory.configuration = config;
      return new DefaultSqlSessionFactory(config);
    }
    
    • 1
    • 2
    • 3
    • 4

    五、创建 SqlSession 对象

    SqlSessionFactory defaultSqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    // 第二步、创建SqlSession
    SqlSession defaultSqlSession = defaultSqlSessionFactory.openSession();
    
    • 1
    • 2
    • 3

    大体分为两步,第一步:创建 Executor 对象;第二步:创建 DefaultSqlSession 对象,将 executor 赋给 defaultSqlSession 的 executor 属性。

    1、创建 Executor 对象

    defaultSqlSessionFactory.openSession();

    @Override
    public SqlSession openSession() {
      return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
    }
    
    • 1
    • 2
    • 3
    • 4

    defaultSqlSessionFactory.openSessionFromDataSource();

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
      // 在解析  标签时,对 configuration 的 environment 属性赋值
      final Environment environment = configuration.getEnvironment();
      //  ===> JdbcTransactionFactory
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // JdbcTransaction,其中... ===> PooledDataSource
      Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      /*
      如果,默认为true:executor = new CachingExecutor(); executor.delegate = new SimpleExecutor();
      如果:executor = SimpleExecutor;
      */
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    其中 Executor、Transaction、DataSource的关系:
    在这里插入图片描述

    2、创建 DefaultSqlSession 对象

    defaultSqlSessionFactory.openSessionFromDataSource();

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
      ......
      return new DefaultSqlSession(configuration, executor, autoCommit);
    }
    
    • 1
    • 2
    • 3
    • 4

    DefaultSqlSession 的构造函数

    public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
      this.configuration = configuration;
      this.executor = executor;
      this.dirty = false;
      this.autoCommit = autoCommit;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    六、创建 Mapper 接口的代理对象

    大体分为两步,第一步:获取 MapperProxyFactory 对象;第二步:通过 mapperProxyFactory 对象创建 Mapper 接口的动态代理对象。

    1、获取 MapperProxyFactory 对象

    SqlSessionFactory defaultSqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    SqlSession defaultSqlSession = defaultSqlSessionFactory.openSession();
    // 第三步、获取mapper接口的动态代理对象
    MybatisTableMapper mybatisTableMapperProxy = defaultSqlSession.getMapper(MybatisTableMapper.class);
    
    • 1
    • 2
    • 3
    • 4

    defaultSqlSession.getMapper();

    @Override
    public <T> T getMapper(Class<T> type) {
      return configuration.<T>getMapper(type, this);
    }
    
    • 1
    • 2
    • 3
    • 4

    configuration.getMapper();

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
      return mapperRegistry.getMapper(type, sqlSession);
    }
    
    • 1
    • 2
    • 3

    mapperRegistry.getMapper();

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
      // 解析  标签时,针对每一个 Mapper 接口:configuration.mapperRegistry.knownMappers.put(Mapper.class, new MapperProxyFactory(Mapper.class));
      // 此处取到 mapperProxyFactory 对象
      final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
      return mapperProxyFactory.newInstance(sqlSession);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2、创建 Mapper 接口的动态代理对象

    mapperProxyFactory.newInstance(SqlSession sqlSession);

    public T newInstance(SqlSession sqlSession) {
      // mapperInterface = Mapper.class
      // MapperProxy 类实现 InvocationHandler 接口,即 MapperProxy 对象为 Mapper 接口动态代理对象的调用处理程序
      final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
      return newInstance(mapperProxy);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    mapperProxyFactory.newInstance(MapperProxy mapperProxy);

    protected T newInstance(MapperProxy<T> mapperProxy) {
      // 这里创建 Mapper 接口的动态代理对象,通过该对象调用方法时,实际上会调用 mapperProxy 对象的 invoke(); 方法
      return (T) Proxy.newProxyInstance(
                              mapperInterface.getClassLoader(), 
                              new Class[] { mapperInterface }, 
                              mapperProxy);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    七、调用 Mapper 接口的代理对象的方法

    此处在介绍 “调用代理对象的方法的执行流程” 前,先介绍一下 四-1 中遗留的 mappers 标签的解析流程。

    1、mappers 标签解析

    mybatis.xml

    <mappers>
       <package name="com.micozone.mybatis.mapper"/>
    mappers>
    
    • 1
    • 2
    • 3

    xmlConfigBuilder.parseConfiguration();

    private void parseConfiguration(XNode root) {   
       ......
       mapperElement(root.evalNode("mappers"));
    }
    
    • 1
    • 2
    • 3
    • 4

    大体分为三步,第一步:将 Mapper 接口添加到 knownMappers 缓存中;第二步:loadXmlResource(); 第三步:加载 Mapper 接口中含有 @Select、@Insert、@Update、@Delete 注解的方法。

    ①、将 Mapper 接口添加到 knownMappers 缓存中

    mapperRegistry.addMappers();

    public void addMappers(String packageName, Class<?> superType) {
      ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
      resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
      Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
      // 遍历指定包下的所有 Mapper 接口
      for (Class<?> mapperClass : mapperSet) {
        addMapper(mapperClass);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    mapperRegistry.addMapper();

    public <T> void addMapper(Class<T> type) {
      // 其中 Map knownMappers = new HashMap<>();
      knownMappers.put(type, new MapperProxyFactory<T>(type));
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      parser.parse();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ②、loadXmlResource();

    mapperAnnotationBuilder.parse();

    public void parse() {
      String resource = type.toString();
      if (!configuration.isResourceLoaded(resource)) {
        // 加载 xml 资源
        loadXmlResource();
        ......
        Method[] methods = type.getMethods();
        for (Method method : methods) {
          parseStatement(method);
        }
      }
      ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    mapperAnnotationBuilder.loadXmlResource();

    private void loadXmlResource() {
      if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
        String xmlResource = type.getName().replace('.', '/') + ".xml";
        // 默认加载 Mapper 接口所在包下的同名 Mapper.xml 文件
        InputStream inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    xmlMapperBuilder.parse();

    public void parse() {
      ...
      configurationElement(parser.evalNode("/mapper"));
      ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    xmlMapperBuilder.configurationElement();

    private void configurationElement(XNode context) {
      cacheRefElement(context.evalNode("cache-ref"));
      /*
      1、创建 Cache 对象
      2、configuration.caches.put(cache.getId(), cache);
      其中 Map caches = new StrictMap("Caches collection");
      */
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      /*
      
         
         
         
      
      1、针对每一个标签,创建 ResultMapping 对象,放到 List resultMappings 集合中
      2、创建 ResultMap 对象,将 resultMappings 集合赋给 resultMap 对象的 resultMappings 属性
      3、configuration.resultMaps.put(resultMap.getId(), resultMap);
      其中 Map resultMaps = new StrictMap("Result Maps collection");
      */
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      /*
      
         id,user_name,user_age
      
      1、configuration.sqlFragments.put(String id, XNode context);
      其中 Map sqlFragments;
      */
      sqlElement(context.evalNodes("/mapper/sql"));
      /*
      
         insert into mybatis_table (id, user_name, user_age)
         values (#{id}, #{userName}, #{userAge})
      
      1、针对每一个