死磕 Mybatis 源码解析系列(一)

325 阅读12分钟

一、发展流程

1.1 最初的 JDBC

最开始,我记得在大学的时候,我还是使用的 JDBC 来连接数据库的,使用起来挺复杂的,原有 JDBC 代码事例:

public class JdbcText {

    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;

        try {
            // 注册 JDBC 驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 打开连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo", "root", "root");

            // 执行查询
            stmt = conn.createStatement();
            String sql = "SELECT id, consume_type, moneys,text_desc FROM day_money_detail";
            ResultSet rs = stmt.executeQuery(sql);

            // 获取结果集
            while (rs.next()) {
                Long id = rs.getLong("id");
                Integer consumeType = rs.getInt("consume_type");
                Double moneys = rs.getDouble("moneys");
                String textDesc=rs.getString("text_desc");
                System.out.format("id:%s  consumeType:%s   moneys:%s   textDesc:%s",id,consumeType,moneys,textDesc);
                System.out.println("");
            }
            rs.close();
            stmt.close();
            conn.close();
        } catch (SQLException se) {
            se.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (stmt != null) stmt.close();
            } catch (SQLException se2) {
            }
            try {
                if (conn != null) conn.close();
            } catch (SQLException se) {
                se.printStackTrace();
            }
        }
    }
}

基本的几个流程嘛我们都知道,老生常谈地罗列一下:

  1. 注册驱动、获取连接
  2. 创建 Statement 对象
  3. 创建并执行 SQL 语句 execute()
  4. 对查询结果进行转换处理并返回
  5. 关闭资源(关闭 Connection、关闭 Statement、关闭 ResultSet )

大家看了这段代码,如果我们直接使用原生 API会有哪些问题?

  1. 代码肯定有重复的
  2. 结果集处理太复杂了
  3. 资源管理也很复杂
  4. 执行的 SQL 语句硬编码,耦合度贼高,用起来很不方便

1.2 工具类的产生

由于原始的 JDBC 存在的问题,到后来生产环境的使用,不怎么方便,这个时候各种工具包就出来了,比如 Spring JDBC、Apache DbUtils、JdbcTemplate 等等,看下它们的基本操作使用事例(注:有一定基础的不需要看这个发展流程,直接进入第三节)。

1.2.1 Apache DbUtils

设置配置文件

# hikari.properties 配置文件

dataSource.user=root
dataSource.password=root
jdbcUrl=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
dataSource.minimumIdle=10
dataSource.maximumPoolSize=30

创建工具类

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.commons.dbutils.QueryRunner;

public class HikariUtil {

    private static final String PROPERTY_PATH = "/hikari.properties";
    /**
     * 数据源
     */
    private static HikariDataSource dataSource;

    public static QueryRunner getQueryRunner() {
        HikariConfig config = new HikariConfig(PROPERTY_PATH);
        //获取数据源
        dataSource = new HikariDataSource(config);
        return new QueryRunner(dataSource);
    }
}

创建 Dao

import com.alibaba.fastjson.JSONObject;
import com.demo.cs.mybatis.dbutils.dto.DayMoneyDetailDto;
import com.demo.cs.mybatis.dbutils.untils.HikariUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.*;

import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;

public class DayMoneyDetailDao {

    /**
     * 它封装了这几种类型的方法
     * 1. execute(执行SQL语句)
     * 2. batch(批量处理语句)
     * 3. insert(执行INSERT语句)
     * 4. insertBatch(批量处理INSERT语句)
     * 5. query(SQL中 SELECT 语句)
     * 6. update(SQL中 INSERT, UPDATE, 或 DELETE 语句)
     */
    static QueryRunner queryRunner;

    static {
        queryRunner = HikariUtil.getQueryRunner();
    }

    /**
     * 返回列表
     * @throws SQLException
     */
    public static void list() throws SQLException {
        String sql = "select * from day_money_detail";
        //获取集合
        List< Object[] > result = queryRunner.query(sql,new ArrayListHandler());
        for (Object[] aList : result) {
            System.out.println(Arrays.toString(aList) + " ");
        }

        // 可以通过 new BeanListHandler 通过反射得到具体的List<ro>对象
        List<DayMoneyDetailDto> list = queryRunner.query(sql, new BeanListHandler<DayMoneyDetailDto>(DayMoneyDetailDto.class));
        System.out.println(JSONObject.toJSONString(list));

    }
}

