整体执行过程

image-20200903172942096

整体执行过程大致如上图所示。具体的步骤可以拆分成一下内容

  1. 首先解析配置,得到 Configuration

  2. 创建 SqlSession 回话,用于和数据库完成交互。

  3. SqlSession 中创建不同的 Executor 执行程序。

  4. Executor 中创建 StatementHandler 来调用 jdbc 程序。

  5. Executor 中指定 ParameterHandler 来将 java 类型转化成 jdbc 类型,然后查询数据库,数据库返回的数据通过 ResultSetHandler 把 jdbc 类型转化成 java 类型

具体执行过程分析

image-20200903172942096

整体的执行过程的时序图如上图所示。

  1. 创建 mybaties 的环境

    本次验证过程中,使用的 mybatis 的编程式方式调用

    mybatis-config.xml 文件配置如下

    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
    <?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>
    <settings>
    <setting name="logImpl" value="SLF4J"/>
    <!--<setting name="cacheEnabled" value="true" />-->
    </settings>
    <!--<plugins>-->
    <!--<plugin interceptor="com.gupaoedu.mybatis.plugins.TestPlugin">-->
    <!--</plugin>-->
    <!--</plugins>-->
    <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://172.16.159.211:3306/user"/>
    <property name="username" value="root"/>
    <property name="password" value="******"/>
    </dataSource>
    </environment>
    </environments>
    <mappers>
    <mapper resource="TestMapper.xml"/>
    </mappers>
    </configuration>

    pom 依赖文件

    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
    <dependencies>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.2</version>
    </dependency>

    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.6</version>
    </dependency>

    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.2</version>
    </dependency>

    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    </dependency>

    <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.2</version>
    </dependency>
    </dependencies>

    TestMapper 接口

    1
    2
    3
    4
    public interface TestMapper {

    User selectByPrimary(Integer id);
    }

    TestMapper.xml 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?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.mybaties.demo.TestMapper">
    <!--<cache-->
    <!--eviction="FIFO"-->
    <!--flushInterval="60000"-->
    <!--size="512"-->
    <!--readOnly="true"/>-->
    <select id="selectByPrimary" resultType="com.mybaties.demo.User">
    select * from user where id = #{id}
    </select>
    </mapper>

    测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
    * 通过接口形式调用
    */
    @Test
    public void Test() throws FileNotFoundException {
    FileInputStream fis = new FileInputStream("/Applications/study/java/freamework-study/mybaties/src/main/java/com/mybaties/demo/mybatis-config.xml");

    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(fis);

    SqlSession sqlSession = sqlSessionFactory.openSession();

    TestMapper mapper = sqlSession.getMapper(TestMapper.class);

    User user = mapper.selectByPrimary(2);

    System.out.println(user.toString());
    }

开始调试代码分析

我们知道 mabtis 的调用方法是通过接口调用的,但是接口是不能被实例化的,那具体的调用是怎么的呢?

1
TestMapper mapper = sqlSession.getMapper(TestMapper.class);

在程序调用上一端代码的时候,进入了 MapperProxy 的 invoke 方法,而且返回的是 MapperProxy 的代理对象,所以我们可以知道 mybatis 是 通过代理模式来调用接口中的方法的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}

if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}

MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}

继续跟踪

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
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
switch(this.command.getType()) {
case INSERT:
...
case UPDATE:
...
case DELETE:
...
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
break;
case FLUSH:
...
}

由于我们调用的是 select 方法 ,那么 在将会调用 sqlSession.selectOne 的方法,selectOne 方法的具体实现在 selectList

1
2
3
4
5
6
7
8
9
10
11
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
...
}

return var5;
}

sqlSession 找到对应的 MappedStatement 对象,然后委派给 executor 来完成调用

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
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//拼接sql
BoundSql boundSql = ms.getBoundSql(parameter);
//拼装缓存key
CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
...

List list;
try {
++this.queryStack;
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
if (list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//查询数据库
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
--this.queryStack;
}

...
return list;
}
}

queryFromDatabase 调用 doQuery 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;

List var10;
try {
this.flushStatements();
Configuration configuration = ms.getConfiguration();
//创建StatementHandler,在构造方法中初始化了ParameterHandler,ResultSetHandler
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
Connection connection = this.getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, this.transaction.getTimeout());
//将请求参数放入到查询sql
handler.parameterize(stmt);
var10 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}

return var10;
}

调用 jdbc 程序查询数据库,并将 jdbc 类型转成 java 类型,返回查询结果。

1
2
3
4
5
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return this.resultSetHandler.handleResultSets(ps);
}

总结

总体来说,mybatis 的程序体系并不复杂。SqlSession 代表一次完整的数据库回话,Executor 代表一次查询动作,Executor 通过委派 StatementHandler 来与数据库交互。StatementHandler 又含有 ParameterHandler 和 ResultSetHandler 两个处理器,这两个处理器 完成 jdbc 类型和 java 类型之间的相互转换。