05-5 transaction management

Posted by control on Tue, 14 Dec 2021 13:55:37 +0100

Transaction operation (transaction concept)

What is a transaction

  1. Transaction is the most basic unit of database operation. Logically, it is a group of operations that either succeed or fail
  2. Classic scenario: bank transfer
    1. Flower - > dance to 100 yuan
    2. The result is 100 less flowers and 100 more dances

Four characteristics of transactions (ACID)

  1. Atomicity
  2. uniformity
  3. Isolation
  4. persistence

Transaction operation (build transaction operation environment)

Create a table and add two pieces of data

CREATE TABLE `user` (
  `user_id` varchar(20) NOT NULL,
  `username` varchar(100) NOT NULL,
  `ustatus` varchar(50) NOT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Add data

INSERT INTO `user_db`.`account` (`id`, `username`, `meony`) VALUES ('1', 'flower', '1000');
INSERT INTO `user_db`.`account` (`id`, `username`, `meony`) VALUES ('2', 'dance', '1000');

Write UserDao

New interface

public interface UserDao {
    
}

Implementation interface

package com.dance.spring.learn.jdbc.dao.impl;

import com.dance.spring.learn.jdbc.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
@Repository
public class UserDaoImpl implements UserDao {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
}

Write UserService

package com.dance.spring.learn.jdbc.service;

import com.dance.spring.learn.jdbc.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

}

UserDao new interface

New interface

void add();

void reduce();

Implementation interface

@Override
public void add() {
    int dance = jdbcTemplate.update("update account set meony=meony+? where username = ?", 100, "dance");
    System.out.println("Transfer completed,dance Account arrival: 100 yuan");
}

@Override
public void reduce() {
    int dance = jdbcTemplate.update("update account set meony=meony-? where username = ?", 100, "flower");
    System.out.println("Transfer completed,flower Account expenditure 100 yuan");
}

UserService new transfer method

public void accountMoney(){
    userDao.reduce();
    userDao.add();
}

Writing test classes

@Test
public void testAccountMoney(){
    UserService userService = classPathXmlApplicationContext.getBean("userService", UserService.class);
    userService.accountMoney();
}

results of enforcement

Transfer completed,flower Account expenditure 100 yuan
 Transfer completed,dance Account arrival: 100 yuan

There is no problem with normal code flow

Modify UserService impersonation exception

public void accountMoney(){
    userDao.reduce();
    int i = 5/0;
    userDao.add();
}

Retest

results of enforcement

Transfer completed,flower Account expenditure 100 yuan

Only flower is 100 less, while dance is not much

How to solve this problem?

Using transaction resolution

Operation process of transaction

public void accountMoney(){
    try {
        // 1: Open transaction

        // 2: Business operation
        userDao.reduce();
        int i = 5/0;
        userDao.add();

        // 3: No exception committed transaction
    } catch (Exception e) {
        e.printStackTrace();
        // 4: Exception, transaction rollback
    }
}

Transaction operation (Introduction to Spring transaction management)

  1. Transactions are added to Java EE, the Service layer (business logic layer) in the three-tier structure
  2. Transaction management operations in Spring
    1. There are two ways
    2. Programming transaction management
    3. Declarative transaction management (use)
  3. Declarative transaction management
    1. Annotation based approach (use)
    2. XML based configuration file mode
  4. For declarative transaction management in Spring, the underlying layer uses AOP principle
  5. Spring Transaction Management API
    1. An interface is provided to represent the transaction manager class. This interface provides different implementation classes for different frameworks

Transaction operations (annotated declarative transaction management)

Configuring the transaction manager in Spring

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource" />
</bean>

Introduce TX namespace

xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd

Open transaction annotation

<tx:annotation-driven transaction-manager="transactionManager" />

Add transaction annotation

Modify the UserService method to remove useless comments

public void accountMoney() {
    userDao.reduce();
    int i = 5 / 0;
    userDao.add();
}

@Transactional

  1. This annotation can be added to classes or methods
  2. If it is added to a class, all the methods in this class will add transactions
  3. If it is added to a method, only this method can add transactions

UserService add annotation

@Service
@Transactional //

test

Test with testAccountMoney

results of enforcement

Transfer completed,flower Account expenditure 100 yuan

There are still 800 and 1100 in the database, which means that there are no fewer flower s and no more dance s. The problem has been solved successfully

Transaction operation (declarative transaction management parameter configuration)

@Transactional parameters

value: transaction management name; the default is transactionManager

transactionManager: transaction manager, which refers to the value of value by default

Propagation: propagation behavior of transactions

Timeout: timeout duration

readOnly: read only

rollbackFor: those exceptions are rolled back

