Declarative transactions

6.1 Use of transactions in Spring

When performing data operations, multiple SQL statements are usually operated as a whole. This one or more SQL statements are called database transactions. Database transactions can ensure that all operations within the scope of the transaction can all succeed or fail. If the transaction fails, the effect is the same as if the SQL had not been executed, with no changes to the database data.

Transactions are the basic unit of recovery and concurrency control.

Transactions should have four attributes: atomicity, consistency, isolation, and persistence. These four properties are commonly referred to as ACID properties.

  • Atomicity. A transaction is an indivisible unit of work in which all or none of the operations involved are performed.
  • Consistency. Transactions must change the database from one consistent state to another. Consistency is closely related to atomicity.
  • Isolation. The execution of a transaction cannot be interfered with by other transactions. That is, the operations and data used within a transaction are isolated from other concurrent transactions, and the concurrent transactions cannot interfere with each other.
  • They are persistent. Persistence, also known as permanence, means that once a transaction is committed, its changes to the data in the database should be permanent. Subsequent operations or failures should not affect it in any way.

Spring provides support for transactions through the @Transactional annotation.

You start by defining the configuration class, which creates the data source and encapsulates the jdbcTemplate and transaction manager.

@ Configuration @ ComponentScan (" com. Enjoy. Cap11 ") @ EnableTransactionManagement / / open transaction management function, Public class Cap11MainConfig {// create DataSource @bean public DataSource DataSource () throws Comopooleddatasource = new ComboPooledDataSource(); dataSource.setUser("root"); dataSource.setPassword("xxxxx"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/Spring? useSSL=false"); return dataSource; @bean public jdbcTemplate jdbcTemplate() throws PropertyVetoException{return new JdbcTemplate(dataSource()); } / / registered transaction manager @ Bean public PlatformTransactionManager PlatformTransactionManager () throws PropertyVetoException {return new DataSourceTransactionManager(dataSource()); }}Copy the code

Create new Order test table:

CREATE TABLE `order` ( `orderid` int(11) DEFAULT NULL, `ordertime` datetime DEFAULT NULL, 'ordermoney' decimal(20,0) DEFAULT NULL, 'orderstatus' char(1) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8Copy the code

Create a new OrderDao operation database,

@Repository public class OrderDao { @Autowired private JdbcTemplate jdbcTemplate; Public void insert(){String SQL = "insert into 'order' (orderTime, orderMoney, orderStatus) values(? ,? ,?) "; JdbcTemplate. Update (SQL, new Date (), 20, 0). }}Copy the code

Create a new OrderService class and inject the orderDao

@Service public class OrderService { @Autowired private OrderDao orderDao; @Transactional public void addOrder(){ orderDao.insert(); System.out.println(" operation complete........." ); //int a = 1/0; }}Copy the code

In the following test case, insert a data into the database normally. Query the database to find that the insert is normal.

public class Cap11Test { @Test public void test01(){ AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(Cap11MainConfig.class); OrderService bean = app.getBean(OrderService.class); bean.addOrder(); app.close(); }}Copy the code

But then test by manually setting an exception in the addOrder method. In the following code, an exception with a divisor of 0 is thrown at run time. As you can see from the run results, the database insert operation did not succeed in this case, indicating that Spring rolled back the INSERT operation to ensure transaction consistency.

@Service public class OrderService { @Autowired private OrderDao orderDao; @Transactional public void addOrder(){ orderDao.insert(); System.out.println(" operation complete........." ); int a = 1/0; }}Copy the code

6.2 Spring Transaction principle analysis

In the example above, in order to make the transaction can take effect, need to add @ EnableTransactionManagement annotations, the entire source code implementation and principle of AOP is consistent, when registered Bean to packaging of object, generate enhanced Bean, return a proxy object. During execution, the transaction interceptor is used to run transaction-annotated code and roll back when an exception occurs.

By introducing the class @ EnableTransactionManagement as you can see, the default PROXY mode, Will introduce AutoProxyRegistrar. Class and ProxyTransactionManagementConfiguration. Class, under the analysis of the function of these two components.

AutoProxyRegistrar.class

As you can see, from the following code and AOP, registered InfrastructureAdvisorAutoProxyCreator this component will be the water in the container, using the post processor mechanism after the object creation, packaging object, return a proxy object (enhancer), Proxy object execution methods are invoked using the chain of interceptors.

ProxyTransactionManagementConfiguration.class

Transaction enhancer with transaction annotation information, AnnotationTransactionAttributeSource analytical transaction annotations.

Intercept execution flow

Like AOP, when intercepting is executed, the chain of interceptors is first retrieved, and the interceptor’s PROCEED method is executed in turn.

The TransactionInterceptor is a TransactionInterceptor, which is a subclass of MethodInterceptor. The main logic for its execution is as follows:

  1. Start by getting the properties associated with the transaction
  2. Obtain PlatformTransactionManager again, if no specified transactionmanger has been added in advance

Ultimately from the container according to the type of access to a PlatformTransactionManager; 3. Implement the target approach

  • If abnormal, obtain transaction manager, use transaction management rollback operation;
  • If it works, use the transaction manager and commit the transaction.

6.3 Spring transaction isolation level and propagation

Isolation level

Isolation, as a key feature of transaction features, requires that each read-write transaction object can be separated from the operation object of other transactions, that is, the transaction is not visible to other transactions before submission, and is implemented by locking at the database level.

Before differentiating the different isolation levels, a few basic concepts are introduced:

1. Dirty read: A dirty read is when a transaction is accessing data and making changes to the data that have not been committed to the database, and another transaction also accesses the data and uses the data.

2. Non-repeatable read: The same data is read multiple times in a transaction. The same data is accessed by another transaction while the transaction is still active. Then, between the two reads in the first transaction, the data read in the first transaction may not be the same because of the modification in the second transaction. This happens when the data read twice in a transaction is not the same and is therefore called a non-repeatable read. In simple terms, transaction A reads the change data that transaction B has committed.

3. Phantom read: a phenomenon that occurs when a transaction is not executed independently, for example, the first transaction modifies the data in a table that involves all rows in the table. At the same time, the second transaction also modifies the data in the table by inserting a new row into the table. Then, as if in an illusion, the user operating on the first transaction will discover that there are unmodified rows in the table. In simple terms, transaction A reads the new data that transaction B has committed.

There are four levels of isolation for transactions, from low to high:

  • READ UNCOMMITTED: This is the lowest isolation level, meaning that one transaction is allowed to READ data that another transaction has not committed. READ UNCOMMITTED is a dangerous isolation level that is rarely used in real development, mainly because of the dirty READ problems it creates.

Dirty reads are fatal to applications that require data consistency. Currently, the isolation level of most databases is not set to READ UNCOMMITTED. However, while dirty reading may seem useless, its main advantage is its high concurrency capability, which is suitable for scenarios with no requirement for data consistency and high concurrency.

  • READ COMMITTED: A transaction can only READ COMMITTED data from another transaction, not READ uncommitted data. READ COMMITTED introduces the problem of unrepeatable reads.

In general, the problem of unrepeatable reads is acceptable because it reads data that has already been committed and is not a big problem in itself. Therefore, many databases, such as ORACLE and SQL SERVER, set their default isolation level to READ COMMITTED to allow unrepeatable reads.

  • REPEATABLE READ: Multiple reads of the same field give consistent results, unless the data is changed by the current transaction itself. Dirty and unrepeatable reads are prevented, but phantom reads may still occur.

  • SERIALIZABLE: The highest isolation level for a database that requires all SQL to be executed sequentially, which overcomes all of the above isolation problems and completely covers data consistency.

There are five isolation levels that can be configured in Spring:

DEFAULT(-1), ## Database DEFAULT level READ_UNCOMMITTED(1), READ_COMMITTED(2), REPEATABLE_READ(4), SERIALIZABLE(8);Copy the code

Isolation levels can be easily configured using annotations like the following:

@Transactional(isolation = Isolation.SERIALIZABLE)
public int insertUser(User user){
    return userDao.insertUser(user);
}
Copy the code

In the code above, we used a serialized isolation level to cover data consistency, which blocks other transactions from concurrency, so it can only be used in low concurrency scenarios where data consistency is needed.

Propagation behavior

In Spring, when one method calls another, transactions can work in different ways, such as creating a transaction or suspending the current transaction, which is the propagation behavior of a transaction.

In Spring transaction mechanism, there are seven kinds of Propagation behavior to the database, which is defined by enumeration class Propagation.

Public enum Propagation {/** * Requires transaction. */ REQUIRED(0), /** * support transaction, if transaction exists, use transaction, * if not, */ SUPPORTS(1), /** * transactions must be used, and an exception is thrown if no transaction is currently available. */ Transaction MANDATORY(2), /** * Regardless of whether the current transaction exists, REQUIRES_NEW(3), /** * Does not support transactions, suspends transactions if they currently exist, */ NOT_SUPPORTED(4), /** * does not support transactions, if the current method has a transaction, will throw an exception, otherwise continue to run with no transaction mechanism */ (5), /** * when the current method calls a child method, If an exception occurs in a submethod * only the SQL executed by the submethod is rolled back, but transactions of the current method are not rolled back */ NESTED(6); }Copy the code

In daily development, only REQUIRED(0),REQUIRES_NEW(3), and NESTED(6) are used.

There is a difference between REQUIRES_NEW and NESTED. REQUIRES_NEW can have its own separate isolation level and locks, while the NESTED propagation behavior inherits features such as the isolation level and locks of the current transaction.

The implementation of NESTED relies primarily on the database SAVEPOINT technology, which records a SAVEPOINT that can be rolled back and forth TO a SAVEPOINT by ROLLBACK TO SAVEPOINT. Enable savepoint technology if the database supports it; If not, a new transaction is created to execute the code, equivalent to REQUIRES_NEW.

Transactional self-invocation fails

If a class calls its own method, we call it self-invocation. MethodA (methodB); methodB (methodB); methodB (methodB);

@Transactional public void methodA(){ for (int i = 0; i < 10; i++) { methodB(); } } @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW) public int methodB(){ . }Copy the code

No matter how methodB sets the isolation level or propagation behavior in the above method, it does not take effect. That is, self-call invalidation.

This is due to the fact that the underlying implementation of @Transactional is based on AOP. AOP is based on dynamic proxy. When a Transactional class is called by itself, rather than by a proxy object, no AOP is created.

There are two ways to overcome this problem:

  • MethodA (methodB); methodA (methodB); methodA (methodB);
  • In the same Service, methodA does not call methodB directly, but retrieves the proxy object from the Spring IOC container firstOrderServiceImpl, and then call methodB. It’s a little confusing, but show you the code.
public class OrderServiceImpl implements OrderService,ApplicationContextAware { private ApplicationContext applicationContext = null; @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @Transactional public void methodA(){ OrderService orderService = applicationContext.getBean(OrderService.class); for (int i = 0; i < 10; i++) { orderService.methodB(); } } @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW) public int methodB(){ . }}Copy the code

In the above code, we realized the ApplicationContextAware interface first, and then through the applicationContext. GetBean () to obtain the OrderService interface object. At this point, you get a proxy object, and AOP’s dynamic proxy can be used normally.


Reference:

  • Juejin. Cn/post / 684490…

This article was originally published on teckee.github. IO/by Teckee.github

Search the public account “The Road to Back-end Improvement”, and immediately get the latest articles and BATJ high-quality interview courses worth 2,000 yuan.