系列内容回顾:
前面的文章分析了Mybatis是如何在给定接口的情况下, 代替我们生成代理对象的, 这篇文章接着带大家来学习Mybatis.
在之前提到, 无论是用代理生成Dao类还是自己手写Dao类, 都是用工厂方法获得一个SqlSession
, 然后调用其select
, update
, insert
方法; 我们也知道, 如果用最原始的JDBC的方法来写, 通常落到执行层面, 通常就是pstmt.executeUpdate
, pstmt.executeQuery
, pstmt.execute
, stmt.execute
这样的一些方法. 那么Mybatis
中是如何代替我们用SqlSession
的方法去操纵底层JDBC的, 这就是今天想分析的内容.
SqlSession
是Mybatis中非常重要的接口, 通过定义了对数据库的常见操作的方法, 屏蔽了底层jdbc细节, 我们先看下该接口提供的方法都有哪些.
可以看到, 除了commit, rollback这样的事务相关的方法, 大多数方法支持的是各种模式的CRUD. 我们可以将他们分为两个部分, 一个是select相关的, 一个是update相关的(update/delete/insert), 通过阅读源码可以发现后者中的方法最终都是对update(String statement, Object parameter)
这样一个方法进行的封装; 而前者中提供的各种方法是对
select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler)
selectCursor(String statement, Object parameter, RowBounds rowBounds)
selectList(String statement, Object parameter, RowBounds rowBounds)
进行的封装. 因此接下来的重点就是上述的四个方法.
update(String statement, Object parameter)
方法的底层调用的是executor.update(MappedStatement ms, Object parameter)
方法, executor 则是SqlSession中执行数据库操作的实际对象, 通过跟踪代码, 可以看到, executor是个一个接口, 里面封装了query和update两种sql statement的执行模型, 这个接口有的接口大致如下:
在SqlSession中, 实际实际引入的executor的实现类是CachingExecutor
, 但是注意到CachingExecutor
本身是一个装饰器模式, 用来处理和查询缓存相关的工作, 其真正功能由成员Executor delegate
完成, delegate是一个SimpleExecutor
的实例, 这个类的结构很简单
/**
* @author Clinton Begin
*/
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
}
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
}
@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) {
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
}
}
甚至没有query方法, 因此我们可以想到, 它应该是继承了父类BaseExecutor
的方法, 基类中的update方法如下
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache(); //使缓存失效
return doUpdate(ms, parameter);
}
在update
中, 方法调用了doUpdate
方法, 而doUpdate
在BaseExecutor
中是个抽象方法, 它的实际实现位于SimpleExecutor
中,
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt); // 实际执行的preparedStatement的位置
} finally {
closeStatement(stmt);
}
}
继续跟着handler看下去, 可以看到, StatementHandler也是一个接口, 用于处理stmt应该如何执行, 它的接口如下:
这里调用的是PreparedStatementHandler
中的update方法, 这个方法如下:
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute(); // 这里看到了我们熟悉的jdbc方法, 也就是在这里实际调用了JDBC底层
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
结论, 看到这里我们基本把SqlSession.update
的调用关系理顺了, 是通过executor调用StatementHandler去执行实际的JDBC语句.
query方法的返回结果通常是比较复杂的, 在xml配置中, 我们要么通过resultType
要么通过resultMap
指定如何解析, 这部分处理都在ResultHandler中进行. 此外查询需求中常见的需要对查询结果进行偏移, 这部分是由RowBounds这个类来表示. select
和selectList
在底层调用的都是executor.query
方法
这个类比较简单, 它只包含两个成员变量, 默认情况是无偏移, limit为Integer.MAX_VALUE
, 上述参数用一个常量对象DEFAULT
来表示.
public static final int NO_ROW_OFFSET = 0;
public static final int NO_ROW_LIMIT = Integer.MAX_VALUE;
public static final RowBounds DEFAULT = new RowBounds();
private final int offset; // 查询偏移
private final int limit; // 查询的条数限制
这是一个结果处理的接口, 用于将结果重新映射成java对象, 它的结构如下
这个类中包含了完成动态内容处理的实际的Sql语句, 可能会包括?这样的占位符以及占位符对应的parameter的列表, 列表的每个元素保存了占位符的某个位置的jdbc类型, java类型等信息, 还可以包括一些参数的额外信息, 比如要如何从实体类中读取数据填到placeholder中的映射关系.
和update很相似的, executor.select落到实处也是执行的SimpleExecutor.doQuery
方法, 方法如下:
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
在PreparedStatementHandler中, 对于query方法的实现也很直接了当,
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute(); // 底层调用JDBC的方法
return resultSetHandler.handleResultSets(ps);
}
用jdbc执行后, 用resultSetHandler进行处理. 在这个对象的方法中调用了resultSetHandler
对结果行进行解析, 并存储到对象. 这部分在之后可能会单独出一篇文章谈查询结果的解析, 限于篇幅的原因, 这里不再展开叙述.