The introduction

The @Transactional annotation is a common annotation used in development to ensure that multiple database operations within a method will either succeed or fail at the same time. There are a lot of details to pay attention to when using the @Transactional annotation, or you’ll find that @Transactional always fails out of nowhere.

Let’s figure out how to answer the interviewer’s questions from four aspects: what, where and when.

WHAT is business?

A Transaction, “Transaction,” usually means something to be done or done. In computer terms, a program execution unit that accesses and possibly updates various data items in a database.

Here we take the money withdrawal example to explain: for example, if you go to the ATM to withdraw 1000 yuan, there are generally two steps: the first step is to input the password amount, the bank card will deduct 1000 yuan; Step two: Withdraw 1000 yuan from the ATM. These two steps must be either performed or not performed. If 1,000 yuan is deducted from your bank card but the ATM payment fails, you will lose 1,000 yuan. If the bank card fails to withdraw the money but the ATM pays out 1000 yuan, the bank will lose 1000 yuan.

How do you guarantee that one of these two steps will not fail and the other will succeed? Transactions are designed to solve such problems. A transaction is a sequence of actions that, taken together, constitute a complete unit of work. These actions must all be completed, and if one fails, the transaction rolls back to its original state as if nothing had happened. Transaction management is an essential technology in enterprise application development to ensure data integrity and consistency.

In our daily development, transactions are divided into declarative transactions and programmatic transactions.

Programmatic transaction

Refers to the manual management of transactions in the code of submission, rollback and other operations, code is relatively invasive.

Programmatic transactions refer to transactions implemented in an encoded manner that allows users to define transaction boundaries precisely in code.

This is similar to JDBC programming to implement transaction management. Management using PlatformTransactionManager TransactionTemplate or directly use the bottom.

For programmatic transaction management, Spring recommends using TransactionTemplate.

try {
   //TODO something
    transactionManager.commit(status);
} catch (Exception e) {
   transactionManager.rollback(status);
   throw new InvoiceApplyException("Abnormal");
}Copy the code

Declarative transaction

Management is built on AOP. The essence is to intercept before and after the method, then create or join a transaction before the target method starts, and commit or roll back the transaction after the target method is executed.

The biggest advantage of declarative transactions is that they do not need to be managed programmatically. This means that you do not need to mix transaction management code with your business logic code. Instead, you can apply transaction rules to your business logic by declaring them in a configuration file (or by using the @Transactional annotation).

Simply put, programmatic transactions intrude into business code, but provide more detailed transaction management;

Based on AOP, declarative transactions can manage transactions without affecting the implementation of business code.

Declarative transactions can also be implemented using XML configuration files based on TX and AOP, or using the @Transactional annotation.

@GetMapping("/user")
@Transactional
public String user() {
       int insert = userMapper.insert(userInfo);
}
Copy the code

WHERE can @transactional be used?

Where does the @Transactional annotation work?

Transactional can work on interfaces, classes, and class methods.

  • On the classThe @Transactional annotation, when placed ona class, represents all of the Transactional annotations of that classpublicMethods are configured with the same transaction property information.
  • Applied to methods: When a class is configured with @Transactional and a method is configured with @Transactional, the method’s transaction overrides the Transactional configuration information of the class.
  • Use on interfaces: This is not recommended because the @Transactional annotation will fail once annotation is on an Interface and Spring AOP is configured to use CGLib dynamic proxies
@Transactional
@RestController
@RequestMapping
public class MybatisPlusController {
   @Autowired
   private UserMapper userMapper;
   
   @Transactional(rollbackFor = Exception.class)
   @GetMapping("/user")
   public String test() throws Exception {
       User user = new User();
       user.setName("javaHuang");
       user.setAge("2");
       user.setSex("2");
       int insert = userMapper.insert(cityInfoDict);
       return insert + ""; }} Copy the codeCopy the code

The Transactional @transactional attribute

The propagation properties

