How to solve big business problems

Big business problems

insert image description here

solution

Use less @Transactional annotations and use programmatic transaction TransactionTemplate

In the actual project development, we add the @Transactional annotation to the business method to enable the transaction function. This is a very common practice, and it is called a declarative transaction.

Part of the code is as follows:

@Transactional(rollbackFor=Exception.class)
   public void save(User user) {
    
    
         doSameThing...
   }

Why use less @Transactional annotations.

We know that the @Transactional annotation works through spring's aop, but if used improperly, the transaction function may fail. If you happen to be inexperienced, this kind of problem is not easy to troubleshoot. As for the circumstances under which transactions will fail, you can refer to
various scenarios where spring transactions do not take effect.
@Transactional annotations are generally added to a certain business method, which will cause the entire business method to be in the same transaction. The granularity is too coarse to control the transaction. scope, is the most common cause of large transaction problems.
So what should we do?

Programmatic transactions can be used to manually execute transactions using the object of the TransactionTemplate class in the spring project.

Part of the code is as follows:

   @Autowired
   private TransactionTemplate transactionTemplate;
   
   ...
   
   public void save(final User user) {
    
    
         transactionTemplate.execute((status) => {
    
    
            doSameThing...
            return Boolean.TRUE;
         })
   }

As can be seen from the above code, using the programmatic transaction function of TransactionTemplate to flexibly control the scope of transactions is the first choice to avoid large transaction problems.

Of course, if some business logic in the project is relatively simple and does not change frequently, it is okay to use the @Transactional annotation to open the transaction to open the transaction, because it is simpler and the development efficiency is higher, but you must be careful about the problem of transaction failure.

Sort out methods that don’t require transactional execution

Put the query (select) method outside the transaction

For example, the following code appears:

@Transactional(rollbackFor=Exception.class)
   public void save(User user) {
    
    
         queryData1();
         queryData2();
         addData1();
         updateData2();
   }

The two query methods queryData1 and queryData2 can be executed outside the transaction, and the code that really needs to be executed in the transaction can be put in the transaction, such as: addData1 and updateData2 methods, so that the granularity of the transaction can be effectively reduced.

If you use the programmatic transaction of TransactionTemplate, it is very easy to modify here.

   @Autowired
   private TransactionTemplate transactionTemplate;
   
   ...
   
   public void save(final User user) {
    
    
         queryData1();
         queryData2();
         transactionTemplate.execute((status) => {
    
    
            addData1();
            updateData2();
            return Boolean.TRUE;
         })
   }

But if you really still want to use the @Transactional annotation, how should you split it?

public void save(User user) {
    
    
         queryData1();
         queryData2();
         doSave();
    }
   
    @Transactional(rollbackFor=Exception.class)
    public void doSave(User user) {
    
    
       addData1();
       updateData2();
    }

This example is a very classic error. This direct method call transaction will not take effect, because the declarative transaction annotated by @Transactional works through spring aop, and spring aop needs to generate a proxy object, and the direct method call is still used The original object, so the transaction will not take effect.

Is there a way around this?

1. Add a new Service method

This method is very simple. You only need to add a new Service method, add the @Transactional annotation to the new Service method, and move the code that requires transaction execution to the new method. The specific code is as follows:

@Servcie
  publicclass ServiceA {
    
    
     @Autowired
     prvate ServiceB serviceB;
  
     public void save(User user) {
    
    
           queryData1();
           queryData2();
           serviceB.doSave(user);
     }
   }
   
   @Servcie
   publicclass ServiceB {
    
    
   
      @Transactional(rollbackFor=Exception.class)
      public void doSave(User user) {
    
    
         addData1();
         updateData2();
      }
   
   }

2. Inject yourself in the Service class

If you don't want to add a new Service class, injecting yourself into the Service class is also an option. The specific code is as follows:

@Servcie
  publicclass ServiceA {
    
    
     @Autowired
     prvate ServiceA serviceA;
  
     public void save(User user) {
    
    
           queryData1();
           queryData2();
           serviceA.doSave(user);
     }
     
     @Transactional(rollbackFor=Exception.class)
     public void doSave(User user) {
    
    
         addData1();
         updateData2();
      }
   }

The three-level cache inside spring ioc guarantees it, and there will be no circular dependency problem.

3. Use AopContext.currentProxy() in the Service class to get the proxy object

The above method 2 can indeed solve the problem, but the code does not look intuitive, and the same function can be achieved by using AOPProxy in the Service class to obtain the proxy object. The specific code is as follows:

@Servcie
  publicclass ServiceA {
    
    
  
     public void save(User user) {
    
    
           queryData1();
           queryData2();
           ((ServiceA)AopContext.currentProxy()).doSave(user);
     }
     
     @Transactional(rollbackFor=Exception.class)
     public void doSave(User user) {
    
    
         addData1();
         updateData2();
      }
   }

Non-core methods are placed outside the transaction

Before using transactions, we should all think about whether all database operations need to be performed in transactions?

   @Autowired
   private TransactionTemplate transactionTemplate;
   
   ...
   
   public void save(final User user) {
    
    
         transactionTemplate.execute((status) => {
    
    
            addData();
            addLog();
            updateCount();
            return Boolean.TRUE;
         })
   }

In the above example, in fact, the method of addLog to increase the operation log and the method of updateCount to update the statistical quantity can not be executed in the transaction, because the business of operation log and statistical quantity allows a small amount of data inconsistency.

Asynchronous processing via MQ

Do all methods in the transaction need to be executed synchronously? We all know that method synchronous execution needs to wait for the method to return. If there are too many synchronous execution methods in a transaction, it will inevitably cause too long waiting time and large transaction problems. The above addLog adds operation log method and updateCount update statistics method, we can process it asynchronously through MQ

   @Autowired
   private TransactionTemplate transactionTemplate;
   
   ...
   
   public void save(final User user) {
    
    
         transactionTemplate.execute((status) => {
    
    
            addData();
            return Boolean.TRUE;
         })
         sendMq();
   }

Avoid remote calls in transactions

It is unavoidable for us to call the interfaces of other systems in the interface. Due to the unstable network, the response time of this remote call may be relatively long. If the code of the remote call is placed in a certain thing, this thing may be a big transaction. Of course, remote calls not only refer to calling interfaces, but also include: sending MQ messages, or connecting to redis, mongodb to save data, etc.

@Transactional(rollbackFor=Exception.class)
   public void save(User user) {
    
    
         callRemoteApi();
         addData1();
   }

The code for remote calling may take a long time, so remember to put it outside the transaction.

   @Autowired
   private TransactionTemplate transactionTemplate;
   
   ...
   
   public void save(final User user) {
    
    
         callRemoteApi();
         transactionTemplate.execute((status) => {
    
    
            addData1();
            return Boolean.TRUE;
         })
   }

Some friends may ask, how to ensure data consistency if the code called remotely is not placed in the transaction? This requires the establishment of a retry + compensation mechanism to achieve final data consistency.

Avoid processing too much data at once in a transaction

If too much data needs to be processed in a transaction, it will also cause large transaction problems. For example, for the convenience of operation, you may update 1,000 pieces of data in batches at a time, which will cause a large number of data locks to wait, especially in high-concurrency systems.

The solution is paging processing, 1000 pieces of data are divided into 50 pages, and only 20 pieces of data are processed at a time, which can greatly reduce the occurrence of large transactions.

Guess you like

Origin blog.csdn.net/qq798280904/article/details/130743156