In-depth understanding of Spring transactions

Characteristics of transactions

  • atomic

A transaction is an atomic operation consisting of a series of actions. The atomicity of the transaction ensures that the action either completes completely or does not work at all.

  • consistency

Once the transaction is complete (whether it succeeds or fails), the system must ensure that the business it models is in a consistent state, not partially complete and partially failed. Data in the real world should not be corrupted.

  • Isolation,

There may be many transactions processing the same data at the same time, so each should be isolated from the others to prevent data corruption.

  • persistence

Once a transaction completes, its results should not be affected by whatever system error occurs, allowing it to recover from any system crash. Typically, the results of a transaction are written to persistent storage.

Propagation of transactions

Type of transaction propagation behavior instructions
PROPAGATION_REQUIRED If there is no transaction, create a new one. If there is already one, join it. This is the most common choice.
PROPAGATION_SUPPORTS Current transactions are supported, and non-transactionally executed if there are none.
PROPAGATION_MANDATORY Use the current transaction and throw an exception if there is no transaction currently.
PROPAGATION_REQUIRES_NEW Create a new transaction and suspend the current transaction if one exists.
PROPAGATION_NOT_SUPPORTED Performs the operation nontransactionally, suspending the current transaction if one exists.
PROPAGATION_NEVER Executes nontransactionally, throwing an exception if a transaction currently exists.
PROPAGATION_NESTED If a transaction currently exists, it is executed within a nested transaction. If there are no transactions currently, an operation similar to PROPAGATION_REQUIRED is performed.

Propagation rules answer the question of whether a new transaction should be started or suspended, or whether a method should run in a transactional context.

The isolation level of the transaction

Isolation level meaning
ISOLATION_DEFAULT Use the default isolation level for the back-end database
ISOLATION_READ_UNCOMMITTED Allows changes that have not yet been committed to be read. May cause dirty reads, phantom reads, or unrepeatable reads.
ISOLATION_READ_COMMITTED (Default Oracle level) allows reading from concurrent transactions that have already been committed. Dirty reads can be prevented, but phantom and non-repeatable reads may still occur.
ISOLATION_REPEATABLE_READ (Default MYSQL level) Multiple reads of the same field give consistent results, unless the data is changed by the current transaction itself. Dirty and unrepeatable reads can be prevented, but phantom reads can still occur.
ISOLATION_SERIALIZABLE Complete compliance with ACID isolation levels ensures that no dirty, non-repeatable, and phantom reads occur. This is also the slowest of all isolation levels, as it is typically done by fully locking the data tables involved in the current transaction.

The problem that transaction concurrency will cause

  1. Dirty read

Dirty reads occur when one transaction reads data that has been overwritten by another transaction but has not yet committed. If these changes are rolled back later, the data read by the first transaction will be invalid.

  1. Nonrepeatable read

Non-repeatable reads occur when a transaction executes the same query twice or more, but each time the query results are different. This is usually due to another concurrent transaction updating data between queries.

  1. Phantom reads

Phantom reading is similar to unrepeatable reading. Phantom reading occurs when a transaction (T1) reads a few rows of records and another concurrent transaction (T2) inserts some records. On subsequent queries, the first transaction (T1) finds some additional records that were not there before.

How Spring transactions are configured

Programmatic transaction management

Programmatic transaction management is invasive affairs management, using TransactionTemplate or directly using the PlatformTransactionManager, for programmatic transaction management, recommend using TransactionTemplate Spring.

  • Sample programmatic transaction

public class BussinessServiceImpl implements BussinessService { private BussinessDao bussinessDao; private TransactionTemplate transactionTemplate; . Public Boolean Transfer (final Long fromId, final Long toId, Final double amount) {/ / return calls to a callback function (Boolean) transactionTemplate. Execute (new TransactionCallback () {public Object  doInTransaction(TransactionStatus status) { Object result; Try {result = bussinessDao.transfer(fromId, toId, amount); } catch (Exception e) { status.setRollbackOnly(); result = false; System.out.println("Transfer Error!" ); } return result; }}); }}Copy the code

Declarative transaction management

Based on AOP, declarative transaction management essentially intercepts methods before and after, then creates or joins a transaction before the target method begins, and then commits or rolls back the target method as it executes.

Implemented programmatic transaction every time all want to separate, but business function is complex, use programmatic transaction is painful, and declarative transaction, declarative transaction belong to no invasive, will not affect the realization of the business logic, in the configuration file only need to do related business rule statement or through annotations, and business rules can be applied to the business logic. Declarative transaction management is clearly superior to programmatic transaction management, which is the non-invasive programming approach that Spring advocates. The only downside is that the granularity of declarative transaction management is at the method level, whereas programmatic transaction management is available to code blocks but can be configured by extracting methods.

  • Configure the transaction manager using XML

  1. Configure the transaction interceptor
<! <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="transactionManager"/> <! <property name="transactionAttributes"> <props> <! -- Key stands for regular matching of business methods, <prop Key =" INSERT *">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop> <prop key="save*">PROPAGATION_REQUIRED,ISOLATION_READ_ UNCOMMITTED</prop> <prop key="add*">PROPAGATION_REQUIRED,ISOLATION_READ_ UNCOMMITTED</prop> <prop key="select*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="del*">PROPAGATION_REQUIRED,ISOLATION_READ_ UNCOMMITTED</prop> <prop key="remove*">PROPAGATION_REQUIRED,ISOLATION_READ_ UNCOMMITTED</prop> <prop key="update*">PROPAGATION_REQUIRED,ISOLATION_READ_ UNCOMMITTED</prop> </props> </property> </bean>Copy the code
  1. Specifies which classes the transaction interceptor intercepts
