preface

Transactions are a very important topic, and previous articles have introduced the implementation process of SpringAOP proxies; Transaction management is also an important feature of AOP.

Basic introduction to transactions
Database transaction features:
  • atomic
  • consistency
  • Isolation,
  • persistence
The isolation level of the transaction

The SQL standard defines four isolation levels, all of which are supported by MySQL. The four isolation levels are:

  • READ UNCOMMITTED
  • READ COMMITTED
  • REPEATABLE READ
  • Serialization (SERIALIZABLE)

The reason for setting the isolation level of the database is that concurrent operations on the database may cause dirty reads, unrepeatable reads, phantom reads, type 1 missing updates, type 2 missing updates, etc.

Dirty read:

Transaction A reads the change data that transaction B has not yet committed and makes changes; If object B rolls back, then the data read by object A is invalid, and A dirty read occurs.

Unrepeatable read:

A transaction executes the same query twice or more, getting different data each time. For example, when A queries the account balance, it happens that B transfers 100 yuan to the account, and A queries the account balance again, so the two query results of A are inconsistent.

Phantom reads:

Object A reads the new data submitted by object B, at which point object A will appear illusory. Illusory and unrepeatable reading are easily confused. How do you distinguish them? A phantom read is a read of new data committed by another thing. A non-repeatable read is a read of changed data (modified or deleted) from an already committed thing.

The first type of missing update phenomenon:

Revoking a transaction overwrites updates committed by other transactions. This is due to the total absence of transaction isolation levels. If transaction 1 is committed and another transaction is cancelled, the updates made by transaction 1 are also cancelled.

The second kind of lost update phenomenon:

It is essentially the same kind of concurrent problem as unrepeatable read, and is usually regarded as a special case of unrepeatable read. The second type of lost update problem occurs when two or more transactions query the same record and then each update the record based on the results of the query. Each transaction is unaware of the existence of the other transactions, and changes made to the record by the last transaction override changes made to the record before the other transactions.

In fact, there are other solutions to the above problems. Setting the database isolation level is one of them. Briefly describe the functions of the four database isolation levels, as shown in the following table

Summary:

  • Read Uncommitted: Dirty reads, unrepeatable reads, type 2 lost updates, and magic Read problems exist.
  • Read COMMITTED: Unrepeatable reads, type 2 lost updates, and phantom Read problems.
  • Repeatable Read problem: Phantom Read problem.
  • Serializable is not a problem.
Let’s take a look at Spring’s core interface for supporting transactions:

Summary:

TransactionDefinition
  • See the source code (TransactionDefinition.java)
