有关数据库事务的基础知识请翻看前面Spring事务管理的几篇文章,与Spring事务管理相比,Mybatis的事务管理非常简单。

底层结构

先熟悉一下Mabtis事务相关的几个概念、或者说他的底层结构。

TransactionFactory:事务工厂,Mybatis执行数据库操作、获取SqlSession之前要通过TransactionFactory来获取事务管理器。
image.png

Transaction:事务管理器,Mybatis最终通过Transaction来完成对数据库操作的事务控制。
image.png

TransactionFactory的配置

从TransactionFactory的类图可以看到Mybatis共提供了3种不同的事务工厂:

  1. JDBCTransactionFactory:基于JDBC的事务工厂,负责创建JDBCTransaction。
  2. ManagedTransactionFactory:“被管理的事务工厂”,负责创建ManagedTransaction。
  3. SpringManagedTransactionFactory:Spring管理的事务工厂,负责创建SpringManagedTransaction。

可以通过Mybatis的环境配置Environment来指定具体使用哪一种事务管理工厂:

 <environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>

通过transactionManager属性可以指定type=“JDBC”或者“MANAGED”,分配对应上述的JDBCTransactionFactory和ManagedTransactionFactory。

然而,如果你正在使用Spring + Mybatis,则SpringManagedTransactionFactory会覆盖掉以上两种设置。

Transaction

Mybatis的事务控制行为最终由Transaction完成,Transaction的接口定义非常简单:

public interface Transaction {

  Connection getConnection() throws SQLException;
  void commit() throws SQLException;
  void rollback() throws SQLException;
  void close() throws SQLException;
  Integer getTimeout() throws SQLException;

}

getConnection负责获取数据库连接,commit提交事务,rollback回滚事务,close关闭数据库连接,getTimeout获取事务超时时间设置。

Mybatis的三种不同的事务管理器需要实现Transaction接口的以上方法从而实现事务控制。

JDBCTransaction

如果我们的项目配置了Mybatis的事务管理器为JDBC的话,Mybatis会使用JDBCTransaction来最终完成事务控制,其实也就是上述Transaction接口的几个方法的实现。

JDBCTransactoin源码非常简单,一目了然。如果事务已经开启的话(数据库连接autoCommit=false)则commit和rollback都是直接调用数据库连接的commit和rollback方法完成事务的提交或回滚。

close方法稍微有一点点特殊,就是在关闭数据库连接之前还调用了一个resetAutoCommit()方法,将数据库连接的autoCommit设置为true(默认值)。其实这个动作是为了兼容连接池的配置,如果是池化管理数据库连接的话,close方法的其实是将connection交给连接池而并非真正的关闭,在归还连接池之前恢复其默认值应该也属于“最佳实践”的一种。

  @Override
  public void close() throws SQLException {
    if (connection != null) {
      resetAutoCommit();
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + connection + "]");
      }
      connection.close();
    }
  }

JDBCTransactoin其实属于手动控制事务的一种,目前应用应该非常少了。因为他相当于是在获取SqlSession的同时获取数据库连接并开启了事务,而我们应用中一个交易往往需要多次执行数据库操作,也就会多次获取SqlSession,因此也就不太容易通过JDBCTransactoin控制事务。

一般情况下我们的项目还是要交给第三方事务管理器来实现事务控制,比如Spring的事务管理机制。

ManagedTransaction

MangedTransaction的实现就更简单了,因为他把事务控制交给了不知名的第三方,所以他完全不管了。

除了获取和关闭链接外,事务的提交和回滚都是空架子,啥也不干:

  @Override
  public void commit() throws SQLException {
    // Does nothing
  }

SpringManagedTransaction

如果使用Spring+Mybatis的话,Mybatis使用SpringManagedTransaction进行事务控制。

getConnection方法要借助DataSourceUtil的getConnection,我们看一下DataSourceUtil已经是在org.springframework.jdbc.datasource包下,说明控制权已经交给Spring了:

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
        try {
            return doGetConnection(dataSource);
        }
        catch (SQLException ex) {
            throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex);
        }
        catch (IllegalStateException ex) {
            throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + ex.getMessage());
        }
    }

调用了doGetConnection方法,其中出现了我们熟悉的TransactionSynchronizationManager和ConnectionHolder:

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
        Assert.notNull(dataSource, "No DataSource specified");

        ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
            conHolder.requested();
            if (!conHolder.hasConnection()) {
                logger.debug("Fetching resumed JDBC Connection from DataSource");
                conHolder.setConnection(fetchConnection(dataSource));
            }
            return conHolder.getConnection();
        }

具体可以参考前面有关Spring事务控制的相关文章。

commit和rollback的代码更简单:

  @Override
  public void commit() throws SQLException {
    if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
      LOGGER.debug(() -> "Committing JDBC Connection [" + this.connection + "]");
      this.connection.commit();
    }
  }

工作原理是:当前connection与从Spring的ConnectionHolder中获取到的connection是否是同一对象(this.isConnectionTransactional==true),是的话则啥也不干(交给Spring的事务管理去干),不是的话就与JDBCTransaction一样、自己提交事务。

总结

Mybatis可以自己处理事务,也可以撒手不管、交给其他事务处理框架去处理。

Mybatis并非事务处理专家,绝大多数情况下Mybatis都是撒手不管的,把事务交给Spring等更加擅长事务管理的框架去处理。

Spring事务管理相关内容请参考:https://segmentfault.com/a/11...

上一篇 Mybatis缓存机制
下一篇 Mybatis的Mapper代理对象生成及调用过程


45 声望17 粉丝