The use of Spring transactions, isolation levels, and the use of @Transactional



        Spring transactions are a mechanism provided by the Spring framework for managing database transactions in applications.

        A transaction is the execution unit of a set of database operations. Either all are submitted successfully or all fail and are rolled back to ensure the consistency and integrity of the data.

Spring transactions provide two methods: declarative transactions and programmatic transactions:

  1. Programmatic transaction: Programmatic transaction is to manually manage the start, commit or rollback of a transaction by writing code. Developers need to explicitly call the transaction manager method to control the boundaries of the transaction.

  2. Declarative transactions: Declarative transactions are implemented by declaring transaction properties in configuration files or annotations. Developers only need to focus on business logic and do not need to care about transaction management. The Spring framework will automatically manage the start, commit or rollback of transactions based on the annotation configuration.


1. Programmatic transactions:

        In Spring programmatic transactions, DataSourceTransactionManager and TransactionDefinition are the two core classes:

  1. DataSourceTransactionManager:  It is a transaction manager based on the data source (DataSource), which manages transactions by obtaining a connection and controlling the submission or rollback of the connection. When using it, you need to configure a data source (DataSource) to obtain a database connection. At the beginning of a transaction, it obtains a database connection from the data source and binds the connection to the current thread context. At the end of the transaction, it will decide whether to commit or rollback the database connection based on the commit or rollback status of the transaction.

  2. TransactionDefinition: It is a transaction attribute interface defined by the Spring framework, which is used to define some attributes of the transaction, such as transaction isolation level, propagation behavior, timeout time, etc.

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    @Autowired
    private DataSourceTransactionManager transactionManager;
    @Autowired
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/del")
    public int del(Integer id) {
        if (id != null && id > 0) {
            // 开启事务
            TransactionStatus transactionStatus = null;
            // 业务操作,进行删除
            int result = 0;
            try {
                transactionStatus = transactionManager.getTransaction(transactionDefinition);
                result = userService.del(id);
                System.out.println("删除成功:" + result);
                transactionManager.commit(transactionStatus); // 提交
            } catch (Exception e) {
                if (transactionStatus != null) {
                    transactionManager.rollback(transactionStatus); // 回滚
                }
            }
            return result;
        }
        return 0;
    }
}

2. Declarative transactions:

        Programmatic transactions are more troublesome to write, while declarative transactions are more convenient to use.

        Declarative transactions can be implemented using the @Transactional annotation:

        When the method execution is completed, if no exception occurs, the transaction manager will automatically commit the transaction; if an exception occurs, the transaction manager will automatically roll back the transaction. You can also manually trigger a transaction rollback by throwing an exception on the method.

@RestController
@RequestMapping("/user2")
public class UserController2 {
    @Autowired
    private UserService userService;

    @Transactional
    @RequestMapping("del")
    public int del(Integer id) {
        if (id == null || id < 0) return 0;
        int ret = userService.del(id);
        System.out.println("删除:" + id);
        return ret;
    }
}

3. Specific use of @Transactional annotation:

3.1 Scope of action: 

The @Transactional annotation supports modified methods and can also modify classes:

  • When modifying methods: Please note that it can only be applied to public methods, otherwise it will not take effect. This usage is recommended.

  •  When modifying a class: Indicates that the annotation is effective for all public methods in the class.

3.2 Support parameter settings:

These parameters can be set according to actual needs. If no parameters are specified, the default values ​​will be used.

  1. value: Specifies the name of the transaction manager to use. Already configured transaction managers can be referenced by name. The default value is empty, which means the default transaction manager is used.

  2. propagation: Specify the propagation behavior of the transaction. Can be set to one of the following values:

    • REQUIRED: If there is no current transaction, create a new transaction; if there is currently a transaction, add it to the current transaction.
    • REQUIRES_NEW: Create a new transaction. If a transaction currently exists, suspend the current transaction.
    • NESTED: Executed in a nested transaction of the current transaction. If there is no current transaction, create a new transaction.
    • SUPPORTS: If there is currently a transaction, it will be added to the current transaction; if there is no current transaction, it will be executed in a non-transactional manner.
    • MANDATORY: If there is currently a transaction, add it to the current transaction; if there is no current transaction, throw an exception.
    • NEVER: Execute in a non-transactional manner and throw an exception if a transaction currently exists.
    • NOT_SUPPORTED: Execute in non-transactional mode. If a transaction currently exists, the current transaction will be suspended.
  3. isolation: Specifies the isolation level of the transaction. Can be set to one of the following values:

    • DEFAULT: Use the default isolation level (subject to the connected database isolation level).
    • READ_UNCOMMITTED: Allows reading uncommitted data.
    • READ_COMMITTED: Only submitted data can be read.
    • REPEATABLE_READ: Guarantees repeatable reading, prohibits dirty reading and non-repeatable reading.
    • SERIALIZABLE: Ensures serializable isolation level and prohibits dirty reads, non-repeatable reads and phantom reads.
  4. timeout: Specifies the transaction timeout, in seconds. If the transaction is not completed within the specified time, it will be automatically rolled back. The default value is -1, which means there is no timeout limit.

  5. readOnly: Specifies whether the transaction is a read-only transaction. If set to true, it indicates a read-only transaction and no modification operations are allowed. The default value is false.

  6. rollbackFor: Specify which exceptions trigger the rollback of the transaction. Can be set to an exception class or an array of exception classes. By default, only runtime exceptions and Errors will trigger transaction rollback.

  7. noRollbackFor: Specify which exceptions do not trigger the rollback of the transaction. Can be set to an exception class or an array of exception classes. By default, runtime exceptions and Errors trigger transaction rollback.