Propagation Indicates the propagation behavior of the transaction. The default value is Propagation.REQUIRED.

  • Propagation.REQUIRED: If a transaction currently exists, join the transaction. If no transaction currently exists, create a new transaction.(That is, if method A and method B are annotated, calling method B internally in the default propagation mode will merge the two methods’ transactions into one)
  • Propagation.SUPPORTS: If a transaction exists, join the transaction. If no transaction currently exists, it continues in a non-transactional manner.
  • Propagation.MANDATORY: If a transaction exists, join the transaction. If no transaction currently exists, an exception is thrown.
  • Propagation.REQUIRES_NEW: Creates a new transaction and suspends the current transaction if one exists.(When A method in class A uses the defaultPropagation.REQUIREDPattern, B method of class B plus adoptionPropagation.REQUIRES_NEWSchema, and then call method B in method A to operate on the database, but after method A throws an exception, method B does not roll back, becausePropagation.REQUIRES_NEWSuspends the transaction for method A)
  • Propagation.NOT_SUPPORTED: Runs in a non-transactional manner, suspending the current transaction if one exists.
  • Propagation.NEVER: Runs in a non-transactional manner, throwing an exception if a transaction currently exists.
  • Propagation.NESTEDPropagation.

The isolation properties

Isolation: Isolation level of a transaction. The DEFAULT is Isolation.default.

TransactionDefinition.ISOLATION_DEFAULT

This is the default value and indicates that the default isolation level of the underlying database is used. For most databases, this is usually the value

TransactionDefinition.ISOLATION_READ_UNCOMMITTED

This isolation level indicates that one transaction can read data modified by another transaction that has not yet been committed. This level does not protect against dirty reads, non-repeatable reads, and phantom reads, so it is rarely used. PostgreSQL, for example, doesn’t actually have this level.

TransactionDefinition.ISOLATION_READ_COMMITTED

This isolation level indicates that a transaction can only read data already committed by another transaction. This level protects against dirty reads and is recommended in most cases. TransactionDefinition.ISOLATION_REPEATABLE_READ

This isolation level means that a transaction can execute a query multiple times throughout the process and return the same records each time. This level prevents dirty and unrepeatable reads.

TransactionDefinition.ISOLATION_SERIALIZABLE

All transactions are executed one by one so that interference between transactions is completely impossible. That is, this level prevents dirty reads, unrepeatable reads, and phantom reads. But this severely affects the performance of the program. This level is also not typically used.

The timeout attribute

Timeout: indicates the timeout period of a transaction. The default value is -1. If the time limit is exceeded but the transaction has not completed, the transaction is automatically rolled back.

ReadOnly attribute

ReadOnly: specifies whether the transaction is read-only. The default value is false. To ignore methods that do not require transactions, such as reading data, you can set read-only to true.

RollbackFor properties

RollbackFor: Specifies the type of exception that can trigger a transaction rollback. Multiple exception types can be specified.

* * noRollbackFor attribute

NoRollbackFor: Throws the specified exception type, does not roll back the transaction, or can specify multiple exception types.

WHEN @transactional fails

The interviewer asked me directly if I’ve ever used @Transactional. I can’t say I’ve never used @Transactional.

The interviewer asked me if I have ever had @Transactional fail during actual development. I can’t say that I haven’t.

The interviewer has a question mark on his face. Can you tell me when @Transactional expires?

What follows is a compilation of the failure scenarios I mentioned in my interview.

@Transactional applies to methods that are not public decorates

If the Transactional annotation is applied to a method that is not public decorated, the Transactional annotation becomes invalid.

