深入分析 MyBatis 获取 SqlSession 的流程

大家好,我是 王有志,一个分享硬核 Java 技术的金融摸鱼侠。

前面我们已经完成了 MyBatis 应用程序初始化阶段的源码分析,接下来我们正式进入 MyBatis 应用程序执行阶段的源码分析。

今天我们先从 MyyBatis 应用程序获取 SqlSession 实例的部分开始,也就是图中调用 SqlSessionFactory#openSession 方法的部分,如下图所示:

深入分析 MyBatis 获取 SqlSession 的流程

SqlSessionFactory#openSession 方法分析

SqlSessionFactory 中提供了多个用于获取 SqlSession 实例的 SqlSessionFactory#openSession 方法的重载方法,用于支持以不同执行器类型,自动提交模式和事务隔离级别来获取 SqlSession 实例,也允许通过传入的数据库链接来获取 SqlSession 实例,我们以 SqlSessionFactory 接口的默认实现类 DefaultSqlSessionFactory 为例,源码如下:

public class DefaultSqlSessionFactory implements SqlSessionFactory {
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), nullfalse);
  }

  public SqlSession openSession(boolean autoCommit) {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
  }

  public SqlSession openSession(ExecutorType execType) {
    return openSessionFromDataSource(execType, nullfalse);
  }

  public SqlSession openSession(TransactionIsolationLevel level) {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
  }

  public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
    return openSessionFromDataSource(execType, level, false);
  }

  public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
    return openSessionFromDataSource(execType, null, autoCommit);
  }

  public SqlSession openSession(Connection connection) {
    return openSessionFromConnection(configuration.getDefaultExecutorType(), connection);
  }

  public SqlSession openSession(ExecutorType execType, Connection connection) {
    return openSessionFromConnection(execType, connection);
  }
}

DefaultSqlSessionFactory 实现的 SqlSessionFactory#openSession 方法最终都指向了 DefaultSqlSessionFactory 的内部方法 DefaultSqlSessionFactory#openSessionFromDataSource 方法或 DefaultSqlSessionFactory#openSessionFromConnection 方法,这两个方法的源码如下:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  final Environment environment = configuration.getEnvironment();
  final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
  Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
  final Executor executor = configuration.newExecutor(tx, execType);
  return new DefaultSqlSession(configuration, executor, autoCommit);
}

private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
  boolean autoCommit = connection.getAutoCommit();
  final Environment environment = configuration.getEnvironment();
  final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
  final Transaction tx = transactionFactory.newTransaction(connection);
  final Executor executor = configuration.newExecutor(tx, execType);
  return new DefaultSqlSession(configuration, executor, autoCommit);
}

它们的差异体现在 DefaultSqlSessionFactory#openSessionFromConnection 方法是通过传入的数据库连接来获取自动提交配置和数据库事务配置的,而 DefaultSqlSessionFactory#openSessionFromDataSource 方法则依赖于 MyBatis 核心配置文件中数据源的配置。

通常我们不会选择传入 Connection 实例的 SqlSessionFactory#openSession 方法的重载方法,所以这里我们以 DefaultSqlSessionFactory#openSessionFromDataSource 方法为例进行分析。

DefaultSqlSessionFactory#openSessionFromDataSource 方法中,我们重点关注这 4 行代码:

  • 第 3 行代码,调用 DefaultSqlSessionFactory#getTransactionFactoryFromEnvironment 方法创建 MyBatis 的事务工厂;
  • 第 4 行代码,调用 TransactionFactory#newTransaction 方法创建 MyBatis 的事务;
  • 第 5 行代码,调用 Configuration#newExecutor 方法创建 MyBatis 的执行器;
  • 第 6 行代码,调用 DefaultSqlSession 的构造方法创建 DefaultSqlSession 实例。

下面,我们对这 4 行代码中涉及到的源码和知识点和大家做一个分享。

MyBatis 事务工厂 TransactionFactory

TransactionFactory 是 MyBatiis 中提供的事务工厂接口,采用了工厂模式,它的体系结构如下:

深入分析 MyBatis 获取 SqlSession 的流程

JdbcTransactionFactory 与 ManagedTransactionFactory 是 MyBatis 中提供的事务工厂的实现,而 SpringManagedTransactionFactory 是在 MyBatis 与 Spring 的整合中提供的事务工厂的实现。

