# MyBatis 源码分析(七):接口层

# sql 会话创建工厂

SqlSessionFactoryBuilder 经过复杂的解析逻辑之后,会根据全局配置创建 DefaultSqlSessionFactory,该类是 sql 会话创建工厂抽象接口 SqlSessionFactory 的默认实现,其提供了若干 openSession 方法用于打开一个会话,在会话中进行相关数据库操作。这些 openSession 方法最终都会调用 openSessionFromDataSourceopenSessionFromConnection 创建会话,即基于数据源配置创建还是基于已有连接对象创建。

# 基于数据源配置创建会话

要使用数据源打开一个会话需要先从全局配置中获取当前生效的数据源环境配置,如果没有生效配置或没用设置可用的事务工厂,就会创建一个 ManagedTransactionFactory 实例作为默认事务工厂实现,其与 MyBatis 提供的另一个事务工厂实现 JdbcTransactionFactory 的区别在于其生成的事务实现 ManagedTransaction 的提交和回滚方法是空实现,即希望将事务管理交由外部容器管理。

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      // 获取事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 创建事务配置
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 创建执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      // 创建 sql 会话
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  /**
   * 获取生效数据源环境配置的事务工厂
   */
  private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    if (environment == null || environment.getTransactionFactory() == null) {
      // 未配置数据源环境或事务工厂,默认使用 ManagedTransactionFactory
      return new ManagedTransactionFactory();
    }
    return environment.getTransactionFactory();
  }

随后会根据入参传入的 execType 选择对应的执行器 ExecutorexecType 的取值来源于 ExecutorType,这是一个枚举类。在下一章将会详细分析各类 Executor 的作用及其实现。

获取到事务工厂配置和执行器对象后会结合传入的数据源自动提交属性创建 DefaultSqlSession,即 sql 会话对象。

# 基于数据库连接创建会话

基于连接创建会话的流程大致与基于数据源配置创建相同,区别在于自动提交属性 autoCommit 是从连接对象本身获取的。

  private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
    try {
      // 获取自动提交配置
      boolean autoCommit;
      try {
        autoCommit = connection.getAutoCommit();
      } catch (SQLException e) {
        // Failover to true, as most poor drivers
        // or databases won't support transactions
        autoCommit = true;
      }
      final Environment environment = configuration.getEnvironment();
      // 获取事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 创建事务配置
      final Transaction tx = transactionFactory.newTransaction(connection);
      // 创建执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      // 创建 sql 会话
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

# sql 会话

SqlSessionMyBatis 面向用户编程的接口,其提供了一系列方法用于执行相关数据库操作,默认实现为 DefaultSqlSession,在该类中,增删查改对应的操作最终会调用 selectListselectupdate 方法,其分别用于普通查询、执行存储过程和修改数据库记录。

  /**
   * 查询结果集
   */
  @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();
    }
  }

  /**
   * 调用存储过程
   */
  @Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  /**
   * 修改
   */
  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

以上操作均是根据传入的 statement 名称到全局配置中查找对应的 MappedStatement 对象,并将操作委托给执行器对象 executor 完成。selectselectMap 等方法则是对 selectList 方法返回的结果集做处理来实现的。

此外,提交和回滚方法也是基于 executor 实现的。

  /**
   * 提交事务
   */
  @Override
  public void commit(boolean force) {
    try {
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  /**
   * 回滚事务
   */
  @Override
  public void rollback(boolean force) {
    try {
      executor.rollback(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  /**
   * 非自动提交且事务未提交 || 强制提交或回滚 时返回 true
   */
  private boolean isCommitOrRollbackRequired(boolean force) {
    return (!autoCommit && dirty) || force;
  }

在执行 update 方法时,会设置 dirty 属性为 true ,意为事务还未提交,当事务提交或回滚后才会将 dirty 属性修改为 false。如果当前会话不是自动提交且 dirty 熟悉为 true,或者设置了强制提交或回滚的标志,则会将强制标志提交给 executor 处理。

# sql 会话管理器

SqlSessionManager 同时实现了 SqlSessionFactorySqlSession 接口,使得其既能够创建 sql 会话,又能够执行 sql 会话的相关数据库操作。

  /**
   * sql 会话创建工厂
   */
  private final SqlSessionFactory sqlSessionFactory;

  /**
   * sql 会话代理对象
   */
  private final SqlSession sqlSessionProxy;

  /**
   * 保存线程对应 sql 会话
   */
  private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();

  private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }

  @Override
  public SqlSession openSession() {
    return sqlSessionFactory.openSession();
  }

  /**
   * 设置当前线程对应的 sql 会话
   */
  public void startManagedSession() {
    this.localSqlSession.set(openSession());
  }

  /**
   * sql 会话代理逻辑
   */
  private class SqlSessionInterceptor implements InvocationHandler {

    public SqlSessionInterceptor() {
        // Prevent Synthetic Access
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 获取当前线程对应的 sql 会话对象并执行对应方法
      final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
      if (sqlSession != null) {
        try {
          return method.invoke(sqlSession, args);
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      } else {
        // 如果当前线程没有对应的 sql 会话,默认创建不自动提交的 sql 会话
        try (SqlSession autoSqlSession = openSession()) {
          try {
            final Object result = method.invoke(autoSqlSession, args);
            autoSqlSession.commit();
            return result;
          } catch (Throwable t) {
            autoSqlSession.rollback();
            throw ExceptionUtil.unwrapThrowable(t);
          }
        }
      }
    }
  }

SqlSessionManager 的构造方法要求 SqlSessionFactory 对象作为入参传入,其各个创建会话的方法实际是由该传入对象完成的。执行 sql 会话的操作由 sqlSessionProxy 对象完成,这是一个由 JDK 动态代理创建的对象,当执行方法时会去 ThreadLocal 对象中查找当前线程有没有对应的 sql 会话对象,如果有则使用已有的会话对象执行,否则创建新的会话对象执行,而线程对应的会话对象需要使用 startManagedSession 方法来维护。

之所以 SqlSessionManager 需要为每个线程维护会话对象,是因为 DefaultSqlSession 是非线程安全的,多线程操作会导致执行错误。如上文中提到的 dirty 属性,其修改是没有经过任何同步操作的。

# 小结

SqlSessionMyBatis 提供的面向开发者编程的接口,其提供了一系列数据库相关操作,并屏蔽了底层细节。使用 MyBatis 的正确方式应该是像 SqlSessionManager 那样为每个线程创建 sql 会话对象,避免造成线程安全问题。

  • org.apache.ibatis.session.SqlSessionFactorysql 会话创建工厂。
  • org.apache.ibatis.session.defaults.DefaultSqlSessionFactorysql 会话创建工厂默认实现。
  • org.apache.ibatis.session.SqlSessionsql 会话。
  • org.apache.ibatis.session.defaults.DefaultSqlSessionsql 会话默认实现。
  • org.apache.ibatis.session.SqlSessionManagersql 会话管理器。

# 注释源码

注释源码