<! Indicate the transaction interceptor interceptor which classes -- - > < bean class = "org. Springframework. Aop. Framework. Autoproxy. BeanNameAutoProxyCreator" > < property name="beanNames"> <list> <value>*ServiceImpl</value> </list> </property> <property name="interceptorNames"> <list> <value>transactionInterceptor</value> </list> </property> </bean>Copy the code

Use annotations for transaction configuration

@Transactional(propagation = Propagation.REQUIRED)
public boolean transfer(Long fromId, Long transactionId, double num) {
  return bussinessDao.transfer(fromId, transactionId, num);
}
Copy the code

Spring transaction management API

Transaction management is essentially the execution of commit or rollback operations according to a given transaction rule

PlatformTransactionManager interface

The Spring transaction strategy is through the PlatformTransactionManager interface, interface source code is as follows:

Public interface PlatformTransactionManager {/ / platform independent acquire the method of transaction TransactionStatus getTransaction (TransactionDefinition definition) throws TransactionException; // Platform-independent transaction commit method void commit(TransactionStatus status) throws TransactionException; // Platform independent transaction rollback method void rollback(TransactionStatus status) throws TransactionException; }Copy the code
  • Within the PlatformTransactionManager interface, contains a getTransaction (TransactionDefinition definition) method, this method is based on a TransactionDefinition parameters, Return a TransactionStatus object. The TransactionStatus object represents a transaction, which may be a new transaction or an existing transaction object, depending on the transaction rules defined by the TransactionDefinition.

TransactionDefinition interface

The TransactionDefinition interface is used to define rules for a transaction. It contains static properties of the transaction, such as transaction propagation behavior, timeout duration, and so on. At the same time, the Spring also provides us with a default implementation class: DefaultTransactionDefinition, this class is suitable for most of the time. If the class does not meet your requirements, you can implement your own TransactionDefinition by implementing the TransactionDefinition interface. The source code of the interface is as follows:

Public interface TransactionDefinition{// Transaction isolation level int getIsolationLevel(); // Transaction propagationBehavior int getationBehavior (); // transaction timeout int getTimeout(); Boolean isReadOnly(); }Copy the code
  • The TransactionDefinition interface defines transaction rules including: transaction isolation level, transaction propagation behavior, transaction timeout, transaction read-only attribute, and transaction rollback rule
  • Isolation of transactions, propagation of transactions, has been described previously and will not be described again
  • Transaction timeout: The maximum time that a transaction is allowed to execute, and if the transaction has not completed after this time, the transaction is automatically rolled back.
  • Read-only property of a transaction: Read-only or read-write operations are performed on transactional resources. Transactional resources are those managed by transactions, such as data sources, JMS resources, custom transactional resources, and so on.
  • Rollback rules for transactions: Normally, if an unchecked exception (inherited from RuntimeException) is thrown in a transaction, the transaction is rolled back by default. If no exception is thrown, or if a checked exception is thrown, the transaction is still committed.

TransactionStatus interface

The TransactionStatus interface provides a simple way to control transaction execution and query TransactionStatus. The source code for this interface is as follows:

public  interface TransactionStatus{
   boolean isNewTransaction();
   void setRollbackOnly();
   boolean isRollbackOnly();
}
Copy the code

Rollback of transactions

Programmatic transaction rollback

An exception is explicitly caught in the code logic or other judgment logic is rolled back so that no rollback occurs

Declarative transaction rollback

Spring rolls back transactions when a RuntimeException is thrown in a section or in an annotated method. The default is to catch a RuntimeException for a method, which means that any exception thrown that is run-time (that is, RuntimeException and its subclasses) can be rolled back; However, when a non-runtime exception is thrown, the transaction is not rolled back.

Common non-rollback situations in declarative transactions

  1. The declarative transaction configuration pointcut expression was written incorrectly, missing the method in the Service
  2. In the Service method, we pass an exception to a try catch, but the catch only prints the exception. No RuntimeException is manually thrown
  3. In the Service method, exceptions thrown are not run-time exceptions (such as IO exceptions) because Spring by default catches run-time exceptions and rolls them back

How can transaction rollback be guaranteed

  1. If the Service layer throws exceptions that are not run-time exceptions that need to be rolled back, you can change Spring’s default rollback Exception to Exception. This ensures that you can roll back any Exception encountered, as follows:
  • Declarative transactions, add a rollback-for to the configuration
 <tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception"/> 
Copy the code
  • Annotate the transaction by specifying the rollback-for parameter directly above the annotation
@Transactional(rollbackFor=Exception.class)
Copy the code
  1. Only non-read-only transactions can be rolled back. Read-only transactions are not rolled back
  2. If a try catch is used in the Service layer, throw a RuntimeException inside the catch so that the exception is rolled back
  3. If you don’t want to use the third solution, you can write a rollback code after the catch to implement the rollback. In this way, you can return the value even after throwing the exception. This is suitable for scenarios where you need to retrieve the return value from the Service layer, as shown below
@Transactional(rollbackFor = { Exception.class }) public boolean test() { try { doDbSomeThing(); } catch (Exception e) { e.printStackTrace(); / / and then throw the exception can be rolled back (with the code is no longer need to manually runtime exception is thrown) TransactionAspectSupport. CurrentTransactionStatus (). The setRollbackOnly (); return false; } return true; }Copy the code