This is the 13th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

Writing in the front

We all know that adding @Transactional annotations makes things happen (regardless of distributed transactions), but sometimes it doesn’t work. Annotations don’t work.

Today we are divided into two parts: learning about the past and learning about the new

So, recap those failure scenarios. By learning new, we can also find something. And then we look down

Let’s start by saying that there are roughly six reasons why Spring transactions fail.

  • The database engine does not support transactions (most of us use Mysql to support transactions naturally, so this is rare)

  • Not managed by Spring

  • Methods are not public

  • Self call problem (common)

  • Abnormality is eaten (common)

  • Exception type error

Here are some examples:

Must be managed by Spring

 // @Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public void updateOrder(Order order) {
        // update order}}Copy the code

If you comment out the @service annotation at this point, the class will not be loaded as a Bean, the class will not be managed by Spring, and the transaction will be invalidated.

Methods must be public

The following is from the Official Spring documentation:

When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

Transactional methods can only be used for public methods, otherwise transactions will not fail. To use a non-public method, turn on the AspectJ proxy mode.

I can’t tune myself anymore

@Service
public class OrderServiceImpl implements OrderService {

    public void update(Order order) {
        updateOrder(order);
    }

    @Transactional
    public void updateOrder(Order order) {
        // Update order logic}}Copy the code

Call the @Transactional updateOrder method without the Transactional annotation. Do transactions on the updateOrder method work?

The answer is that it doesn’t work, because they call their own methods when they do, whereas proxy classes that don’t go through Spring only work when transactions are called externally by default.

Solution:

@Resource
private UpdateOrder updateOrder;

public void update(Order order) {
      updateOrder.updateOrder(order);
}
Copy the code

This leaves it up to Spring to manage.

Don’t try catch

@Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public void updateOrder(Order order) {
        try {
            // update order
        } catch{}}}Copy the code

We write code like this all the time. But the transaction fails because the exception is eaten. Other people’s business is based on your anomaly to roll back the results of the food you eat.

But we can flip it. You can solve it by throwing it in a catch but not by throwing any exception. Next, look at exception type errors

Be careful with the exception type

@Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public void updateOrder(Order order) {
        try {
            // update order
        } catch {
            throw new Exception("Update error"); }}}Copy the code

This transaction also does not take effect, because the default rollback is RuntimeException. If you want to trigger the rollback of other exceptions, you need to configure this in the annotation, such as:

@Transactional(rollbackFor = Exception.class)
Copy the code

Okay, so that’s the end of the failure scenario, the next thing we’re going to think about is can we implement a transaction in a try catch case, and the answer is yes. Let’s move on to the new findings

New solutions

The transaction rollback above assumes that the @Transactional annotated method does not have a try{… }catch{… } catch exceptions, so that the process of running exceptions can be successfully thrown, thus triggering transaction rollback.

But in the actual development, we often need to capture the exception in the method, so as to judge the exception and return the prompt message for the client. However, because the exception is caught, the rollback of the transaction is not triggered, resulting in the failure of the transaction.

So how do you do that? There are three ways to do that

Use the @Transactional annotation to throw a RuntimeException that the @Transactional annotation recognizes by default

@Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public void updateOrder(Order order) {
        try {
            // update order
        } catch {
            logger.error("Failed to change order")
            throw new RuntimeException("RuntimeException"); }}}Copy the code

Transactional(rollbackFor = {exception.class}) also throws caught non-runtimeExceptions

Methods use the @transactional (rollbackFor = {exception.class}) annotation to declare the transaction rollback level. When an Exception is caught, it is thrown directly in a catch statement.

@Service
public class OrderServiceImpl implements OrderService {

    @Transactional(rollbackFor = { Exception.class })
    public void updateOrder(Order order) {
        try {
            // update order
        } catch {
            logger.error("Failed to change order")
            throwe; }}}Copy the code

Catch {… } methods that throw exceptions have the disadvantage of not being able to catch{… } there is a return clause, for example, we need some return information, then we must set up manual rollback, when caught an exception, manual rollback, and return the foreground message.

Manual rollback

@Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public int updateOrder(Order order) {
        try {
            // update order
        } catch {
            logger.error("Failed to change order")
            // Manually roll back
            TransactionAspectSupport.currentTransactionStatus()
            .setRollbackOnly();
            return 0;
        }
        return 1; }}Copy the code

OK. That’s all for today, and I’ll see you next time

overtones

Thank you for reading, and if you feel like you’ve learned something, you can like it and follow it. Also welcome to have a question we comment below exchange

Come on! See you next time!

To share with you a few I wrote in front of a few SAO operation

Copy object, this operation is a little SAO!

Dry! SpringBoot uses listening events to implement asynchronous operations