rollbackForClassName: those exception class names are rolled back

noRollbackFor: those exceptions are not rolled back

noRollbackForClassName: those exception class names are not rolled back

Transaction propagation behavior and data isolation level replication come from: https://blog.csdn.net/edward0830ly/article/details/7569954

Speak very clearly, I like

Transaction propagation behavior

1) PROPAGATION_REQUIRED, the default spring transaction propagation level. The feature of using this level is that if a transaction already exists in the context, it will be added to the transaction for execution. If there is no transaction in the current context, a new transaction will be created for execution. Therefore, this level can usually meet most business scenarios. 2)PROPAGATION_SUPPORTS, literally, supports. The characteristic of this propagation level is that if there is a transaction in the context, it supports the transaction to join the transaction. If there is no transaction, it is executed in a non transaction manner. Therefore, not all packages are in transactiontemplate The code in execute will have transaction support. This is usually used to handle non core business logic operations that are not atomic. There are few application scenarios. 3)PROPAGATION_MANDATORY. Transactions at this level require that there must be transactions in the context, otherwise an exception will be thrown! Configuring the propagation level of this method is an effective means to control the omission of context calling code and add transaction control. For example, a piece of code cannot be called and executed alone, but once called, there must be transaction inclusion. This propagation level can be used. 4)PROPAGATION_REQUIRES_NEW, literally, a new transaction is required every time. The characteristic of this propagation level is that a new transaction will be created every time, and the transaction in the context will be suspended at the same time. After the current new transaction is completed, the context transaction will be resumed and then executed. This is a very useful propagation level. Take an application scenario: there is an operation to send 100 red packets. Before sending, do some system initialization, verification and data recording operations, then send 100 red packets, and then record the sending log. The sending log requires 100% accuracy. If the log is not accurate, the whole parent transaction logic needs to be rolled back. How to deal with the whole business needs? Through this promotion_ REQUIRES_ New level transaction propagation control can be completed. The child transaction sending the red packet will not directly affect the commit and rollback of the parent transaction. 5)PROPAGATION_NOT_SUPPORTED, which is not supported. The characteristic of the current level is that if there is a transaction in the context, the transaction will be suspended, the current logic will be executed, and the transaction in the context will be restored after the end. What are the benefits of this level? It can help you reduce your business as much as possible. We know that the larger a transaction is, the more risks it has. Therefore, in the process of dealing with transactions, we should ensure to narrow the scope as much as possible. For example, a piece of code must be called every logical operation, such as a non core business logical operation that circulates 1000 times. If such code is wrapped in a transaction, it is bound to cause the transaction to be too large, resulting in some exceptions that are difficult to consider. Therefore, the propagation level of this transaction level comes in handy. Just pick up the transaction template at the current level. 6)PROPAGATION_NEVER, this transaction is more strict. The above transaction propagation level is just not supported. If there is a transaction, it will be suspended, and the promotion_ The never propagation level requires that there can be no transaction in the context. Once there is a transaction, a runtime exception will be thrown to forcibly stop execution! This level has a lifelong feud with affairs. 7)PROPAGATION_NESTED, literally, nested, nested level transactions. The propagation level feature is that if there is a transaction in the context, the nested transaction is executed, and if there is no transaction, a new transaction is created. So what is nested transaction? Many people don't understand. I've read some blogs and there are some misunderstandings. Nesting means that the child transaction is nested in the parent transaction and the child transaction is a part of the parent transaction. Before entering the child transaction, the parent transaction establishes a rollback point called save point, and then executes the child transaction. The execution of the child transaction is also a part of the parent transaction. Then the execution of the child transaction ends and the parent transaction continues to execute. The point is the save point. Just look at a few questions: What happens if a sub transaction is rolled back? The parent transaction will roll back to the save point established before entering the child transaction, and then try other transactions or other business logic. The operations before the parent transaction will not be affected, nor will they be rolled back automatically. What happens if the parent transaction rolls back? If the parent transaction is rolled back, the child transaction will also be rolled back! Why? Because before the end of the parent transaction, the child transaction will not be committed. We say that the child transaction is a part of the parent transaction. Then: What is the status of transaction submission? Is the parent transaction committed first and then the child transaction committed, or is the child transaction committed first and then the parent transaction committed? The answer is the second case or that sentence. The child transaction is a part of the parent transaction and is uniformly committed by the parent transaction. Now, do you think this "nesting" is so interesting? The above seven propagation levels of transactions can usually meet various business needs in daily applications. However, in addition to the propagation level, if two transactions execute concurrently in the process of reading the database, how does the data between them affect each other? This requires understanding another feature of transactions: the level of data isolation

Data isolation level

