前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MyBatis源码阅读(十一) --- MyBatis事务管理机制

MyBatis源码阅读(十一) --- MyBatis事务管理机制

作者头像
终有救赎
发布2024-01-10 10:15:10
930
发布2024-01-10 10:15:10
举报
文章被收录于专栏:多线程多线程
一、概述

事务的概念,大家都不会陌生。在我们写增删改的时候,我们肯定都需要加上事务,来保证数据的一致性。MyBatis作为Java语言的数据库框架,对数据库的事务管理是其非常重要的一个方面。在Mybatis中,同样提供了事务的功能,所以我们有必要了解一下MyBatis的事务管理的实现机制。

二、Mybatis事务管理机制

Mybatis也提供了事务的机制,MyBatis将事务抽象成了Transaction接口。Transaction接口定义如下:

代码语言:javascript
复制
//包装数据库连接, 处理connection连接的生命周期,包括:它的创建、准备、提交/回滚和关闭。
public interface Transaction {
 
  /**
   * 获取数据库连接
   */
  Connection getConnection() throws SQLException;
 
  /**
   * 提交
   */
  void commit() throws SQLException;
 
  /**
   * 回滚
   */
  void rollback() throws SQLException;
 
  /**
   * 关闭数据库连接
   */
  void close() throws SQLException;
 
  /**
   * 获取事务超时时间
   */
  Integer getTimeout() throws SQLException;
 
}

Transaction的继承关系图:

图片.png
图片.png

如上图,Transaction有两个实现子类,对应着MyBatis的事务管理的两种形式:

  • JdbcTransaction:直接使用JDBC提交和回滚功能,依赖于从数据源获取到的连接【java.sql.Connection对象】来管理事务;
  • ManagedTransaction:这种机制MyBatis自身不会去实现事务管理,而是让程序的容器如(JBOSS,Weblogic)来实现对事务的管理;

事务的配置,在Mybatis全局配置文件中,我们进行了事务管理器相关的配置,它的作用就是生成一个事务工厂,然后事务工厂用于创建事务对象。

代码语言:javascript
复制
<environments default="development">
    <environment id="development">
        //指定事务管理器是Jdbc类型
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://127.0.0.1:3306/user_mybatis?serverTimezone=UTC"/>
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </dataSource>
    </environment>
</environments>

<environment节点定义了连接某个数据库的信息,其子节点<transactionManager 的type 会决定具体使用什么类型的事务管理机制。

配置完后,在解析XML的时候,也就是在XMLConfigBuilder类的parseConfiguration方法里面调用了environmentsElement方法解析environment标签内配置的信息,包括事务管理器和数据源的解析:

代码语言:javascript
复制
//解析environments
private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    if (environment == null) {
      //development  
      environment = context.getStringAttribute("default");
    }
    //循环遍历XML各个节点
    for (XNode child : context.getChildren()) {
       //获取ID属性,对应<environment id="development">的ID属性, 即id=development
      String id = child.getStringAttribute("id");
      if (isSpecifiedEnvironment(id)) {
        //获取transactionManager事务管理器相关配置
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        //解析数据源配置,具体见下面分析
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        //将解析出来的数据源配置等信息赋值给configuration对象    
        configuration.setEnvironment(environmentBuilder.build());
      }
    }
  }
}

到这里,事务相关的配置已经解析完成,下一步就要看一下事务是在哪里创建的。

如果看了前面总结的sqlSession获取流程的小伙伴一下就能想到,事务其实是在获取sqlSession的会话中创建的,我们来看看:

代码语言:javascript
复制
// DefaultSqlSessionFactory
  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);
      // 创建SqlSession对象
      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();
    }
  }

如上我们可以看到,在配置文件中我们将<transactionManager的type配置为"JDBC",所以在MyBatis初始化解析<environment节点时,会根据type="JDBC"创建一个JdbcTransactionFactory工厂,然后根据事务工厂创建一个Transaction事务对象,接着又根据这个事务对象,创建具体的Executor,最终创建出创建SqlSession对象。

下面我们看一下TransactionFactory的源码:

代码语言:javascript
复制
public interface TransactionFactory {
 
  /**
   * 设置事务工厂相关属性
   */
  void setProperties(Properties props);
 
  /**
   * 从已有的连接中创建Transaction对象
   */
  Transaction newTransaction(Connection conn);
 
  /**
   * 根据数据源,数据库隔离级别,自动提交创建Transaction对象
   * @param dataSource 数据源信息
   * @param 数据库隔离级别
   * @param 是否自动提交
   * @return Transaction
   * @since 3.1.0
   */
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
 
}