Public interface TransactionDefinition {** * if there is no current object, create a new object; If one already exists, add to it. */ int PROPAGATION_REQUIRED = 0; /** * supports the current thing, and if there is no current thing, executes in non-thing mode. */ int PROPAGATION_SUPPORTS = 1; /** * Use the current thing, and throw an exception if there is no current thing */ int PROPAGATION_MANDATORY = 2; /** * creates a new object, suspends the current object if it already exists. */ int PROPAGATION_REQUIRES_NEW = 3; /** * executes in non-transaction mode, suspends current transaction if current transaction exists. */ int PROPAGATION_NOT_SUPPORTED = 4; /** * executes in a non-transaction manner, throwing an exception if something currently exists. */ int PROPAGATION_NEVER = 5; /** * execute within a nested object if the object currently exists; */ int PROPAGATION_NESTED = 6 if there is no current thing, the same as PROPAGATION_REQUIRED */ int PROPAGATION_NESTED = 6; /** * Use the default isolation level for the back-end database. */ int ISOLATION_DEFAULT = -1; /** * READ_UNCOMMITTED isolation level */ int ISOLATION_READ_UNCOMMITTED = 1; // same as java.sql.Connection.TRANSACTION_READ_UNCOMMITTED; /** * READ_COMMITTED isolation level */ int ISOLATION_READ_COMMITTED = 2; // same as java.sql.Connection.TRANSACTION_READ_COMMITTED; /** * REPEATABLE_READ isolation level */ int ISOLATION_REPEATABLE_READ = 4; // same as java.sql.Connection.TRANSACTION_REPEATABLE_READ; /** * SERIALIZABLE isolation level */ int ISOLATION_SERIALIZABLE = 8; // same as java.sql.Connection.TRANSACTION_SERIALIZABLE; /** * Default timeout */ int TIMEOUT_DEFAULT = -1; * @return */ default int getPropagationBehavior() {return PROPAGATION_REQUIRED; } @return */ default int getIsolationLevel() {return ISOLATION_DEFAULT; } @return */ default int getTimeout() {return TIMEOUT_DEFAULT; } /** * default Boolean isReadOnly() {return false; } /** * get object name * @return */ @nullable default String getName() {return null; } static TransactionDefinition withDefaults() { return StaticTransactionDefinition.INSTANCE; }}Copy the code
Spring transaction propagation behavior
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.
Isolation levels supported by Spring
Isolation level describe
DEFAULT Use the isolation level used by the database itself ORACLE (read committed) MySQL (repeatable read)
READ_UNCOMMITTED Read uncommitted (dirty read) lowest isolation level, anything is possible.
READ_COMMITTEN Read committed, ORACLE default isolation level, phantom and unrepeatable read risks.
REPEATABLE_READ Repeatable reads, which addresses the isolation level of non-repeatable reads, but still has phantom read risk. MySQL default isolation level
SERLALIZABLE Serialization, the highest transaction isolation level, no matter how many transactions, all the children of one transaction can be executed after all the children of another transaction. This solves the problem of dirty reads, unrepeatable reads and phantom reads
The central interface in the Spring transaction infrastructure (PlatformTransactionManager.JAVA)
  • Look at the source code
/ * * * in the Spring transaction infrastructure center interface * / public interface PlatformTransactionManager extends TransactionManager {/ * * * according to the specified propagation behavior, Returns the currently active transaction or creates a new transaction. * @param definition * @return * @throws TransactionException */ TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; /** * Commit a given transaction on the status of a given transaction * @param status * @throws TransactionException */ void commit(TransactionStatus status) throws TransactionException; /** * Performs a rollback of the given transaction * @param status * @throws TransactionException */ void rollback(TransactionStatus status) throws TransactionException; }Copy the code
  • Source code analysis

Spring will be entrusted to the underlying persistence framework to complete the transaction management, therefore, Spring provides different persistence framework with different PlatformTransactionManager interface implementation class, let’s see what are the specific transaction manager:

A brief description of some of the transaction managers highlighted below:

Transaction manager describe
DataSourceTransactionManager Provides transaction management for a single Javax.sqL. DataSource for the Spring JDBC abstraction framework, iBATIS, or MyBatis framework
JpaTransactionManager Provide for a single javax.mail. Persistence. EntityManagerFactory transaction support, used to integrate JPA implementation framework of transaction management
JtaTransactionManager Provides support for distributed transaction management and delegates transaction management to the Java EE application server transaction manager

After looking at the transaction manager, let’s take a look at part 3 of the Overview diagram, the TransactionStatus Transaction State description interface class

Spring transaction state description
  • See the source code (TransactionStatus.java)
Public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {/** * returns whether the transaction carries savepoints internally, that is, has been created as a nested transaction based on savepoints. * @return */ boolean hasSavepoint(); */ @override void flush(); }Copy the code

Continue to have a look at its parent class TransactionExecution. Java and SavepointManager Java

Public interface TransactionExecution {/** * returns whether the current transaction is a new transaction (otherwise it will participate in an existing transaction, Or it may not be running in an actual transaction in the first place) * @return */ Boolean isNewTransaction(); /** * set transaction rollback only. */ void setRollbackOnly(); /** * returns whether the transaction has been marked for rollback only * @return */ Boolean isRollbackOnly(); /** * returns whether the thing has been completed, whether committed or rolled back. * @return */ boolean isCompleted(); }Copy the code
Public interface SavepointManager {/** * creates a new savepoint. * @return * @throws TransactionException */ Object createSavepoint() throws TransactionException; /** * rollback to the given savepoint. * Note: Calling this method to roll back to a given savepoint does not automatically release savepoints. * Release savepoints by calling the releaseSavepoint method. * @param savepoint * @throws TransactionException */ void rollbackToSavepoint(Object savepoint) throws TransactionException; /** * Explicitly releases the given savepoint. * @param SavePoint * @throws TransactionException */ void releaseSavepoint(Object SavePoint) throws TransactionException; }Copy the code

Now that the concepts related to Spring transactions have been roughly introduced, let’s get familiar with the application examples of Spring programmatic transactions:

Spring programmatic transactions
package com.vipbbo.spring.transaction; import org.apache.commons.dbcp2.BasicDataSource; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import javax.sql.DataSource; public class MyTransaction { private JdbcTemplate jdbcTemplate; private DataSourceTransactionManager transactionManager; private DefaultTransactionDefinition transactionDefinition; private String insertSql = "insert into account (balance) values ('100')"; Public void save(){// Initialize jdbcTemplate DataSource DataSource = getDataSource(); jdbcTemplate = new JdbcTemplate(dataSource); / / create a transaction manager transactionManager = new DataSourceTransactionManager (); transactionManager.setDataSource(dataSource); / / define the transaction attribute transactionDefinition = new DefaultTransactionDefinition (); transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); / / open transaction TransactionStatus TransactionStatus = transactionManager. GetTransaction (transactionDefinition); // Execute business logic try {jdbctemplate.execute (insertSql); //int i = 1/0; jdbcTemplate.execute(insertSql); transactionManager.commit(transactionStatus); } catch (DataAccessException e) { e.printStackTrace(); } catch (TransactionException e) { transactionManager.rollback(transactionStatus); e.printStackTrace(); } } public DataSource getDataSource(){ BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); The dataSource. SetUrl (" JDBC: mysql: / / 192.168.1.100:3306 / spring_aop?" + "useSSL=false&useUnicode=true&characterEncoding=UTF-8"); dataSource.setUsername("root"); dataSource.setPassword("root"); return dataSource; }}Copy the code

The test class:

package com.vipbbo.spring.transaction; import org.junit.jupiter.api.Test; public class MyTransactionTest { @Test public void test1() { MyTransaction myTransaction = new MyTransaction(); myTransaction.save(); }}Copy the code
Analysis of the

Run the test class, once released int I = 1/0; This code, after throwing an exception, manually rolls back the transaction, so no records are added to the database table.

Declarative transactions based on the @Transactional annotation
The bottom layer is based on 'AOP', intercepting methods before and after, then creating or joining a transaction before the target method starts, and then committing or rolling back the transaction according to the execution of the target method. With declarative transactions, transaction rules can be applied to business logic without adulterating the code for transaction management with the corresponding transaction rule declaration in the configuration file (or by equivalent annotation-based means).Copy the code

Configure declarative transactions in a non-XML manner

package com.vipbbo.spring.transaction;
​
​
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
​
@Transactional(propagation = Propagation.REQUIRED)
public interface AccountByAnnotationService {
  void save() throws RuntimeException;
}
​
Copy the code

Implementation class:

package com.vipbbo.spring.transaction; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; / * * @ * * * account interface implementation author paidaxing * / @ Service (" accountByAnnotationService ") public class AccountByAnnotationServiceImpl  implements AccountByAnnotationService { @Autowired private JdbcTemplate jdbcTemplate; private static String insertSql = "insert into account(balance) values (100)"; @override public void save() throws RuntimeException {system.out.println ("====== start executing SQL ======"); jdbcTemplate.execute(insertSql); System.out.println("====== SQL execution end ======"); System.out.println("====== ready to throw an exception ======"); Throw new RuntimeException(" manually throw exception "); }}Copy the code

Annotations start declarative transactions

The configuration class

package com.vipbbo.spring.config; import org.apache.commons.dbcp2.BasicDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; import java.net.ProtocolException; @ ComponentScan (basePackages = {} "com. Vipbbo") @ annotation-based Configuration / / open declarative transaction @ EnableTransactionManagement public class SpringConfig {/** * annotated DataSource * @return * @throws ProtocolException */ @bean public DataSource DataSource () throws ProtocolException { BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUsername("root"); dataSource.setPassword("root"); The dataSource. SetUrl (" JDBC: mysql: / / 192.168.1.100:3306 / spring_aop?" + "useSSL=false&useUnicode=true&characterEncoding=UTF-8"); return dataSource; } /** * Register JdbcTemplate * @return * @throws ProtocolException */ @bean public JdbcTemplate JdbcTemplate () throws ProtocolException{// Two methods of obtaining a DataSOurce //1. Public JdbcTemplate JdbcTemplate (DataSource DataSource) public JdbcTemplate (DataSource DataSource) Return new JdbcTemplate(dataSource()) return JdbcTemplate(dataSource()); } / registration transaction manager * * * * * @ @ return throws ProtocolException * / @ Bean public PlatformTransactionManager transactionManager () Throws ProtocolException {/ / need to dataSource return new DataSourceTransactionManager (dataSource ()); }}Copy the code

Test code class

package com.vipbbo.spring.transaction; import com.vipbbo.spring.config.SpringConfig; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class MyTransactionByAnnotationTest { @Test public void test(){ ApplicationContext tx = new AnnotationConfigApplicationContext(SpringConfig.class); AccountByAnnotationService annotationService = tx.getBean("accountByAnnotationService",AccountByAnnotationService.class); annotationService.save(); }}Copy the code

Test class running screenshot:

We manually throw an exception in the above implementation class, Spring automatically rolls back the transaction, and we can check the database to see that there is no new data.

Focus on priorities

By default, transactions in Spring only roll back the RuntimeException method, so replacing RuntimeException with a normal Exception here will not have a rollback effect

Refer to the article: cloud.tencent.com/developer/a…

Wechat search [code to meet you] for the first time to get more exciting content!