There are four different levels of data isolation: 1. Serializable: the most stringent level. Transactions are executed serially, with the largest resource consumption; 2. REPEATABLE READ: it ensures that one transaction will not modify data that has been read by another transaction but has not been committed (rolled back). It avoids "dirty read" and "non REPEATABLE READ", but brings more performance losses. 3. READ COMMITTED: the default transaction level of most mainstream databases ensures that one transaction will not read the modified but uncommitted data of another parallel transaction, avoiding "dirty reading". This level applies to most systems. 4. Read Uncommitted: it ensures that illegal data will not be read during reading. In fact, the above explanation has some difficulties in each definition, which involves several terms: dirty reading, unrepeatable reading and illusory reading. Here is an explanation: Dirty reading: the so-called dirty reading is actually reading dirty data before rollback of other transactions. For example, transaction B modifies data X during execution. Before committing, transaction A reads x, but transaction B rolls back, so transaction A forms A dirty read. Non repeatable reading: the literal meaning of non repeatable reading is clear. For example, when transaction A first reads A piece of data, and then executes logic, transaction B changes the data, and then when transaction A reads it again, it finds that the data does not match, which is the so-called non repeatable reading. Unreal reading: when I was a child, I counted 10 fingers for the first time, and 11 for the second time. What's the matter? Hallucinating? The same is true for phantom reading. Transaction A first obtains 10 pieces of data according to the condition index, and then transaction B changes one piece of data in the database, which also meets the search conditions of transaction A at that time. In this way, transaction A searches again and finds 11 pieces of data, resulting in phantom reading. A cross reference table: Dirty reads non-repeatable reads phantom reads Serializable no REPEATABLE READ no READ COMMITTED will not Read Uncommitted Therefore, the most secure is Serializable, but it is accompanied by high performance overhead. In addition, there are two properties commonly used in transactions: readonly and timeout One is to set transactions as read-only to improve performance. The other is to set the transaction timeout, which is generally used to prevent large transactions. Still, the business should be as small as possible! Finally, a question is introduced: There are 20 conditions that need to be checked for a logical operation. Can you put the checking content outside the transaction in order to reduce the transaction? Many systems start transactions inside the DAO, then operate, and finally commit or rollback. This involves the problem of code design. Smaller systems can be done in this way, but in some larger systems, In systems with complex logic, too much business logic is bound to be embedded into DAO, resulting in the decline of DAO reusability. So this is not a good practice.

To answer this question: can you put some business logic checks outside the transaction in order to narrow the transaction? The answer is: the core business check logic cannot be put outside the transaction, and must be used as concurrency control under distributed environment! Once the check is performed outside the transaction, it is bound to cause the checked data of transaction A to be modified by transaction B, resulting in futility and concurrency problems of transaction A, which directly leads to business control failure. Therefore, in the distributed high concurrency environment, the lock mechanism should be used to check the core business logic. For example, to start a transaction, you need to read a piece of data for verification, then modify the data in the logical operation, and finally commit. In such a process, if the read and verified code is placed outside the transaction, the read data is likely to have been modified by other transactions. Once the current transaction is committed, it will overwrite the data of other transactions again, resulting in data exceptions. Therefore, this data must be locked when entering the current transaction. Using for update is a good control method in a distributed environment. A good practice is to use programmatic transactions rather than lifecycles, especially in larger projects. For transaction configuration, when the amount of code is very large, it will be a torture, and human flesh can never avoid this problem. Maintain the DAO as the most basic operation for a table, and then put the processing of business logic into the manager and service. At the same time, programmatic transactions are used to more accurately control the transaction scope. Special attention should be paid to some exceptions that may be thrown inside the transaction. Be careful not to catch exceptions casually, which will cause the exception of the transaction to be eaten and cannot be rolled back normally.

Transaction operations (full annotation Development)

Create a configuration class instead of XML

package com.dance.spring.learn.jdbc.config;

import com.alibaba.druid.pool.DruidDataSource;
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.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration
@ComponentScan(basePackages = {"com.dance.spring.learn.jdbc"})
@EnableTransactionManagement
public class SpringJdbcConfig {

    @Bean
    public DataSource dataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/user_db?useSSL=false&characterEncoding=utf-8&autoReconnect=true");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("123456");
        return druidDataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }

}

Just remember to change & in XML to&

Writing test classes

@Test
public void testAccountMoneyAnnotation(){
    AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(SpringJdbcConfig.class);
    UserService userService = annotationConfigApplicationContext.getBean("userService", UserService.class);
    userService.accountMoney();
}

results of enforcement

Transfer completed,flower Account expenditure 100 yuan

The value has not changed, and the transaction is OK