The main research in this article is spring transaction Propagation and its implementation principles, which are introduced as follows.
Spring is currently a de facto standard for Java development, thanks to its convenience, complete functions, and easy to use. During the development process, operating DB is a very common operation, and when it comes to db, it involves transactions. During the normal development process of transactions, even if you do not notice it, there will be no side effects on the normal execution of the program. However, if an exception occurs and the transaction is not handled properly, unexpected results may occur. Spring has encapsulated various operations in terms of transactions, especially the emergence of declarative transactions, making development more comfortable. Spring expands transactions and supports the definition of multiple propagation attributes, which is also the focus of this article.
Not strictly speaking, a transaction is the abbreviation of multiple operations. These operations either take effect or none of them take effect (equivalent to not being executed). A general operation process is simplified as follows:
try{ Connection conn = getConnection(); // Perform some database operations}catch(Exception e){ conn.rollback();} finally{ conn.close();}Some problems can be seen from the above code:
Spring provides declarative transactions, so we don’t have to pay attention to the specific implementation of the underlying layer, and blocks many different implementation details of the underlying layer. In order to support the fine control of transactions by multiple complex businesses, spring provides the propagation attributes of transactions, and combined with declarative transactions, it has become a major transaction tool.
In the TransactionDefinition class, spring provides 6 propagation properties, which are explained with simple examples.
Warm reminder: The following mentioned joining the current transaction refers to the use of the same Connection at the bottom, but the transaction state object can be recreated and does not affect it. The article mentioned that there is only one transaction at present, which means that the underlying Connection is shared, and does not care about how many transaction state objects (TransactionStatus) are created.
Description: If a transaction already exists, then join the transaction. If there is no transaction, create a transaction. This is the default propagation attribute value.
Let’s see a small example, the code is as follows:
@Transactionalpublic void service(){ serviceA(); serviceB();}@TransactionalserviceA();@TransactionalserviceB();serviceA and serviceB both declare transactions. By default, propagation=PROPAGATION_REQUIRED. During the entire service call process, there is only one shared transaction. When any exception occurs, all operations will be rolled back.
Description: If a transaction already exists, join the transaction, otherwise a so-called empty transaction is created (it can be considered to be no transaction execution).
Let’s see a small example, the code is as follows:
public void service(){ serviceA(); throw new RunTimeException();}@Transactional(propagation=Propagation.SUPPORTS)serviceA();There is currently no transaction when serviceA is executing, so the exception thrown in the service will not cause serviceA to rollback.
Let’s take a look at another small example, the code is as follows:
public void service(){ serviceA();}@Transactional(propagation=Propagation.SUPPORTS)serviceA(){ do sql 1 1/0; do sql 2}Since serviceA has no transactions when running, at this time, if the underlying data source defaultAutoCommit=true, then sql1 is effective. If defaultAutoCommit=false, then sql1 is invalid. If the service has the @Transactional tag, serviceA shares the service transactions (no longer rely on defaultAutoCommit). At this time, serviceA is all rolled back.
Description: A transaction must currently exist, otherwise an exception will be thrown.
Let’s see a small example, the code is as follows:
public void service(){ serviceB(); serviceA();}serviceB(){ do sql}@Transactional(propagation=Propagation.MANDATORY)serviceA(){ do sql }In this case, executing service will throw an exception. If defaultAutoCommit=true, serviceB will not roll back. If defaultAutoCommit=false, serviceB execution will be invalid.
Note: If a transaction currently exists, first encapsulate the current transaction-related content into an entity, then recreate a new transaction, accept this entity as a parameter, and use it for transaction recovery. A more blunt statement is to suspend the current transaction (no transaction is needed) and create a new transaction. In this case, there is no dependency between the two transactions, and the new transaction can be rolled back, but the external transaction continues to execute.
Let’s see a small example, the code is as follows:
@Transactionalpublic void service(){ serviceB(); try{ serviceA(); }catch(Exception e){ }}serviceB(){ do sql}@Transactional(propagation=Propagation.REQUIRES_NEW)serviceA(){ do sql 1 1/0; do sql 2}When calling the service interface, since serviceA uses REQUIRES_NEW, it will create a new transaction. However, because serviceA throws a runtime exception, serviceA is rolled back. In the service method, exceptions are caught, so serviceB is submitted normally. Note that the try... catch code in the service is necessary, otherwise the service will throw an exception, causing the serviceB to be rolled back.
Note: If a transaction currently exists, suspend the current transaction, and then the new method is executed in an environment without transactions and in an environment without spring transactions, the commit of SQL completely depends on the defaultAutoCommit property value.
Let’s see a small example, the code is as follows:
@Transactionalpublic void service(){ serviceB(); serviceA();}serviceB(){ do sql}@Transactional(propagation=Propagation.NOT_SUPPORTED)serviceA(){ do sql 1 1/0; do sql 2}When the service method is called, an exception is thrown when the 1/0 code in the serviceA method is executed. Since serviceA is in a transaction-free environment, whether sql1 is effective depends on the value of defaultAutoCommit. When defaultAutoCommit=true, sql1 is effective, but serviceB will be rolled back because the service throws an exception.
Description: If a transaction is currently present, an exception is thrown, otherwise the code is executed on a transactionless environment.
Let’s see a small example, the code is as follows:
public void service(){ serviceB(); serviceA();}serviceB(){ do sql}@Transactional(propagation=Propagation.NEVER)serviceA(){ do sql 1 1/0; do sql 2}After the above example calls service, if defaultAutoCommit=true, the serviceB method and sql1 in serviceA will take effect.
Note: If a transaction currently exists, use SavePoint technology to save the current transaction state, and then the underlying layer shares a connection. When an error occurs within NESTED, it will roll back to the SavePoint state by itself. As long as an exception is caught by the outside, it can continue to commit external transactions without being disturbed by the embedded business. However, if an exception is thrown by the external transaction, the entire large transaction will be rolled back.
Note: The spring configuration transaction manager must actively specify nestedTransactionAllowed=true, as shown below:
<bean id="dataTransactionManager"> <property name="dataSource" ref="dataDataSource" /> <property name="nestedTransactionAllowed" value="true" /> </bean>
See a small example, the code is as follows:
@Transactionalpublic void service(){ serviceA(); try{ serviceB(); }catch(Exception e){ }}serviceA(){ do sql}@Transactional(propagation=Propagation.NESTED)serviceB(){ do sql1 1/0; do sql2}serviceB is an embedded service, and a runtime exception is thrown internally, so serviceB is rolled back. Since service caught the exception, serviceA can submit it normally.
Let’s take a look at another example, the code is as follows:
@Transactionalpublic void service(){ serviceA(); serviceB(); 1/0;}@Transactional(propagation=Propagation.NESTED)serviceA(){ do sql}serviceB(){ do sql}Since the service throws an exception, the entire service method will be rolled back. (This is different from PROPAGATION_REQUIRES_NEW. Embedded services under NESTED mode will be rolled back by exceptions from external transactions.)
The examples above illustrate several propagation attributes provided by spring transactions are used to meet a variety of different business needs, which can be determined by the business. Next, let’s take a look at what the most important technical dependencies of spring implements these propagation attributes. This section lists PROPAGATION_REQUIRES_NEW and Propagation.NESTED for brief descriptions respectively.
The following code call:
@Transactionalpublic void service(){ serviceB(); try{ serviceA(); }catch(Exception e){ }}@Transactional(propagation=Propagation.REQUIRES_NEW)serviceA(){ do sql 1 1/0; do sql 2}serviceB(){ do sql}The execution schematic is as follows:
a. Create a transaction state object, get a new connection, reset the autoCommit, fetchSize, timeout and other properties of the connection
b. Bind the connection to the ThreadLocal variable
c. Pending the current transaction, encapsulating the current transaction state object, connection and other information into a SuspendedResources object, which can be used for recovery
d. Create a new transaction state object, reacquire a new connection, reset the autoCommit, fetchSize, timeout and other properties of the new connection. At the same time, save the SuspendedResources object for transaction recovery, and bind the new connection to the ThreadLocal variable (overwrite operation)
e. Catch an exception, roll back the connection in ThreadLocal, restore the connection parameters, close the connection, and restore SuspendedResources
f. Submit the connection in the ThreadLocal variable (causing serviceB to be submitted), restore the connection parameters, close the connection, and return the connection to the data source
So the result of program execution is that serviceA is rolled back and serviceB is successfully submitted.
The following code call:
@Transactionalpublic void service(){ serviceA(); try{ serviceB(); }catch(Exception e){ }}serviceA(){ do sql}@Transactional(propagation=Propagation.NESTED)serviceB(){ do sql1 1/0; do sql2}The execution schematic is as follows:
a. Create a transaction state object, get a new connection, reset the autoCommit, fetchSize, timeout and other properties of the connection
b. Bind the connection to the ThreadLocal variable
c. Mark the use of the current transaction state object, obtain the ThreadLocal connection object, save the SavePoint of the current connection, and is used for exception recovery. At this time, the SavePoint is the status after the serviceA is executed
d. Catch an exception and use SavePoint in c for transaction rollback, that is, roll back the state to the state after executing serviceA. All executions of serviceB methods do not take effect
e. Get the connection object in ThreadLocal, submit transactions, restore connection properties, close connection
Based on the underlying data source, spring uses ThreadLocal, SavePoint and other technical points to realize a variety of transaction propagation attributes, which facilitates the implementation of various complex services. Only by understanding the principle of propagation attributes can we better control spring transactions. Spring rollback transactions rely on the capture of exceptions. By default, transactions will only be rolled back if the RuntimeException and Error are thrown. Of course, it can be configured. For more information, you can check the @Transactional annotation.
Spring's declarative transactions bring us great convenience. In order to make good use of this weapon, it is still necessary to understand the underlying principles. This article is just the tip of the iceberg of spring transactions. Readers can explore in depth on this basis.
The above is all the content of this article about spring transaction Propagation and its implementation principles. I hope it will be helpful to everyone. Interested friends can continue to refer to other related topics on this site. If there are any shortcomings, please leave a message to point it out. Thank you friends for your support for this site!