likes
comments
collection
share

水煮MyBatis(十一)- 事务

作者站长头像
站长
· 阅读数 57

前言

在springboot中,如果需要启用事务,有两个核心注解是不能缺失的:

  • @EnableTransactionManagement 在Application上添加;
  • @Transactional 在方法或者类上添加;

在这里以MySQL举例

事务语句

一般涉及到的语句主要有下面几个:

  • BEGIN :显式地开启一个事务,也可以使用START TRANSACTION;
  • COMMIT:提交事务,并使已对数据库进行的所有修改成为永久性的;
  • ROLLBACK: 结束用户的事务,并撤销正在进行的所有未提交的修改;
  • SET TRANSACTION :设置事务的隔离级别。InnoDB 存储引擎提供事务的隔离级别有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE。

其中还有一个保存点的概念【SAVEPOINT】,在业务代码中比较少见,这里就不介绍了。

Mybatis事务类图

水煮MyBatis(十一)- 事务

简要说明:

  • SpringManagedTransaction:看名称就知道,在Spring体系中一般使用此实现。代理 JDBC 连接的生命周期。 它从 Spring 的事务管理器中获取连接,结束后归还。如果 Spring 的事务处理处于活动状态,它将不操作所有提交/回滚/关闭调用,假设 Spring 事务管理器将完成这项工作。如果不是,则与JdbcTransaction的处理方式一致。
  • JdbcTransaction:直接使用 JDBC 的conn提交和回滚。它依赖从 dataSource 获取到的连接来管理事务的范围。延迟连接获取,直到调用 getConnection()。启用自动提交时忽略提交或回滚请求
  • ManagedTransaction:让容器管理事务的整个生命周期。延迟连接获取,直到调用 getConnection()。忽略所有提交或回滚请求。默认情况下,它会关闭连接,但可以配置为不这样做。

相关源码

本来想介绍JdbcTransaction的,一来ManagedTransaction很少用到,二来在Spring体系中,一般直接使用SpringManagedTransaction,所以这里就以SpringManagedTransaction为主来展开说明了。

开始和关闭

每开启一个事务,都会创建SpringManagedTransaction,对象中的其余几个属性,在获取链接时再进行初始化。

  public SpringManagedTransaction(DataSource dataSource) {
    this.dataSource = dataSource;
  }
  
  // 打开数据库链接
  private void openConnection() throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    // 是否以事务的方式打开链接
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
  }

ConnectionTransactional: Determine whether the given JDBC Connection is transactional, that is,bound to the current thread by Spring's transaction facilities. 判定给定的jdbc链接是否存在事务,如果是,则把Srping事务管理器绑定到当前线程。

事务初始化里,需要注意的是spring会将autoCommit属性自动设置为false,然后归还链接之前,恢复原始设置。autoCommit默认为true。 相关类:DataSourceTransactionManager

	@Override
	protected void doBegin(Object transaction, TransactionDefinition definition) {
		...
		// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
		// so we don't want to do it unnecessarily (for example if we've explicitly
		// configured the connection pool to set it already).
		if (con.getAutoCommit()) {
			// 设置标记,在归还conn时,将AutoCommit设置为true
			txObject.setMustRestoreAutoCommit(true);
			con.setAutoCommit(false);
		}
		...
	}

注意那行英文注释:

手动提交的成本是比较高的,如果没有什么必要,还是用自动提交。

	@Override
	protected void doCleanupAfterCompletion(Object transaction) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
		// Reset connection.
		Connection con = txObject.getConnectionHolder().getConnection();
		if (txObject.isMustRestoreAutoCommit()) {
			// 重置自动提交
			con.setAutoCommit(true);
		}
		// 重置隔离级别和只读标记
		DataSourceUtils.resetConnectionAfterTransaction(
				con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
		// 重置一些设置,比如保存点、事务、回滚等标记
		txObject.getConnectionHolder().clear();
	}

主要有三件事

  • 如果之前有修改过autoCommit设定,这里需要重置;
  • 重置隔离级别和只读标记
  • 重置一些常用设置,比如保存点、事务、回滚等标记

提交和回滚

从代码中可以看到,提交和回滚的前置条件都是一样,即:

  • 开启事务;
  • 关闭自动提交;
  • 链接非空;

  public void commit() throws SQLException {
    if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
      this.connection.commit();
    }
  }
  
  public void rollback() throws SQLException {
    if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
      this.connection.rollback();
    }
  }

抛开最后一个条件不说,如果一个链接没有开启事务,手动调用commit的时候,也会报错的。我们来看MySQL驱动中,ConnectionImpl类对commit的实现:

public void commit() throws SQLException {
    if (this.connectionLifecycleInterceptors != null) {
        // 执行链接生命周期拦截器,忽略不看
    }
    // 如果是自动提交,则报错
    if (this.autoCommit && !getRelaxAutoCommit()) {
        throw SQLError.createSQLException("Can't call commit when autocommit=true", getExceptionInterceptor());
    } else if (this.transactionsSupported) {
        // 执行提交语句
        execSQL(null, "commit", -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false);
    }

}

connection执行提交的时候,就做了这些事情

  • 执行链接生命周期拦截器;
  • 如果是自动提交,则抛出异常;
  • 执行提交语句;
转载自:https://juejin.cn/post/7244339465559425079
评论
请登录