创建 Dto 实体类

import lombok.Data;

@Data
public class DayMoneyDetailDto {
    private Long id;
    private Integer consumeType;
    private Double moneys;
    private String textDesc;
}

测试

import com.demo.cs.mybatis.dbutils.dao.DayMoneyDetailDao;
import java.sql.SQLException;

public class TestMain {
    public static void main(String[] args) throws SQLException {
        //查询集合
        DayMoneyDetailDao.list();
    }
}

结果:

[1, 1, 4.0, 公交, 2019-04-16 18:16:14.0] 
[2, 1, 6.3, 早餐, 2019-04-16 18:16:33.0] 

[{"id":1,"moneys":4.0},{"id":2,"moneys":6.3}]

Apache DbUtils 的优点:

  1. 引入数据源,使得注册驱动、获取连接更加方便
  2. QueryRunner 的封装使得我们不再需要编写繁琐的 curd 方法
  3. QueryRunner 对返回结果集做了很好的优化处理

1.2.2 JdbcTemplate

下面是我们的 JdbcTemplate 的代码事例。

数据源配置

# 数据源
spring.datasource.url=jdbc:mysql://localhost:3306/demo?characterEncoding=UTF-8&useUnicode=true&useSSL=false&serverTimezone=UTC
spring.datasource.username=xxx
spring.datasource.password=xxx
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.minimum-idle=1
spring.datasource.maximum-pool-size=15
spring.datasource.maxActive=10

Maven 配置

导入我们需要的包:

<modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>com.jdbcs.demo</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class JdbcTemplateTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void listId() {
        String  sql="select count(*) from deo_user";
        int result=jdbcTemplate.queryForObject(sql,Integer.class);
        System.out.println(JSONObject.toJSONString(result));
    }
}

其它的例子就不举了,其实上面这些只能称之为工具包,而我们需要一套整体的框架体系来对 MySQL 进行连接管理、MySQL 缓存的控制等等,这里就引入了 MyBatis。(在看它的源码时,需要对设计模式有所理解)

二、基本特性及基础使用

2.1 基本特性

这些都是看完源码的理解。

2.1.1 使用连接池对连接进行管理

<environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/demo?useUnicode=true&amp;characterEncoding=utf-8" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>

POOLED 表示数据库连接池,在内存中开辟一块空间,存放多个数据库连接对象,有两种状态:

  • active 状态:当前连接对象被应用程序使用中
  • Idle 空闲状态:等待被应用程序使用

使用数据库连接池的目的:在高频率访问数据库时,使用数据库连接池可以降低服务器系统的压力,提升程序运行的效率。

2.1.2 SQL 和代码分离、集中管理

我们使用 MyBatis 时,每个 Dao 基本会对应一个 mapper.xml 文件,这个文件里面就是我们所有的 SQL 语句,这样非常方便我们管理,有什么错误也能及时排查。

2.1.3 参数映射和动态 SQL

能直接将我们的程序参数转换为数据库能识别的参数,最主要的就是编写的时候有多重判断标签,如:<if> <foreach> 等。

2.1.4 结果集映射

在 MyBatis 的 BaseStatementHandler.java 类中,我们会看到在这里面 configuration.newResultSetHandler 的方法,在这里它设置了结果集处理的 ResultHandler,后面会有插件对它去实现处理 DefaultResultSetHandler.java,这样我们就可以直接将数据库的对象转换成我的 Java 对象。

在 XML 文件中,我们会看到 <resultMap id="resultMap" type="xxxx"> JDBC 到 Object 的映射。

2.1.5 缓存管理(一级缓存、二级缓存)

工具包:对于缓存的管理来说根本就没有体现出来,非常不方便。

MyBatis 里面有对一二级缓存的开启,细致到标签级别 useCache="true" flushCache="true",除此之外,还能集成三方缓存插件,如 Redis。

2.1.6 重复 SQL 的提取

在自定义的 XML 映射文件中可以将相同的返回结果,或者执行关联查询相同的语句,放到 <sql>标签里面,只需要在各个语句中引入即可 <include refid="id />

2.1.7 插件机制(扩展功能)

我们在 SQL 处理期间,做一些其它的扩展,比如我们用的分页工具,这是最典型的例子。

2.2 不涉及到容器时的使用

2.2.1 导入包

<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
    <groupId>com.mybatis.demo2</groupId>
    <artifactId>demo2</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.50</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2.2.2 创建 Dto

