6 @Transactional annotation failure scenarios

1. Affairs

Transaction management system development is an integral part, Springprovides a good transaction management mechanism, it is divided into 编程式事务and 声明式事务two kinds.

Programmatic transaction : refers to the manual management of transaction submission, rollback and other operations in the code, the code is relatively intrusive, as the following example:

try {
//TODO something
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
thrownew InvoiceApplyException("异常失败");
}

Declarative transactions : Based on AOPthe aspect-oriented, it decouples specific business and transaction processing parts, and the code intrusion is very low, so declarative transactions are used more in actual development. Declarative transaction also implemented in two ways, one is based TXand AOPxml configuration file way, is based on two kinds of @Transactional annotated.

@Transactional
@GetMapping("/test")
public String test() {

int insert = cityInfoDictMapper.insert(cityInfoDict);
}

2. Introduction to @Transactional

1. Where can @Transactional annotation work?

@Transactional It can act 接口, , 类方法.

  • Act on the class : When the @Transactional 注解放在类上时,表示所有该类的 public  methods are configured with the same transaction attribute information.
  • Effect on method : When the class is configured @Transactional, the method is also configured @Transactional, the transaction of the method will  override  the transaction configuration information of the class.
  • Acting on the interface : This method of use is not recommended, because once marked on the Interface and configured with Spring AOP to use CGLib dynamic proxy, the @Transactionalannotation will be invalid
@Transactional
@RestController
@RequestMapping
publicclass MybatisPlusController {
@Autowired
private CityInfoDictMapper cityInfoDictMapper;

@Transactional(rollbackFor = Exception.class)
@GetMapping("/test")
public String test() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setParentCityId(2);
cityInfoDict.setCityName("2");
cityInfoDict.setCityLevel("2");
cityInfoDict.setCityCode("2");
int insert = cityInfoDictMapper.insert(cityInfoDict);
return insert + "";
}
}

2. What are the attributes of @Transactional annotation?

propagation attribute

propagation Represents the propagation behavior of the transaction. The default value is  Propagation.REQUIREDas follows. Other attribute information is as follows:

  • Propagation.REQUIRED: If there is currently a transaction, join the transaction, if there is no current transaction, create a new transaction. (  That is to say, if both method A and method B are annotated, in the default propagation mode, method A internally calls method B, and the transactions of the two methods are merged into one transaction  )
  • Propagation.SUPPORTS: If there is currently a transaction, then join the transaction; if there is no transaction, then continue to run in a non-transactional way.
  • Propagation.MANDATORY: If there is currently a transaction, the transaction is added; if there is no current transaction, an exception is thrown.
  • Propagation.REQUIRES_NEW: Re-create a new transaction, if there is currently a transaction, suspend the current transaction. (  When the a method in class A uses the default Propagation.REQUIREDmode, the b method in class B plus the adoption  Propagation.REQUIRES_NEWmode, and then call the b method in the a method to operate the database, but after the a method throws an exception, the b method does not roll back, Because it Propagation.REQUIRES_NEWwill suspend the transaction of method a  )
  • Propagation.NOT_SUPPORTED: Run in a non-transactional way, if there is currently a transaction, suspend the current transaction.
  • Propagation.NEVER: Run in a non-transactional way, if a transaction currently exists, an exception is thrown.
  • Propagation.NESTED : Same effect as Propagation.REQUIRED.
isolation property

isolation : The isolation level of the transaction, the default value is  Isolation.DEFAULT.

  • TransactionDefinition.ISOLATION_DEFAULT:  Use the default isolation level of the back-end database, Mysql default REPEATABLE_READ isolation level Oracle default READ_COMMITTED isolation level.
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: the  lowest isolation level, allowing reading of uncommitted data changes, which may result in dirty reads, phantom reads, or non-repeatable reads
  • TransactionDefinition.ISOLATION_READ_COMMITTED:  allows reading of data that has been submitted by concurrent transactions, can prevent dirty reads, but magic reads or non-repeatable reads may still occur
  • TransactionDefinition.ISOLATION_REPEATABLE_READ: The  results of multiple reads on the same field are consistent, unless the data is modified by its own transaction, which can prevent dirty reads and non-repeatable reads, but magic reads may still occur.
  • TransactionDefinition.ISOLATION_SERIALIZABLE: The  highest isolation level, fully compliant with the ACID isolation level. All transactions are executed one by one in turn, so that there is no possibility of interference between transactions, that is, this level can prevent dirty reads, non-repeatable reads, and magic reads . But this will seriously affect the performance of the program. Normally, this level is not used.
timeout attribute

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

readOnly attribute

readOnly : Specify whether the transaction is a read-only transaction, the default value is false; in order to ignore those methods that do not require transactions, such as reading data, you can set read-only to true.

rollbackFor attribute

rollbackFor : Used to specify the type of exception that can trigger a transaction rollback. Multiple exception types can be specified.

noRollbackFor attribute **

noRollbackFor: Throw the specified exception type. You can also specify multiple exception types without rolling back the transaction.