As shown in the picture: If you want to add multiple parameters, you can use commas to separate them.

 

Precautions for use: no rollback if no exception is detected

The @Transactional annotation is used to roll back when an exception is detected, but if an exception is detected using try-catch in the code and the program terminates at this time, the transaction will not be rolled back, which is very scary. matter.

For example:

 

Solution 1: Start with the cause

        Since @Transactional has not detected the exception, we can just let it be detected, so we can throw the exception in catch so that @Transactional can detect it.

 

Solution 2: Start with the results

        Use the relevant methods of TransactionAspectSupport in catch to manually roll back the transaction

3.3 Classic interview questions:

(1) How does @Transactional work?

@Transactional is an annotation used in the Spring framework to implement declarative transaction management. Its working principle is based on AOP (Aspect-Oriented Programming) technology.

  1. When the Spring container starts, it will scan all classes and methods annotated with @Transactional annotation.

  2. When calling a method marked with the @Transactional annotation, Spring will use AOP technology to weave transaction management logic before and after method execution.

  3. Before the method is executed, the transaction manager will open a new transaction.

  4. During method execution, if an exception occurs, the transaction manager will roll back the transaction, that is, undo the previous operation.

  5. If the method executes successfully, the transaction manager commits the transaction, which permanently saves the previous operation to the database.

  6. If other methods annotated with the @Transactional annotation are called during method execution, the transaction manager will decide whether to add these methods to the current transaction based on the propagation behavior of the transaction.

        It should be noted that declarative transactions only apply to public methods, that is, methods that are proxied in the Spring container. If a public method annotated with @Transactional is called from a non-public method in the same class, the transaction will not take effect. This is because Spring uses AOP technology to implement declarative transactions and can only intercept calls to public methods.

(2) What are the reasons for Spring transaction failure?

There are several possible reasons for Spring transaction failure:

1. The transaction manager is not configured correctly: In Spring, a transaction manager (such as DataSourceTransactionManager) needs to be configured to manage transactions. If the transaction manager is not configured correctly, or is configured incorrectly, transactions may become invalid.

2. The transaction annotation does not take effect: the @Transactional annotation is used to declare the transaction, but the annotation is not correctly recognized and parsed. It may be because the parsing of transaction annotations is not enabled in the configuration file, or the class where the annotations are located is not scanned by the Spring container.

3. Exceptions are not thrown correctly: transaction rollback is based on exceptions. By default, only runtime exceptions and Errors will trigger transaction rollback. If an exception is caught in a transaction method and is not rethrown, or the caught exception type does not meet the rollback conditions, the transaction will not be rolled back.

4. The transaction propagation behavior is incorrectly set: the transaction propagation behavior determines the relationship between transaction methods and other transaction methods. If the propagation behavior of a transaction method is set incorrectly, it may cause the transaction to be invalid or not as expected.

5. Methods are not proxied: Declarative transactions are implemented through AOP technology. Only methods in beans managed by the Spring container will be proxied, thereby achieving transaction management. If the method is not proxied, the transaction will not take effect.

6. The database does not support transactions: Some databases may not support transactions, or the isolation level of the transaction is not supported. In this case, transaction management is not possible.

7. Methods are overloaded: If there are multiple methods with the same name in a class, but the parameter lists are different, and only one of the methods is annotated with @Transactional annotation, then the transactions of other methods with the same name will not take effect.

Guess you like

Origin blog.csdn.net/m0_65190367/article/details/131954091