package com.mybatis.entity;

import lombok.Data;

import java.io.Serializable;

@Data
public class UserDTO implements Serializable {
    private static final long serialVersionUID = -747917934132617417L;
    private Long id;
    private String userName;

}

2.2.3 创建 mapper 接口

package com.mybatis.mapper;

import com.mybatis.entity.UserDTO;

public interface UserMapper {

    int insert(UserDTO userDTO);

    int update(@Param("id") Long id,@Param("userName") String userName);

    UserDTO getById(@Param("id") Long id);

}

2.2.4 创建 mapper.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="com.mybatis.mapper.UserMapper">
    <resultMap id="resultMap" type="userDTO">
        <id column="id" property="id" />
        <result column="user_name" property="userName" />
    </resultMap>

    <sql id="Base_Column_List">
        id, user_name
    </sql>

    <insert id="insert" parameterType="userDTO">
        <selectKey resultType="java.lang.Long" order="AFTER" keyProperty="id">
            SELECT LAST_INSERT_ID()
        </selectKey>
        insert into deo_user(user_name)
        values (#{userName})
    </insert>

    <update id="update" >
        update deo_user set user_name=#{userName} where id = #{id}
    </update>

    <select id="getById" resultMap="resultMap">
        SELECT <include refid="Base_Column_List" />  from deo_user where id= #{id}
    </select>

</mapper>

2.2.5 配置 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 这个标签可对应源码里面去看 org.apache.ibatis.session.Configuration -->
<configuration>
    <settings>
        <!-- 打印 sql 语句 -->
        <setting name="logImpl" value="STDOUT_LOGGING" />
        <!-- 全局缓存(二级缓存) -->
        <setting name="cacheEnabled" value="false" />
    </settings>

    <typeAliases>
        <!-- 别名  mybatis 又些默认的映射的类型 org.apache.ibatis.type.TypeAliasRegistry.TypeAliasRegistry  -->
        <typeAlias alias="userDTO"  type="com.mybatis.entity.UserDTO" />
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/demo?useUnicode=true&amp;characterEncoding=utf-8" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>
    
    <mappers>
        <mapper resource="mybatis/mapper/UserMapper.xml"/>
    </mappers>

</configuration>

2.2.6 测试

package com.mybatis.test;

import com.alibaba.fastjson.JSONObject;
import com.mybatis.entity.UserDTO;
import com.mybatis.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.Reader;

public class Test1 {

    public static void main(String[] args){
        try {
            Reader reader = Resources.getResourceAsReader("mybatis/config/mybatis-config.xml");
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
            // sqlSession能够执行配置文件中的SQL语句
            SqlSession sqlSession = factory.openSession();
            UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
            UserDTO u1=userMapper.getById(1L);
            System.out.println(JSONObject.toJSONString(u1));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

三、基本流程及核心源码分析

3.1 基本流程

通过上面工具类的代码流程和 MyBatis 的小 Demo,我画了一个理解图,这个在网上也有很多,画这个图的目的是方便我们后面查看源码有一个友好的流程。

基本流程

当然了,这个是大的流程,里面肯定有细节了,但是我们从整个结构来思考,它是不是也分为几个核心的结构呢?会不会类似于 Spring 那种架构图的结构呢?分析我不分析了,现在网上你只要一搜就有一大堆的架构图,但是我们要有这种思想;下面我们直接进入主题吧。

3.2 源码解析

通过上面的测试方法,来解读下源码(下面都是我画的时序图去理解的,图片如果失效,我会在文章最后标上本次的源码和图片的下载地址,整个核心链路全部在图里面哦,下面我们看下它的具体调用逻辑,时序图比较大,建议用 PC 端查看)。

3.2.1 解析配置

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);

在这里插入图片描述

  1. 在 MyBatis 运行开始时先通过 Resources 加载全局配置文件
  2. 再实例化 SqlSessionFactoryBuilder 构建器,帮助 SqlSessionFactory 接口实现类 DefaultSqlSessionFactoy
  3. 在实例化 DefaultSqlSessionFactoy 之前先创建 XMLConfigBuilder 解析全局配置文件流,把解析结果存放在 Configuration 中
  4. 之后会开始解析配置文件,如 configuration 、settings 等标签和插件,然后将其解析结果存放在 Configuration 中
  5. 再将 Configuration 传给 DefaultSqlSessionFactoy,上述步骤执行完,SqlSessionFactory 工厂创建成功

3.2.2 创建会话

SqlSession sqlSession = factory.openSession();

在这里插入图片描述

  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);
      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();
    }
  }
  1. 根据已获取的 SqlSessionFactory 通过调用 openSession() 开启会话
  2. 调用 getEnvironment() 获取上一步我们解析的 environments 标签内容,用于后面步骤开启事务
  3. 调用 getTransactionFactoryFromEnvironment(environment) 获取事务工厂,会根据配置里面选择事务类型 <transactionManager type="JDBC"></transactionManager>,这里事务类型有两种,一种是: JDBC,还有一种是容器(管理):manage
  4. 通过 newExecutor(tx, execType) 来创建一个执行器,一般默认的是 SIMPLE 执行器,然后判断是否开启缓存,获取所有的插件;最后获得一个 SqlSession
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