It fails because when proxying Spring AOP, the TransactionInterceptor intercepts the target method before and after execution, DynamicAdvisedInterceptor (CglibAopProxy inner classes) intercept method or JdkDynamicAopProxy invoke method indirect invocation The AbstractFallbackTransactionAttributeSource computeTransactionAttribute ` method, Transactional annotation transaction configuration information.

protected TransactionAttribute computeTransactionAttribute(Methodmethod, Class<? > targetClass) { // Don't allow no-public methods as required. if (allowPublicMethodsOnly() &&! Modifier.isPublic(method.getModifiers())) { return null; }Copy the code

Modifier. IsPublic checks whether the target method’s Modifier isPublic. If it is not public, the @transactional attribute configuration information is not obtained.

Note:protected,privateThe method of modification is used on@TransactionalNote that even though the transaction is invalid, there will be no errors, which is a big mistake for us.

The database engine does not support transactions

The database engine should support transactions. If MySQL is used, note that tables should use a transaction engine such as InnoDB. If MyISAM is used, transactions will not work.

3. Annotation invalid due to error in propagation setting of @

In reading the propagation properties above, we know that

TransactionDefinition.PROPAGATION_SUPPORTS

If a transaction exists, join the transaction. If there is no transaction currently, it continues in a non-transactional manner.

TransactionDefinition.PROPAGATION_NOT_SUPPORTED

Runs nontransactionally and suspends the current transaction if one exists.

TransactionDefinition.PROPAGATION_NEVER

Runs nontransactionally and throws an exception if a transaction currently exists.

The @Transactional annotation does not work when we set the Propagation attribute to the above three

Error rollbackFor setting @Transactional annotation invalid

We know that when we read the rollbackFor property

RollbackFor specifies the type of exception that can trigger a transaction rollback.

By default, Spring throws unchecked exceptions (exceptions inherited from RuntimeException) or errors to roll back a transaction;

Other exceptions do not trigger rollback transactions. If other types of exceptions are thrown in a transaction, but Spring is expected to rollback the transaction, you need to specify the rollbackFor property.

/ / wish to custom exception a rollback @ Transactional (propagation = propagation. The REQUIRED rollbackFor = MyException. ClassCopy the code

The transaction is also rolled back if the exception thrown in the target method is a subclass of the exception specified by rollbackFor. Spring source code is as follows:

private int getDepth(Class<? > exceptionClass, int depth) {if (exceptionClass.getName().contains(this.exceptionName)) {
       // Found it!
       return depth;
}
   // If we've gone as far as we can go and haven't found it...
   if (exceptionClass == Throwable.class) {
       return- 1; }return getDepth(exceptionClass.getSuperclass(), depth + 1);
}Copy the code

Methods that call each other also fail @Transactional

Let’s look at the following scenario:

For example, if you have A class User, one of its methods, A, calls method B of that class (whether method B is decorated with public or private), but method A does not declare an annotation transaction, whereas method B does. Method B’s transaction will not take effect after an external call to method A. This is also where mistakes are often made.

So why is this happening? Again, this is due to the use of Spring AOP proxies, because only when transactional methods are invoked by code other than the current class are they managed by spring-generated proxy objects.

   //@Transactional
   @GetMapping("/user")
   private Integer A() throws Exception {
       User user = new User();
       user.setName("javaHuang"); /** * B insert topJavaer data */ this.insertb (); Int insert = usermapper. insert(user);return insert;
  }

   @Transactional()
   public Integer insertB() throws Exception {
       User user = new User();
       user.setName("topJavaer");
       return userMapper.insert(user);
  }Copy the code

Exceptions “eat” by your catch cause @Transactional to fail

This is the most common @Transactional annotation failure scenario,

  @Transactional
  private Integer A() throws Exception {
      int insert = 0;
      try {
          User user = new User();
          user.setCityName("javaHuang"); user.setUserId(1); /** * A insert into javaHuang */ insert = usermapper. insert(user); /** * B Insert data with field topJavaer */ b.insertb (); } catch (Exception e) { e.printStackTrace(); }}Copy the code

If B throws an exception internally and A tries catch B’s exception, the transaction will not be rolled back normally and will raise an exception instead

org.springframework.transaction.UnexpectedRollbackException: Transactionrolled back because it has been marked as rollback-onlyCopy the code

Solutions:

First declare transaction add rollback=’exception’

Second cATH code block inside manual rollback

conclusion

The @Transactional annotation is often used, but often only as a Transactional annotation. Many times when a Transactional annotation fails, you don’t know why, and it takes a long time to resolve.

This article makes it easy to see the @Transactional annotation when it fails, and to feel your own smooth forehead when it does.

Mom no longer has to worry that I can’t find my own bug.