Tips:JdbcTransactionFactory 和 ManagedTransactionFactory 位于 mybatis 项目中,SpringManagedTransactionFactory 位于 mybatis-spring-boot-starter 项目中。

TransactionFactory 接口声明如下:

public interface TransactionFactory {

  default void setProperties(Properties props) {}

  /**
   * 从指定的数据库连接中获取事务
   */

  Transaction newTransaction(Connection conn);

  /**
   * 从指定的数据源配置中获取事务
   * 并且对事务级别,自动提交进行配置
   */

  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}

TransactionFactory 接口提供了两个用于获取 Transaction 实例的方法,它的实现类 JdbcTransactionFactory 和 ManagedTransactionFactory 对这两个方法的实现非常简单,源码如下:

public class JdbcTransactionFactory implements TransactionFactory {

  private boolean skipSetAutoCommitOnClose;

  @Override
  public void setProperties(Properties props) {
    if (props == null) {
      return;
    }
    String value = props.getProperty("skipSetAutoCommitOnClose");
    if (value != null) {
      skipSetAutoCommitOnClose = Boolean.parseBoolean(value);
    }
  }

  @Override
  public Transaction newTransaction(Connection conn) {
    return new JdbcTransaction(conn);
  }

  @Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit, skipSetAutoCommitOnClose);
  }
}

public class ManagedTransactionFactory implements TransactionFactory {

  private boolean closeConnection = true;

  @Override
  public void setProperties(Properties props) {
    if (props != null) {
      String closeConnectionProperty = props.getProperty("closeConnection");
      if (closeConnectionProperty != null) {
        closeConnection = Boolean.parseBoolean(closeConnectionProperty);
      }
    }
  }

  @Override
  public Transaction newTransaction(Connection conn) {
    return new ManagedTransaction(conn, closeConnection);
  }

  @Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new ManagedTransaction(ds, level, closeConnection);
  }
}

可以看到 JdbcTransactionFactory 和 ManagedTransactionFactory 在创建 Transaction 实例时,只调用了 JdbcTransaction 和 ManagedTransaction 的构造方法。

我们回到 DefaultSqlSessionFactory#openSessionFromDataSource 方法中调用的 DefaultSqlSessionFactory#getTransactionFactoryFromEnvironment 方法,源码如下:

private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
  if (environment == null || environment.getTransactionFactory() == null) {
    return new ManagedTransactionFactory();
  }
  return environment.getTransactionFactory();
}

DefaultSqlSessionFactory#getTransactionFactoryFromEnvironment 方法的作用是,根据不同的配置情况创建合适的事务工厂,这里是一个简单工厂的应用。

再来关注下 TransactionFactory#newTransaction 方法,源码如下:

public class JdbcTransactionFactory implements TransactionFactory {
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit, skipSetAutoCommitOnClose);
  }
}

public class ManagedTransactionFactory implements TransactionFactory {
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new ManagedTransaction(ds, level, closeConnection);
  }
}

虽然说不同的工厂有不同的实现,但是看到实现源码后,可以发现这里只是调用了不同的 Transaction 实现类的构造方法。

DefaultSqlSessionFactory, TransactionFactory 与 Transaction 使用了工厂模式进行设计,DefaultSqlSessionFactory#getTransactionFactoryFromEnvironment 方法与 TransactionFactory 组成了用于创建 TransactionFactory 实例的简单工厂,TransactionFactory 与 Transaction 组成了用于创建 Transaction 实例的工厂模式,这是一个比较典型的工厂模式的应用,文章最后的部分我们还会再讨论这里工厂模式的应用。

MyBatis 的事务 Transaction

Transaction 是 MyBatis 中提供的事务接口,是对数据库事务的抽象描述,它的体系结构如下:

深入分析 MyBatis 获取 SqlSession 的流程

Transaction 与 TransactionFactory 一样,有 3 个接口实现,分别对应了 TransactionFactory 接口的 3 个具体的事务工厂的实现。

Transaction 接口的声明如下:

public interface Transaction {

  /**
   * 获取数据库链接,即 Connection 实例
   */

  Connection getConnection() throws SQLException;

  /**
   * 提交事务
   */

  void commit() throws SQLException;

  /**
   * 回滚事务
   */

  void rollback() throws SQLException;

  /**
   * 关闭数据库链接
   */

  void close() throws SQLException;

  /**
   * 获取事务超时时间
   */

  Integer getTimeout() throws SQLException;
}