3.2.3 获取 mapper

UserMapper userMapper=sqlSession.getMapper(UserMapper.class);

在这里插入图片描述

1. 通过获取 SqlSession 中的 getMapper(Class) 方法,获取我们的 mapper;这里会从我们开始解析后存放到 Configuration 中的 MapperRegistry 对象中获取。

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

2. 这里,再通过调用 mapperProxyFactory.newInstance(sqlSession) 获取 mapper,这里使用了 JDK 的动态代理,而它代理的确实我们的 mapper.xml 文件,通过我们的 namepace 来一一对应;其实这里我们可以看成直接返回对应的 XML 文件,这样好理解点。

  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

3.2.4 最后执行,获取结果

UserDTO u1=userMapper.getById(1L);

在这里插入图片描述

既然我们获取了我们的 mapper.xml 文件后,下面就是怎么去执行这个 SQL 了,那么我们下面看看如何执行的。

1. 上面一步已经通过 JDK 的动态代理获取了 mapper,所有会调用 MapperProxy 中的 invoke 方法;会判断是否是 Object 自带的方法,不是则继续调用 cachedMapperMethod(method) 获取是否使用缓存配置,这里的缓存 key 和 value 存放的的规则会在后面说明。

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

2. 调用我们需要调用的方法,测试代码中我是调用的查询方法,那么会调用 selectOne(String statement, Object parameter);在前一步会将我们的入参转换一次,在 sql 里面会通过填充,这里的 statement 是包名 + 方法名,这个是用来找到我们具体代理 mapper.xml 里的方法的标识。

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        //会走这个方法
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          //入参转换
          Object param = method.convertArgsToSqlCommandParam(args);
          //执行
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

3. 调用 executor.query() 之前会通过 getMappedStatement(statement) 方法获取 MappedStatement 对象,这个里面存放了我们的配置和 SQL 语句(未填充入参的)、入参信息,这里会有两个实现类 BaseExecutor、CachingExecutor(委派模式),如果我们开启了二级缓存,那么会使用 CachingExecutor,未开启会使用 BaseExecutor。不管是否开启二级缓存,我们都会调用 createCacheKey(ms, parameterObject, rowBounds, boundSql) 来创建 key 值(也是委派模式),委派给 BaseExecutor 来实现的;我们可以知道 key = 类名地址 + 方法名 + 分页信息 + SQL 语句 + 参数。

  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // key 的创建
    
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

4. 最后调用 query(xxx) 方法,会根据配置的 flushCache 决定是否清除本地缓存(一级缓存),之后会获取本地缓存是否存有数据,为空直接调用 queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql) 方法,之后初始化一级缓存,通过上文的 SIMPLE 执行器:

list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

里面有一个 RoutingStatementHandler 路由,里面会通过委派 s 使用 PreparedStatementHandler 调用父类 BaseStatementHandler 获取一些基本配置信息,然后返回到 SimpleExecutor 执行器,通过 prepareStatement(handler, ms.getStatementLog()) 处理参数,之后会调用 PreparedStatementHandler 类中的 query(),最后 ps.execute() 执行 SQL;后续就是对于出参进行处理。

RoutingStatementHandler.java

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

Configuration.java

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

BaseStatementHandler.java

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;
	//这里就是对出入参进行处理的地方,具体的如何实现,有兴趣可以点进去看下
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

上面就是 MyBatis 整体的源码分析,发现里面用到很多设计模式,所以建议大家看这个源码之前,对设计模式需要掌握;知其然知其所以然。