在我的博客阅读本文会有更好的阅读体验哦!博客: www.lxiaocode.com
最近看些 MyBatis 的源码,虽说不是很深入但是也对 MyBatis 的查询的执行流程有了整体的了解。本文将会从 selectOne 方法开始一直追溯到 JDBC 的访问数据库,来看一看 MyBatis 到底做了什么,简单的对 MyBatis 的查询接口有个简单的整体的了解。
文本会以 MyBatis 源码为主,不考虑 Spring 的封装。
第零步:MyBatis 查询示例
MyBtais 3 官网:https://mybatis.org/mybatis-3/zh/getting-started.html
在开始阅读源码之前,我们先来复习一下使用 MyBatis 调用查询接口进行查询的方法,下面是从 MyBatis 3 的官方中获取的查询示例:
// 使用 XML 构建 SqlSessionFactory
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取 SqlSession 调用 selectOne 查询接口
try (SqlSession session = sqlSessionFactory.openSession()) {
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}
从上面的示例中可以看出,MyBatis 对我们开放的顶层 API 接口是由 SqlSession 接口所提供的。我们接下来就由 SqlSession.selectOne 为起点开始阅读 MyBatis 的查询接口源码。
第一步:SqlSession 顶层接口
1.1 SqlSession 接口
SqlSession 作为 MyBatis 的顶层接口,为我们提供了许多功能。通过这个接口,我们可以执行 SQL 命令、获取映射器和管理事务。本文会聚焦在 selectOne 方法上进行解析。
public interface SqlSession extends Closeable {
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
// 此处省略其他方法...
}
1.2 DefaultSqlSession 实现类
从类名我们就知道,这是 SqlSeesion 接口的默认实现类。所以说 SelectOne 的默认实现也在这里。
// 这是 SqlSession 的默认实现
// 注意,这个类不是线程安全的
public class DefaultSqlSession implements SqlSession {
// 配置文件
private final Configuration configuration;
// 执行器
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
// 两个构造器
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
public DefaultSqlSession(Configuration configuration, Executor executor) {
this(configuration, executor, false);
}
// 无参查询接口
@Override
public <T> T selectOne(String statement) {
return this.selectOne(statement, null);
}
// 1. 我们根据示例中调用的查询接口,最后会进入到这个实现方法中来。
// 这个方法非常简单,会直接调用 selectList 方法进行查询
// 然后检查返回是否为一条结果,如果存在多条结果则会抛出异常
@Override
public <T> T selectOne(String statement, Object parameter) {
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
// 无参的 list 查询接口
@Override
public <E> List<E> selectList(String statement) {
return this.selectList(statement, null);
}
// 2. MyBatis 将所有的 selectOne 查询都转换成了 selectList 查询
// 这个方法是 selectList 的一个重载方法,会获取一个 RowBounds 实例对象
// 然后调用另一个重载的方法
// 也就是说,这个方法是目的就是获取一个默认的 RowBounds 实例对象
// RowBounds 对象主要用于分页查询,因为我们调用的查询接口不涉及到分页
// 所以才会进到这个方法中,给我们一个默认的实例
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
// 3. 在这个 selectList 的重载方法中终于要开始执行查询操作了
// 在这个方法中最终会调用 executor.query 执行查询操作
// 在此之前我们还需要为查询做一些准备
// 还记得示例中 statement 参数的吗? -> "org.mybatis.example.BlogMapper.selectBlog"
// 这是一条 Mapper 接口方法的路径,每一个 Mapper 接口方法都对应了一条在 xml 映射文件中的 SQL 语句
// 我需要使用这个参数从 configuration 中获取 MappedStatement 实例对象
// 这个 MappedStatement 实例对象就是用于封装这一条 SQL 语句
// 在这个方法中还有 wrapCollection(parameter) 用于包装查询参数
// Executor.NO_RESULT_HANDLER 获取一个为 null 的结果处理器
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// 此处省略其他方法...
}
第二步:Executor 执行器
2.1 Executor 接口
在第一步,我们在 SqlSession 完成了查询前的参数准备,然后调用了 executor.query 方法使用执行器进行查询操作。老规矩,我们先来看看 Executor 接口。
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement ms, Object parameter) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
// 接下来我们会进入这个方法的实现
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
List<BatchResult> flushStatements() throws SQLException;
void commit(boolean required) throws SQLException;
void rollback(boolean required) throws SQLException;
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
boolean isCached(MappedStatement ms, CacheKey key);
void clearLocalCache();
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
Transaction getTransaction();
void close(boolean forceRollback);
boolean isClosed();
void setExecutorWrapper(Executor executor);
}
2.2 BaseExecutor 实现类
从类名我们就知道,这是 Executor 接口的一个抽象实现类。除了提供 Executor 接口方法的实现,还提供了其他的抽象方法让子类实现。
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
protected Transaction transaction;
protected Executor wrapper;
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
protected PerpetualCache localCache;
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
protected int queryStack;
private boolean closed;
// 构造器
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
// 1. 紧接着 SqlSession 调用执行器进行查询操作,会进入到执行的 query 方法
// 首先在方法中从 MappedStatement 实例中使用查询参数获取了 BoundSql 实例对象
// 这个 BoundSql 这个对象用于保存本次执行的 SQL 语句
// 如何使用这个 BoundSql 实例对象创建出一个用于查询缓存的 CacheKey
// 最后会带着这个这个 CacheKey 对象进入到 query 的重载方法中
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
// 2. 当我们获取了本次执行的 SQL 语句的 CacheKey 之后会进入到这个重载方法中
// 这个方法看起来很长,起始主要的工作就是使用这个 CacheKey 对象来查询缓存
// 如果存在缓存就可以直接返回结果,不需要连接数据库查询
// 如果没有缓存就会调用 queryFromDatabase 方法,进行进一步的操作
@SuppressWarnings("unchecked")
@Override
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());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
// 3. 在没有缓存的情况下,会进入到这个方法中
// 在执行查询之前,会先使用 CacheKey 保存一条用于占位的缓存信息
// 然后再调用 doQuery 方法进行查询操作
// 完成查询后会删除占位的缓存,然后将查询的结果缓存起来
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
// 4. 在 queryFromDatabase 方法中会调用 doQuery 进行查询操作
// 而 doQuery 方法是 BaseExecutor 的一个抽象方法
// 我们需要进入 BaseExecutor 的子类中继续进行查询操作
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
// 此处省略其他方法...
}
2.3 SimpleExecutor 实现类
SimpleExecutor 是 BaseExecutor 比较常用的实现类。
public class SimpleExecutor extends BaseExecutor {
// 构造器
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
// 1. 进入到这个方法中,那么连接数据库不远了
// 在这个方法中,首先会从 configuration 中获取 StatementHandler 实例对象
// StatementHandler 是一个非常核心的组件
// StatementHandler 主要负责处理 JDBC 的 Statement 的交互,以及转换结果集
// 紧接着在 StatementHandler 中获取 Statement 实例对象
// 调用 StatementHandler 的 query 方法
@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);
}
}
}
第三步:StatementHandler 处理器
StatementHandler 接口
StatementHandler 这是一个与 JDBC 交互的处理器,可以说是 MyBatis 的核心。
public interface StatementHandler {
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
void parameterize(Statement statement)
throws SQLException;
void batch(Statement statement)
throws SQLException;
int update(Statement statement)
throws SQLException;
// 接下来我们会进入这个方法,进行 JDBC 处理
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
<E> Cursor<E> queryCursor(Statement statement)
throws SQLException;
BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}
SimpleStatementHandler 实现类
public class SimpleStatementHandler extends BaseStatementHandler {
// 构造器
public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
// 1. statement.execute(sql) 方法就可以说明一切了
// 这就是 JDBC 中执行 SQL 语句的方法
// 这个方法一目了然,先从 boundSql 中获取需要执行的 SQL 语句
// 然后使用 execute 执行 SQL yuju
// 接着返回查询结果
// 后面就是一直返回到给顶层 API,放回给调用的用户
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.handleResultSets(statement);
}
}
最后的总结
流程图示

流程总结
-
第一步 SqlSession 顶层 API:
在第一步中,主要是准备查询所需的组件。比较重要的就是 MappedStatement 实例对象,还有一些其他的组件,比如:RowBounds 分页对象、对查询参数进行包装、结果处理器等。
-
第二步 Executor 执行器:
在第二步中,主要进行查询执行。查询执行分为两部分:1. 从缓存中查询。2. 使用 StatementHandler 从数据库中查询。
-
第三步 StatementHandler 处理器
在第三步中,就是与 JDBC 进行交互的部分。使用 JDBC 连接数据库。
到这里本文就结束了,其实阅读源码对于开发来说其实并没有实际的帮助。但是我们我们所使用的工具(框架)有了更深的了解,可能在开发中会更有自信吧。这次其实并没有过于深入的研究源码,仅仅是顺这一条线大致梳理了一下 MyBatis 执行的 SQL 命令的流程而已。