可以看到,Transaction 接口中定义了获取数据库链接,提交事务和回滚事务等方法,在具体方法的实现上,JdbcTransaction 和 ManagedTransaction 是有明显的差别的:

  • JdbcTransaction 依赖 JDBC 实现事务的提交和回滚;
  • ManagedTransaction 依赖容器实现事务的提交和回滚。

具体的源码比较简单,大家自行阅读即可。

Configuration#newExecutor 方法分析

Configuration#newExecutor 方法用于创建 Executor 实例,该方法的源码如下:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : 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);
  }
  return (Executor) interceptorChain.pluginAll(executor);
}

可以看到,Configuration#newExecutor 方法也是简单工厂的实现,用于根据不同类型的 Executor 配置或者是缓存配置来创建 Executor 实例。关于 Executor 的内容我们在 《MyBatis中一级缓存的配置与实现原理》 中已经了解过了一部分,更多的内容我们在后面的文章中和大家分享。

这里再额外的提一点,在 Configuration#newExecutor 方法中我们能看到的是对工厂模式的应用,除此之外,在 Executor 中还应用到了模板方法和委派模式,而第 14 行代码中出现的用于保存 Executor 实例的 interceptorChain 容器,它使用了责任链模式,用于修改或是拓展 Executor 的行为。

除了 DefaultSqlSessionFactory,TransactionFactory 和 Transaction 中应用的工厂模式外,上面提到的场景中提到的 MyBatis 对于设计模式的应用都是不错的学习资料,由此可见,学习源码真的能够提升我们的代码编写水平。

MyBatis 的数据库会话 SqlSession

DefaultSqlSessionFactory#openSessionFromDataSource 方法的最后,调用了 SqlSession 的实现类 DefaultSqlSession 的构造方法。

DefaultSqlSession 是 MyBatis 中 SqlSession 接口的默认实现,用于表示一个数据库会话,它的体系结构如下图所示:

深入分析 MyBatis 获取 SqlSession 的流程

关于 DefaultSqlSession 与 SqlSessionManager 的区别,我们已经在 《MyBatis应用的组成》 中提到过了,这里我们就不赘述了。

SqlSession 的接口声明如下:

public interface SqlSession extends Closeable {

  <T> selectOne(String statement);

  <T> selectOne(String statement, Object parameter);

  <E> List<E> selectList(String statement);

  <E> List<E> selectList(String statement, Object parameter);

  <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);

  <K, V> Map<K, V> selectMap(String statement, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);

  <T> Cursor<T> selectCursor(String statement);

  <T> Cursor<T> selectCursor(String statement, Object parameter);

  <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);

  void select(String statement, Object parameter, ResultHandler handler);

  void select(String statement, ResultHandler handler);

  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);

  int insert(String statement);

  int insert(String statement, Object parameter);

  int update(String statement);

  int update(String statement, Object parameter);

  int delete(String statement);

  int delete(String statement, Object parameter);

  void commit();

  void commit(boolean force);

  void rollback();

  void rollback(boolean force);

  List<BatchResult> flushStatements();

  void clearCache();

  Configuration getConfiguration();

  <T> getMapper(Class<T> type);

  Connection getConnection();

  @Override
  void close();
}

SqlSession 接口中提供了非常多的方法,但是整理可以分为 3 大类:

  • 执行 SQL 语句的相关方法,如 SqlSession#selectOne 方法,SqlSession#insert 方法等;
  • 数据库事务相关方法,如 SqlSession#commit 方法,SqlSession#rollback 方法等;
  • MyBatis 应用相关方法,如 SqlSession#clearCache 方法,SqlSession#getMapper 方法等。

关于 SqlSession 的默认实现类 DefaultSqlSession 对 SqlSession 接口中的方法实现,我们下一篇文章再做分析。

回顾与思考

到这里,我们已经从整体上了解了 MyBatis 获取 SqlSession 的流程了,现在我们用一张图来总结下源码的执行流程,如下:

深入分析 MyBatis 获取 SqlSession 的流程

今天涉及到的源码比较简单,主要是介绍了简单介绍了 MyBatis 中 Transaction 组件和 SqlSession 组件的功能。

最后,我们来讨论下在这段源码中 MyBatis 对于工厂模式应用的方式,以及为什么我说“除了 DefaultSqlSessionFactory,TransactionFactory 和 Transaction 中应用的工厂模式外,上面提到的场景中提到的 MyBatis 对于设计模式的应用都是不错的学习资料”。

