Xiao Ming: Pretty boy, I have met something very strange recently.
Pretty boy: Oh? Tell me.
Xiaoming: Last time I read your article “That’s it? An article to make you understand Spring Transactions”, I had a detailed understanding of transactions. However, I still ran into problems in the project.
Pretty boy: Then today I will give you a summary of the scenarios in which the transaction will fail.
The database engine does not support transactions
Mysql 5.5.5 uses InnoDB as its default storage engine, while Mysql 5.5.5 uses MyISAM as its default storage engine. InnoDB, so make sure you use a database that does not support transactions.
2. Not managed by Spring
The class of the transaction method is not injected into the Spring container, as follows:
public class OrderServiceImpl implements OrderService {
@Autowired
AccountMapper accountMapper;
@Autowired
ProductMapper productMapper;
@Transactional
@Override
public void placeOrder(a) {
// Omit a bunch of logic here
// Modify user balance and merchandise inventoryaccountMapper.update(); productMapper.update(); }}Copy the code
If this class is not annotated with @service, the transaction will not take effect.
3. Not a public method
As the official documentation makes clear, the @Transactional annotation can only be used with public methods. If you want to use it with non-public methods, you can turn on the AspectJ proxy mode.
4. The exception is caught
Take this example:
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
AccountMapper accountMapper;
@Autowired
ProductMapper productMapper;
@Transactional
@Override
public void placeOrder(a) {
try{
// Omit a bunch of logic here
// Modify user balance and merchandise inventory
accountMapper.update();
productMapper.update();
} catch (Exception e) {
}
}
}
Copy the code
When an exception occurs in this method, the transaction will fail because the exception is caught and not thrown. How to resolve this situation? Don’t worry, look down
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
AccountMapper accountMapper;
@Autowired
ProductMapper productMapper;
@Transactional
@Override
public void placeOrder(a) {
try{
// Omit a bunch of logic here
// Modify user balance and merchandise inventory
accountMapper.update();
productMapper.update();
} catch (Exception e) {
// Manually roll backTransactionAspectSupport.crrentTransactionStatus().setRollbackOnly(); }}}Copy the code
Can be achieved by
TransactionAspectSupport.crrentTransactionStatus().setRollbackOnly();
Manually roll back.
5. The exception type is incorrect
The @Transactional annotation only rolls back exceptions of type RuntimeException by default, so it is recommended that you change it to Exception when you use it
@Transactional(rollbackFor = Exception.class)
6. Call transaction methods internally
This is probably the most common scenario where a transaction fails, and it’s the one I’m going to focus on.
Some relatively complex business logic operations, such as methods of order of the previous example, often before the write operation will have a heap of logic, if all operations in a method, and combined with the transaction, so will likely because the transaction execution time is too long, cause the transaction timeout, even if no timeout orders will also affect the performance of the interface. At this point, you can isolate the write operations and add transactions only to the write operations, so the pressure is much less.
Consider the following example:
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
AccountMapper accountMapper;
@Autowired
ProductMapper productMapper;
@Override
public void placeOrder(a) {
// Omit a bunch of logic here
this.updateByTransactional();
}
@Transactional
public void updateByTransactional(a) {
// Modify user balance and merchandise inventoryaccountMapper.update(); productMapper.update(); }}Copy the code
Because an internal call occurs and does not go through Spring’s proxy, the transaction will not take effect, as noted in the official documentation:
So what do you do in this case?
Plan 1: Make an external call instead
Internal calls don’t work, so I’ll make external calls instead
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
OrderTransactionService orderTransactionService;
@Override
public void placeOrder(a) {
// Omit a bunch of logic hereorderTransactionService.updateByTransactional(); }}@Service
public class OrderTransactionService {
@Autowired
AccountMapper accountMapper;
@Autowired
ProductMapper productMapper;
@Transactional
public void updateByTransactional(a) {
// Modify user balance and merchandise inventoryaccountMapper.update(); productMapper.update(); }}Copy the code
That’s the easy way to understand it
Option two: Use programmatic transactions
Since declarative transactions are problematic, can I switch to programmatic transactions?
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
AccountMapper accountMapper;
@Autowired
ProductMapper productMapper;
@Autowired
TransactionTemplate transactionTemplate;
@Override
public void placeOrder(a) {
// Omit a bunch of logic here
/ / TransactionCallbackWithoutResult no return parameter
// TransactionCallback has a return parameter
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
this.updateByTransactional();
} catch (Exception e) {
log.error("Order failed", e); transactionStatus.setRollbackOnly(); }}}); }public void updateByTransactional(a) {
// Modify user balance and merchandise inventoryaccountMapper.update(); productMapper.update(); }}Copy the code
Don’t care if it’s black or white, it’s a good cat that catches mice
Plan 3: Call it back through external methods
This is a method THAT I saw provided by netizens, and want to use annotations, and want to call, so you can refer to the way of programmatic transactions to achieve.
@Component
public class TransactionComponent {
public interface Callback<T>{
T run(a) throws Exception;
}
public interface CallbackWithOutResult {
void run(a) throws Exception;
}
// Take a return argument
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
@Nullable
public <T> T doTransactional(Callback<T> callback) throws Exception {
return callback.run();
}
// No return argument
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
@Nullable
public void doTransactionalWithOutResult(CallbackWithOutResult callbackWithOutResult) throws Exception { callbackWithOutResult.run(); }}Copy the code
This will resolve the invalidation problem by calling internal methods through the TransactionComponent.
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
AccountMapper accountMapper;
@Autowired
ProductMapper productMapper;
@Autowired
TransactionComponent transactionComponent;
@Override
public void placeOrder(a) {
// Omit a bunch of logic here
transactionComponent.doTransactionalWithOutResult(() -> this.updateByTransactional());
}
public void updateByTransactional(a) {
// Modify user balance and merchandise inventoryaccountMapper.update(); productMapper.update(); }}Copy the code
conclusion
This article summarizes several common transaction failure scenarios and some solutions, which may or may not be exhaustive. What other scenes have you met that I haven’t mentioned? Welcome to share and correct any shortcomings.
END
Phase to recommend
Is this it? An article will help you read Spring transactions
SpringBoot+Redis implements message subscription publishing
The most detailed graphic analysis of various Locks in Java
Common code refactoring techniques that you can use
Graphic description of 23 design modes