Three, @Transactional failure scenarios

Next, let's analyze the specific code combined with the specific code, @Transactional annotation will be invalid.

1. @Transactional applies to non-public decorated methods

If the Transactionalannotation is applied to a non- public decorated method, Transactional will be invalid.

The reason why it is invalid is that in the Spring AOP proxy, as shown in the above figure  TransactionInterceptor (transaction interceptor) before and after the execution of the target method, DynamicAdvisedInterceptorthe intercept method (inner class of CglibAopProxy) or  JdkDynamicAopProxy invoke method will indirectly call  AbstractFallbackTransactionAttributeSourcethe  computeTransactionAttribute method to obtain Transactional Annotated transaction configuration information.

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

This method will check whether the modifier of the target method is public, @Transactional and property configuration information that will not be obtained if it is not public .

Note: protected, private using the modified method  @Transactional comment, although the transaction is not valid, but there will be no error, this is our very little tolerance for error.

2. @Transactional annotation property propagation is set incorrectly

This failure is due to a configuration error. If the following three configurations are incorrectly configured, the transaction will not be rolled back.

TransactionDefinition.PROPAGATION_SUPPORTS: If there is currently a transaction, join the transaction; if there is no transaction currently, continue to run in a non-transactional way.

TransactionDefinition.PROPAGATION_NOT_SUPPORTED: Run in non-transactional mode, if there is currently a transaction, suspend the current transaction.

TransactionDefinition.PROPAGATION_NEVER: Run in non-transactional mode, if a transaction currently exists, an exception is thrown.

3. @Transactional annotation attribute rollbackFor is set incorrectly

rollbackFor You can specify the type of exception that can trigger a transaction rollback. By default, Spring throws unchecked uncheckedexceptions (inherited  RuntimeException exceptions) or  Errorrolls back the transaction; other exceptions will not trigger the rollback transaction. If other types of exceptions are thrown in the transaction, but expect Spring to be able to roll back the transaction, you need to specify the  rollbackFor attribute.

// 希望自定义的异常可以进行回滚
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class

If the exception thrown in the target method is rollbackFor a subclass of the  specified exception, the transaction will also be rolled back. 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);
}

4. Method calls in the same class cause @Transactional to fail

During the development, it is inevitable to call methods in the same class. For example, there is a class Test, one of its methods A, A then calls method B of this class (regardless of whether method B is modified with public or private), but method A does not The statement annotates the transaction, and the B method has. After externally calling method A, the transaction of method B will not work. This is also a place where mistakes are often made.

Why does this happen? In fact, this is caused by the use of Spring AOPproxy, because only when the transaction method is called by code other than the current class, it will be managed by the Springgenerated proxy object.

//@Transactional
@GetMapping("/test")
private Integer A() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
/**
* B 插入字段为 3的数据
*/
this.insertB();
/**
* A 插入字段为 2的数据
*/
int insert = cityInfoDictMapper.insert(cityInfoDict);

return insert;
}

@Transactional()
public Integer insertB() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("3");
cityInfoDict.setParentCityId(3);

return cityInfoDictMapper.insert(cityInfoDict);
}

5. The exception is "eaten" by your catch and causes @Transactional to fail

This situation is the most common type of  @Transactional annotation failure scenario,

    @Transactional
private Integer A() throws Exception {
int insert = 0;
try {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
cityInfoDict.setParentCityId(2);
/**
* A 插入字段为 2的数据
*/
insert = cityInfoDictMapper.insert(cityInfoDict);
/**
* B 插入字段为 3的数据
*/
b.insertB();
} catch (Exception e) {
e.printStackTrace();
}
}

If method B throws an exception internally and method A tries to catch the exception of method B at this time, can this transaction be rolled back normally?

Answer: No!

Will throw an exception:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

Because when ServiceBthrown an exception after ServiceBidentifying the current transaction needs rollback. However ServiceA, because you manually catch this exception and handle it, you ServiceAthink the current transaction should be normal commit. At this time, there was an inconsistency, that is, because of this, the previous UnexpectedRollbackExceptionexception was thrown .

springThe transaction is started before the business method is called, commit or after the business method is executed, or  rollbackwhether the transaction is executed depends on whether it is thrown runtime异常. If thrown runtime exception and there is no catch in your business method, the transaction will be rolled back.

Generally, catch exceptions are not required in business methods. If you want to catch, you must throw them throw new RuntimeException(), or specify the exception type in the annotations @Transactional(rollbackFor=Exception.class), otherwise it will cause transaction failure and data commit will cause data inconsistency.

6. The database engine does not support transactions

The probability of this situation is not high, and whether the transaction can take effect is the key to whether the database engine supports the transaction. Commonly used MySQL databases use transaction-supporting innodbengines by default . Once the database engine is switched to not support transactions myisam, then the transaction is basically invalid.

to sum up

@Transactional The annotations seem to be simple and easy to use, but if you don't know a little about its usage, you will still step on many pits.

Guess you like

Origin www.cnblogs.com/frankyou/p/12691463.html