工厂模式与建造者模式的区别

MyBatiis 中创建 SqlSession 实例和 Transaction 实例时都使用了工厂模式,工厂模式与我们在 《深入分析 MyBatis 应用程序初始化流程》 中提到的建造者模式一样,也是一种常用的创建型设计模式。虽然建造者模式和工厂模式都是创建型的设计模式,但是他们有比较明显的区别。

建造者模式更关注与被创建实例的组成部分等细节,以及如何组合这些组成部分。例如使用 SqlSessionFactoryBuilder 创建 SqlSeessionFactoryy 实例时,SqlSessionFactoryBuilder#build 方法中屏蔽了解析 MyBatis 核心配置文件的复杂过程,使得我们只需要通过一两行简单的代码就可以完成 SqlSessionFactory 实例的创建,而不必关注创建过程中做了哪些事情。

工厂模式更关注实例的创建,而不是实例的组成部分等细节。这里就不以 MyBatis 中的源码举例了,因为我觉得 MyyBatis 在 SqlSessionFactory 以及 TransactionFactory 上的处理并不是很好。

为什么说 MyBatis 对于工厂模式的应用并不好?

如果是我的话,我会添加一个简单工厂 WyzTransactionFactory(随便起的名字,不要吐槽~),将 DefaultSqlSessionFactory#getTransactionFactoryFromEnvironment 方法的逻辑收敛到其中,如下图所示:

深入分析 MyBatis 获取 SqlSession 的流程

WyzTransactionFactory 作为 TransactionFactory 的简单工厂,用于根据 Environment 实例创建生产 Transaction 的具体的 TransactionFactory 实现,而 TransactionFactory 是抽象工厂接口,用于定义创建 Transaction 实例的具体工厂,这样就形成了简单工厂(WyzTransactionFactory)和工厂模式(TransactionFactory)的组合,这也是在使用工厂时的一种常用用法。

我这么做的原因是:对于 DefaultSqlSessionFactory 来说,它的业务逻辑就是创建 SqlSession 实例,因此在 DefaultSqlSessionFactory 中所有的代码应该都是围绕着 SqlSession 实例的创建而展开的,虽然说 DefaultSqlSessionFactory#getTransactionFactoryFromEnvironment 方法也可以算是其中的一部分,但既选择使用工厂模式,那就不应该把创建 TransactionFactory 实例的逻辑暴露到 SqlSessionFactory 中,而是应该收敛到 TransactionFactory 中。

换句话说,DefaultSqlSessionFactory 并不关心 TransactionFactory 的创建过程,它只需要拿到一个 TransactionFactory 实例并且能够创建 Transaction 实例就可以了,因此 DefaultSqlSessionFactory#getTransactionFactoryFromEnvironment 方法的逻辑对于 DefaultSqlSessionFactory 来说是多余的。

为什么使用工厂模式?

关于工模式我们再聊一点,你可能会感到奇怪,工厂模式的意义是什么?因为从上面的代码来看,工厂模式并没有解决业务逻辑的复杂度,那么为什么要使用工厂模式?

我给出的答案是,工厂模式提供了一种良好的分解和转移复杂的设计方式

千万不要小看复杂度转移,经过良好的设计将业务逻辑的复杂度分解并转移到合理的位置,能够显著的提升程序的可维护性和可观测性。近年来比较流行的 DDD,就是利用充血模型和领域层的设计,来实现管理,分解和转移复杂度的。

最后我们再来看 Configuration#newExecutor 方法,这也是一个简单工厂的应用,但是 MyBatis 将这个方法与 Configuration 类耦合到了一起,使得本来就已经非常臃肿的 Configuration 类更加雪上加霜。

虽然,Executor 实例的创建与 Configuration 实例密切相关,但是 DefaultSqlSessionFactory 实例中已经存储了完整的的 Configuration 实例,所以可以提供独立的简单工厂 ExecutorFactory,通过传入 Configuration 实例去构建 Executor 实例,而不是将创建 Executor 实例的逻辑耦合在 Configuration 中。

好了,今天的内容到这里就结束了,最后欢迎关注 王有志,一起探讨,学习~~


深入分析 MyBatis 获取 SqlSession 的流程


原文始发于微信公众号(王有志):深入分析 MyBatis 获取 SqlSession 的流程

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/301708.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!