Understanding of Spring Transactions

1. The basic principle of business

The essence of Spring transaction is actually the support of the database for transactions. Without the transaction support of the database, spring cannot provide transaction functions. For pure JDBC operation database, if you want to use transaction, you can follow the following steps:

  1. Get the connection Connection con = DriverManager.getConnection()
  2. Open transaction con.setAutoCommit(true/false);
  3. Execute CRUD
  4. Commit transaction/rollback transaction con.commit() / con.rollback();
  5. Close the connection conn.close();

After using Spring's transaction management function, we can no longer write the code of steps 2 and 4, but Spirng will do it automatically. ?So how does Spring open and close transactions before and after we write CRUD? To solve this problem, we can understand Spring's transaction management implementation principle as a whole. The following is a brief introduction, the annotation method is an example

  1. The configuration file enables annotation-driven, and is identified by the annotation @Transactional on the relevant classes and methods.
  2. When spring starts, it will parse and generate related beans. At this time, it will check the classes and methods with related annotations, generate proxies for these classes and methods, and perform related configuration injection according to the relevant parameters of @Transaction, so that in the proxy In the middle, we have dealt with the related transactions (open the normal commit transaction, and abnormally roll back the transaction).
  3. The transaction commit and rollback of the real database layer is implemented through binlog or redo log.

Second, the propagation properties of Spring transactions

The so-called propagation property of spring transaction is to define how spring should handle the behavior of these transactions when multiple transactions exist at the same time. These properties are defined in TransactionDefinition, and the explanation of specific constants is shown in the following table:

constant name Constant explanation
PROPAGATION_REQUIRED Support the current transaction, if there is no current transaction, create a new transaction. This is the most common choice and is Spring's default transaction propagation.
PROPAGATION_REQUIRES_NEW Create a new transaction. If there is a current transaction, suspend the current transaction. The newly created transaction will have nothing to do with the suspended transaction. It is two independent transactions. After the outer transaction fails and rolls back, the execution result of the inner transaction cannot be rolled back. The inner transaction fails and an exception is thrown, and the outer transaction fails. Capture, or do not handle rollback operations
PROPAGATION_SUPPORTS Support the current transaction, if there is no current transaction, it will be executed in a non-transactional mode.
PROPAGATION_MANDATORY Supports the current transaction and throws an exception if there is no current transaction.
PROPAGATION_NOT_SUPPORTED Execute the operation in a non-transactional manner, suspending the current transaction if there is a current transaction.
PROPAGATION_NEVER Executes non-transactionally and throws an exception if a transaction currently exists.
PROPAGATION_NESTED

If an active transaction exists, run in a nested transaction. If there is no active transaction, it is executed according to the REQUIRED attribute. It uses a single transaction with multiple savepoints that can be rolled back. The rollback of the inner transaction does not affect the outer transaction. It only works on the DataSourceTransactionManager transaction manager.

3. Database isolation level

isolation level The value of the isolation level cause problems
Read-Uncommitted 0 cause dirty reads
Read-Committed 1 Avoid dirty reads, allow non-repeatable reads and phantom reads
Repeatable-Read 2 Avoid dirty reads, non-repeatable reads, allow phantom reads
Serializable 3 Serialized read, transactions can only be executed one by one, avoiding dirty reads, non-repeatable reads, and phantom reads. The execution efficiency is slow, use it with caution

Dirty read: A transaction adds, deletes, or modifies data, but it is not committed, and another transaction can read the uncommitted data. If the first transaction rolls back at this time, then the second transaction has read the dirty data.

Non-repeatable read: Two read operations occur in a transaction. Between the first read operation and the second operation, another transaction modifies the data. At this time, the two read data are inconsistent.

Phantom read: The first transaction modifies a certain range of data in batches, and the second transaction adds a piece of data to this range. At this time, the first transaction will lose the modification of the newly added data.

Summarize:

