MyBatis 从selectOne到JDBC的源码解析

本文从MyBatis的selectOne方法入手,深入解析MyBatis查询执行流程,涉及SqlSession接口、Executor执行器和StatementHandler处理器。通过分析,了解MyBatis如何从顶层API到JDBC执行SQL,帮助理解MyBatis框架的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在我的博客阅读本文会有更好的阅读体验哦!博客: 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 命令的流程而已。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值