TransactionFactory默认有两个实现子类:

  • JdbcTransactionFactory:创建JdbcTransaction事务对象;
  • ManagedTransactionFactory:创建ManagedTransaction事务对象;

通过事务工厂TransactionFactory很容易获取到Transaction对象实例,我们以JdbcTransaction为例,看一下JdbcTransactionFactory是怎样生成JdbcTransaction的,代码如下:

代码语言:javascript
复制
public class JdbcTransactionFactory implements TransactionFactory {
 
  @Override
  public void setProperties(Properties props) {
  }
 
  @Override
  public Transaction newTransaction(Connection conn) {
     //创建JdbcTransaction事务
    return new JdbcTransaction(conn);
  }
 
  @Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
     //创建JdbcTransaction事务,传入数据源、数据库隔离级别、是否自动提交属性
    return new JdbcTransaction(ds, level, autoCommit);
  }
}

简单理解,JdbcTransaction只是相当于对java.sql.Connection事务处理进行了一次包装,利用connection的commit()、rollback()、close()来实现对事务的管理。

代码语言:javascript
复制
//直接使用JDBC提交和回滚功能。它依赖于从数据源获取到的连接来管理事务的范围
public class JdbcTransaction implements Transaction {
 
  private static final Log log = LogFactory.getLog(JdbcTransaction.class);
 
  //数据库连接
  protected Connection connection;
  //数据源
  protected DataSource dataSource;
 //隔离级别
 protected TransactionIsolationLevel level;
 //是否为自动提交
 protected boolean autoCommmit;
  
  public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
    dataSource = ds;
    level = desiredLevel;
    autoCommit = desiredAutoCommit;
  }
 
  public JdbcTransaction(Connection connection) {
    this.connection = connection;
  }
 
  @Override
  public Connection getConnection() throws SQLException {
    if (connection == null) {
      openConnection();
    }
    return connection;
  }
 
  @Override
  public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
      //使用connection的commit()
      connection.commit();
    }
  }
 
  @Override
  public void rollback() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Rolling back JDBC Connection [" + connection + "]");
      }
      //使用connection的rollback()
      connection.rollback();
    }
  }
 
  @Override
  public void close() throws SQLException {
    if (connection != null) {
      resetAutoCommit();
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + connection + "]");
      }
      //使用connection的close()
      connection.close();
    }
  }
 
  protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
    try {
      if (connection.getAutoCommit() != desiredAutoCommit) {
        if (log.isDebugEnabled()) {
          log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
        }
        connection.setAutoCommit(desiredAutoCommit);
      }
    } catch (SQLException e) {
      // Only a very poorly implemented driver would fail here,
      // and there's not much we can do about that.
      throw new TransactionException("Error configuring AutoCommit.  "
          + "Your driver may not support getAutoCommit() or setAutoCommit(). "
          + "Requested setting: " + desiredAutoCommit + ".  Cause: " + e, e);
    }
  }
 
  protected void resetAutoCommit() {
    try {
      if (!connection.getAutoCommit()) {
        // MyBatis does not call commit/rollback on a connection if just selects were performed.
        // Some databases start transactions with select statements
        // and they mandate a commit/rollback before closing the connection.
        // A workaround is setting the autocommit to true before closing the connection.
        // Sybase throws an exception here.
        if (log.isDebugEnabled()) {
          log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
        }
        connection.setAutoCommit(true);
      }
    } catch (SQLException e) {
      if (log.isDebugEnabled()) {
        log.debug("Error resetting autocommit to true "
          + "before closing the connection.  Cause: " + e);
      }
    }
  }
 
  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommit);
  }
 
  @Override
  public Integer getTimeout() throws SQLException {
    return null;
  }
 
}
三、总结

MyBatis提供的两种管理机制来管理事务:

  • JdbcTransaction:直接使用JDBC提交和回滚功能,依赖于从数据源获取到的连接来管理事务的范围。设置为自动提交时会忽略提交或回滚请求。
  • ManagedTransaction:MyBatis不会管理事务,这使得程序的运行容器能够管理事务的整个生命周期。这种情况下它会忽略所有提交或回滚请求。

Mybatis的事务管理这一块相对来说没有其他复杂,基本上都是封装了原生JDBC的connection连接来实现事务的管理。当然,如果我们集成到Spring以后,基本上也是使用Spring提供的事务,更加强大更加好用。

鉴于笔者水平有限,如果文章有什么错误或者需要补充的,希望小伙伴们指出来,希望这篇文章对大家有帮助。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-01-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、概述
  • 二、Mybatis事务管理机制
  • 三、总结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档