The higher the isolation level, the better the data integrity and consistency can be guaranteed, but the greater the impact on concurrent performance.

大多数的数据库默认隔离级别为 Read Commited,比如 SqlServer、Oracle

少数数据库默认隔离级别为:Repeatable Read 比如: MySQL InnoDB

四、Spring中的隔离级别

常量 解释
ISOLATION_DEFAULT 这是个?PlatfromTransactionManager?默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别相对应。
ISOLATION_READ_UNCOMMITTED 这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
ISOLATION_READ_COMMITTED 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
ISOLATION_REPEATABLE_READ 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。

五、事务的嵌套

通过上面的理论知识的铺垫,我们大致知道了数据库事务和spring事务的一些属性和特点,接下来我们通过分析一些嵌套事务的场景,来深入理解spring事务传播的机制。

假设外层事务 Service A 的 Method A() 调用 内层Service B 的 Method B()

PROPAGATION_REQUIRED(spring 默认)

如果ServiceB.methodB() 的事务级别定义为 PROPAGATION_REQUIRED,那么执行 ServiceA.methodA() 的时候spring已经起了事务,这时调用 ServiceB.methodB(),ServiceB.methodB() 看到自己已经运行在 ServiceA.methodA() 的事务内部,就不再起新的事务。

假如 ServiceB.methodB() 运行的时候发现自己没有在事务中,他就会为自己分配一个事务。

这样,在 ServiceA.methodA() 或者在 ServiceB.methodB() 内的任何地方出现异常,事务都会被回滚。

PROPAGATION_REQUIRES_NEW

比如我们设计 ServiceA.methodA() 的事务级别为 PROPAGATION_REQUIRED,ServiceB.methodB() 的事务级别为 PROPAGATION_REQUIRES_NEW。

那么当执行到 ServiceB.methodB() 的时候,ServiceA.methodA() 所在的事务就会挂起,ServiceB.methodB() 会起一个新的事务,等待 ServiceB.methodB() 的事务完成以后,它才继续执行。

他与 PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为 ServiceB.methodB() 是新起一个事务,那么就是存在两个不同的事务。如果 ServiceB.methodB() 已经提交,那么 ServiceA.methodA() 失败回滚,ServiceB.methodB() 是不会回滚的。如果 ServiceB.methodB() 失败回滚,如果他抛出的异常被 ServiceA.methodA() 捕获,ServiceA.methodA() 事务仍然可能提交(主要看B抛出的异常是不是A会回滚的异常)。

PROPAGATION_SUPPORTS

假设ServiceB.methodB() 的事务级别为 PROPAGATION_SUPPORTS,那么当执行到ServiceB.methodB()时,如果发现ServiceA.methodA()已经开启了一个事务,则加入当前的事务,如果发现ServiceA.methodA()没有开启事务,则自己也不开启事务。这种时候,内部方法的事务性完全依赖于最外层的事务。

PROPAGATION_NESTED

现在的情况就变得比较复杂了, ServiceB.methodB() 的事务属性被配置为 PROPAGATION_NESTED, 此时两者之间又将如何协作呢???ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:

a、捕获异常,执行异常分支逻辑

void methodA() {
 
         try {
 
             ServiceB.methodB();
 
         } catch (SomeException) {
 
             // 执行其他业务, 如 ServiceC.methodC();
 
         }
 
     }

这种方式也是潜套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点。

b、 外部事务回滚/提交 代码不做任何修改, 那么如果内部事务(ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback

另外三种事务传播属性基本用不到,在此不做分析。

六、总结

对于项目中需要使用到事务的地方,我建议开发者还是使用spring的TransactionCallback接口来实现事务,不要盲目使用spring事务注解,如果一定要使用注解,那么一定要对spring事务的传播机制和隔离级别有个详细的了解,否则很可能发生意想不到的效果。

这里再引用下这篇文章也挺好的spring事务嵌套

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325249500&siteId=291194637