• notice
  • Congratulations on the launch of the Sought Tech site

Transactional Transactional in Spring, is this really correct?

Transaction management in Spring is very simple. Just add annotations to the method, @Transactionaland Spring can automatically help us open, commit, and roll back transactions. Many people even @Transactionalequate Spring transactions with Spring transactions, and @Transactionalannotate methods directly as long as there are database-related operations.

To tell you the truth, I have been like this before, until the use @Transactionalled to a production accident, and that production accident also caused my performance of the month to be hit D...

@Transactionalproduction accident

In 2019, I did an internal reimbursement project in the company. There is such a business logic:

1. Employees who work overtime can take taxis directly through Didi Chuxing Enterprise Edition, and the next day's taxi fees can be directly synchronized to our reimbursement platform

2. Employees can check their own taxi expenses on the reimbursement platform and create a reimbursement form for reimbursement. When creating a reimbursement form, an approval flow (unified process platform) will be created for the leader to approve

The code to create the reimbursement form at that time was written as follows:

/**
* Save claims and create workflows
*/
@Transactional(rollbackFor = Exception.class)
public void save(RequestBillDTO requestBillDTO){
//Call the process HTTP interface to create a workflow
WorkflowUtil.createFlow("BILL",requestBillDTO);

//Convert DTO object
RequestBill requestBill = JkMappingUtils.convert(requestBillDTO, RequestBill.class);
requestBillDao.save(requestBill);
//Save the list
requestDetailDao.save(requestBill.getDetail())
}

The code is very simple and "elegant". First, the workflow engine is called through the http interface to create the approval flow, and then the reimbursement form is saved. In order to ensure the transaction of the operation, the whole method is @Transactionalannotated (think carefully, this can really Guaranteed transaction?).

The reimbursement project belongs to the company's internal project, there is no high concurrency in itself, and the system has been running stably.

One afternoon at the end of the year (it happened to snow heavily a few days ago, and there were a lot of people taking taxis), the company sent a notification email saying that the annual reimbursement window was about to close, and the unreimbursed expenses needed to be reimbursed as soon as possible, and the workflow engine was running on that day. Security hardening.

After receiving the email, the number of reimbursement people began to increase gradually, and it reached its peak when it was close to the end of work. At this time, the reimbursement system began to malfunction: the database monitoring platform has been receiving alarm text messages, the database connection is insufficient, and a large number of deadlocks occur; the log shows the calling process. The engine interface has a large number of timeouts; at the same time, it keeps prompting CannotGetJdbcConnectionExceptionthat the database connection pool connection is full.

After the failure, we tried to kill the deadlock process and restarted violently, but the failure reappeared in less than 10 minutes and received a large number of phone complaints.
In the end, there was no other way but to send downtime maintenance emails and failure reports to all staff, and then, the performance was hit with a D, miserable... .

Accident Cause Analysis

By analyzing the log, we can easily locate the cause of the failure is the save() method that saves the reimbursement form, and the culprit is that @Transactionalannotation.

We know that @Transactional annotations are implemented using AOP, and the essence is to intercept before and after the target method is executed. Join or create a transaction before the target method is executed. After the execution method is executed, choose to commit or roll back the transaction according to the actual situation.

When Spring encounters this annotation, it will automatically obtain the connection from the database connection pool, start the transaction, and then bind it to ThreadLocal. For the entire method wrapped by the @Transactional annotation, the same connection is used. If we have time-consuming operations, such as third-party interface calls, complex business logic, and large-scale data processing, it will cause us to occupy this connection for a long time, and the database connection has been occupied and not released. Once there are too many similar operations, the database connection pool will be exhausted.

Performing RPC operations in a transaction causes the database connection pool to burst, which is a typical long-term transaction problem. Similar operations include a large number of data queries in a transaction, business rule processing, etc...

What is a long business?

As the name implies, it is a transaction that runs for a long time and has not been committed for a long time. It can also be called a large transaction.

What problems can long transactions cause?

Common hazards caused by long transactions are:

  1. The database connection pool is full, and the application cannot obtain connection resources;

  2. It is easy to cause database deadlock;

  3. Database rollback takes a long time;

  4. In the master-slave architecture, the master-slave delay will increase.

How to avoid long transactions?

Now that you know the dangers of long transactions, how to avoid long transaction problems in development?

Obviously, the purpose of solving a long transaction is to split the transaction method, try to make the transaction smaller and faster, and reduce the granularity of the transaction.

Now that the granularity of transactions is mentioned, let's review the way Spring manages transactions.

declarative transaction

First of all, we need to know that the operation of transaction management by using @Transactionalannotations on methods is called declarative transaction.

The advantage of using declarative transactions is obvious, that is, it is very simple to use, and it can automatically help us to open, commit, and roll back transactions. In this way, programmers only need to focus on business logic.

One of the biggest drawbacks of declarative transactions is that the granularity of transactions is the entire method and cannot be finely controlled.

The opposite of declarative transactions is programmatic transactions.

Based on the underlying API, developers manually manage transactions such as opening, committing, and rolling back operations in the code. Objects of classes can be used in spring projects to TransactionTemplatemanually control transactions.

@Autowired
private TransactionTemplate transactionTemplate;

...

public void save(RequestBill requestBill) {
transactionTemplate.execute(transactionStatus -> {
requestBillDao.save(requestBill);
//Save the schedule
requestDetailDao.save(requestBill.getDetail());
return Boolean.TRUE;
});
}

The biggest advantage of using programmatic transactions is that you can fine-tune the scope of the transaction.

So the easiest way to avoid long transactions is not to use declarative transactions @Transactional, but to use programmatic transactions to manually control the transaction scope.

Some students will say that @Transactionalit is so simple to use. Is there a way to use @Transactionalit and avoid long transactions?

Then you need to split the method, and separate the logic that does not require transaction management from the transaction operation:

@Service
public class OrderService{

public void createOrder(OrderCreateDTO createDTO){
query();
validate();
saveData(createDTO);
} }

// transaction operation
@Transactional(rollbackFor = Throwable.class)
public void saveData(OrderCreateDTO createDTO){
orderDao.insert(createDTO);
} }
}

query()Rather than validate()requiring transactions, we decouple it from the transactional approach saveData().

Of course, this split will hit @Transactionalthe classic scenario where the transaction does not take effect when using annotations, and many newbies are prone to make this mistake. @TransactionalThe declarative transaction of the annotation works through spring aop, and spring aop needs to generate a proxy object. The original object is still used for method calls directly in the same class, and the transaction does not take effect. Several other common scenarios where transactions do not take effect are:

"

  • @Transactional applies to non-publicly decorated methods

  • @Transactional annotation attribute propagation setting error

  • @Transactional annotation attribute rollbackFor setting error

  • Method calls in the same class cause @Transactional to fail

  • Exception caught by catch causes @Transactional to fail

"

The correct split method should use the following two:

  1. You can put the method into another class, such as adding  manager层, and inject it through spring, which meets the conditions for calling between objects.

@Service
public class OrderService{
  
    @Autowired
   private OrderManager orderManager;

    public void createOrder(OrderCreateDTO createDTO){
        query();
        validate();
        orderManager.saveData(createDTO);
    }
}

@Service
public class OrderManager{
  
    @Autowired
   private OrderDao orderDao;
  
  @Transactional(rollbackFor = Throwable.class)
    public void saveData(OrderCreateDTO createDTO){
        orderDao.saveData(createDTO);
    }
}
  1. The startup class is added @EnableAspectJAutoProxy(exposeProxy = true), the method is used AopContext.currentProxy()to obtain the proxy class, and the transaction is used.

SpringBootApplication.java

@EnableAspectJAutoProxy(exposeProxy = true)
@SpringBootApplication
public class SpringBootApplication {}
OrderService.java
  
public void createOrder(OrderCreateDTO createDTO){
    OrderService orderService = (OrderService)AopContext.currentProxy();
    orderService.saveData(createDTO);
}

summary

Using @Transactionalannotations is really convenient when developing, but a little carelessness can lead to long transaction problems. So for complex business logic, I recommend that you use programmatic transactions to manage transactions. Of course, if you have to use them @Transactional, you can split them according to the two schemes mentioned above.


Tags

Technical otaku

Sought technology together

Related Topic

1 Comments

author

order atorvastatin 40mg pills & lt;a href="https://lipiws.top/"& gt;lipitor 10mg without preion& lt;/a& gt; atorvastatin 10mg cost

Rwoqnv

2024-03-07

Leave a Reply

+