preface
You may have seen the spring transaction failure scenario in many articles, so let's take a look at the water today to see if you can harvest something different. Go straight to the topic
spring transaction failure scenarios and reasons
1. Scenario 1: the service is not hosted to spring
public class TranInvalidCaseWithoutInjectSpring { private UserService userService; public TranInvalidCaseWithoutInjectSpring(UserService userService) { this.userService = userService; } @Transactional public boolean add(User user){ boolean isSuccess = userService.save(user); int i = 1 % 0; return isSuccess; } }
@Test public void testServiceWithoutInjectSpring(){ boolean randomBoolean = new Random().nextBoolean(); TranInvalidCaseWithoutInjectSpring tranInvalidCaseWithoutInjectSpring; if(randomBoolean){ tranInvalidCaseWithoutInjectSpring = applicationContext.getBean(TranInvalidCaseWithoutInjectSpring.class); System.out.println("service Has been spring trusteeship"); }else{ tranInvalidCaseWithoutInjectSpring = new TranInvalidCaseWithoutInjectSpring(userService); System.out.println("service Not by spring trusteeship"); } boolean isSuccess = tranInvalidCaseWithoutInjectSpring.add(user); Assert.assertTrue(isSuccess); }
Failure reason: the precondition for spring transaction to take effect is that the service must be a bean object
Solution: inject the service into spring
2. Scenario 2: throw the detected exception
@Service public class TranInvalidCaseByThrowCheckException { @Autowired private UserService userService; @Transactional public boolean add(User user) throws FileNotFoundException { boolean isSuccess = userService.save(user); new FileInputStream("1.txt"); return isSuccess; } }
@Test public void testThrowCheckException() throws Exception{ boolean randomBoolean = new Random().nextBoolean(); boolean isSuccess = false; TranInvalidCaseByThrowCheckException tranInvalidCaseByThrowCheckException = applicationContext.getBean(TranInvalidCaseByThrowCheckException.class); if(randomBoolean){ System.out.println("to configure@Transactional(rollbackFor = Exception.class)"); isSuccess = tranInvalidCaseByThrowCheckException.save(user); }else{ System.out.println("to configure@Transactional"); tranInvalidCaseByThrowCheckException.add(user); } Assert.assertTrue(isSuccess); }
Failure reason: spring will roll back only non check exceptions and error exceptions by default
Solution: configure rollback for
3. Scenario 3: the business catches exceptions
@Transactional public boolean add(User user) { boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) { } return isSuccess; }
@Test public void testCatchExecption() throws Exception{ boolean randomBoolean = new Random().nextBoolean(); boolean isSuccess = false; TranInvalidCaseWithCatchException tranInvalidCaseByThrowCheckException = applicationContext.getBean(TranInvalidCaseWithCatchException.class); if(randomBoolean){ randomBoolean = new Random().nextBoolean(); if(randomBoolean){ System.out.println("Throw the exception as it is"); tranInvalidCaseByThrowCheckException.save(user); }else{ System.out.println("set up TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();"); tranInvalidCaseByThrowCheckException.addWithRollBack(user); } }else{ System.out.println("The business caught the exception itself"); tranInvalidCaseByThrowCheckException.add(user); } Assert.assertTrue(isSuccess); }
Failure reason: the spring transaction can be processed only after it catches the exception thrown by the business. If the business catches the exception itself, the transaction cannot perceive it
Solution:
1. Throw the exception as it is;
2. Set transactionaspectsupport currentTransactionStatus(). setRollbackOnly();
4. Scenario 4: the sequence of sections leads to
@Service public class TranInvalidCaseWithAopSort { @Autowired private UserService userService; @Transactional public boolean save(User user) { boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) { throw new RuntimeException(); } return isSuccess; } }
@Aspect @Component @Slf4j public class AopAspect { @Around(value = " execution (* com.github.lybgeek.transcase.aopsort..*.*(..))") public Object around(ProceedingJoinPoint pjp){ try { System.out.println("This is a section"); return pjp.proceed(); } catch (Throwable throwable) { log.error("{}",throwable); } return null; } }
Failure reason: the priority order of spring transaction aspect is the lowest. However, if the customized aspect has the same priority and the customized aspect does not correctly handle exceptions, it will be the same as the scenario in which the business catches exceptions
Solution:
1. Throw the exception as it is in the section;
2. Set transactionaspectsupport. In the aspect currentTransactionStatus(). setRollbackOnly();
5. Scenario 5: non public method
@Service public class TranInvalidCaseWithAccessPerm { @Autowired private UserService userService; @Transactional protected boolean save(User user){ boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) { throw new RuntimeException(); } return isSuccess; } }
public class TranInvalidCaseWithAccessPermTest { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(Application.class); TranInvalidCaseWithAccessPerm tranInvalidCaseWithAccessPerm = context.getBean(TranInvalidCaseWithAccessPerm.class); boolean isSuccess = tranInvalidCaseWithAccessPerm.save(UserUtils.getUser()); System.out.println(isSuccess); } }
Failure reason: the default effective method permissions of spring transactions must be public
Solution:
1. Change the method to public;
2. Modify the TansactionAttributeSource and change the publicMethodsOnly to false [the conclusion is drawn from the source code trace]
3. Draw conclusions from AspectJ
The document is as follows
Method visibility and @Transactional
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.
Specific steps:
1. Introduce aspectjrt coordinates and corresponding plug-ins into pom
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.9</version> </dependency> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.9</version> <configuration> <showWeaveInfo>true</showWeaveInfo> <aspectLibraries> <aspectLibrary> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </aspectLibrary> </aspectLibraries> </configuration> <executions> <execution> <goals> <goal>compile</goal> <!-- use this goal to weave all your main classes --> <goal>test-compile</goal> <!-- use this goal to weave all your test classes --> </goals> </execution> </executions> </plugin>
2. Add the following configuration to the startup class
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
Note: if it is running on idea, the following configuration is required
4. Directly use TransactionTemplate
Example:
@Autowired private TransactionTemplate transactionTemplate; private void process(){ transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { processInTransaction(); } }); }
6. Scenario 6: parent child container
Failure reason: the scan range of the sub container is too large. Scan the serivce without transaction configuration
Solution:
1. The range of parent-child containers;
2. Without parent-child containers, all bean s are managed by the same container
Note: because the example uses springboot, and there is no parent-child container by default for springboot startup, there is only one container, so this scenario demonstrates the example
7. Scenario 7: the method is modified with final
@Transactional public final boolean add(User user, UserService userService) { boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) { throw new RuntimeException(); } return isSuccess; }
Failure reason: because spring transaction is implemented by dynamic proxy, if the method uses final modification, the proxy class cannot rewrite the target method and implant transaction function
Solution:
1. Methods should not be modified with final
8. Scenario 8: the method is modified with static
@Transactional public static boolean save(User user, UserService userService) { boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) { throw new RuntimeException(); } return isSuccess; }
Failure reason: the reason is the same as final
Solution:
1. Methods should not be modified with static
9. Scenario 9: call this method
public boolean save(User user) { return this.saveUser(user); } @Transactional public boolean saveUser(User user) { boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) { throw new RuntimeException(); } return isSuccess; }
Failure reason: this kind of method cannot be enhanced without agent
Solution:
1. Inject yourself to call;
2. Use @ enableaspectjautoproxy (exposeproxy = true) + aopcontext currentProxy()
10. Scenario 10: multithreaded call
@Transactional(rollbackFor = Exception.class) public boolean save(User user) throws ExecutionException, InterruptedException { Future<Boolean> future = executorService.submit(() -> { boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) { throw new Exception(); } return isSuccess; }); return future.get(); }
Failure reason: because the transaction of spring is realized through database connection, and the database connection spring is placed in threadLocal. The same transaction can only be connected with the same database. In the multithreaded scenario, the database connections obtained are different, that is, they belong to different transactions
11. Scenario 11: wrong communication behavior
@Transactional(propagation = Propagation.NOT_SUPPORTED) public boolean save(User user) { boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) { throw new RuntimeException(); } return isSuccess; }
Failure reason: the propagation feature used does not support transactions
12. Scenario 12: using a storage engine that does not support transactions
Failure reason: a storage engine that does not support transactions is used. For example, MyISAM in mysql
13. Scenario 13: the data source is not configured with a transaction manager
Note: because of the spring boot, the transaction manager has been started by default. org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration. Therefore, the example is omitted
14. Scenario 14: premature instantiation of the proxy class
@Service public class TranInvalidCaseInstantiatedTooEarly implements BeanPostProcessor , Ordered { @Autowired private UserService userService; @Transactional public boolean save(User user) { boolean isSuccess = userService.save(user); try { int i = 1 % 0; } catch (Exception e) { throw new RuntimeException(); } return isSuccess; } @Override public int getOrder() { return 1; } }
Failure reason: when the instantiation of the proxy class is earlier than the AbstractAutoProxyCreator postprocessor, it cannot be enhanced by the AbstractAutoProxyCreator postprocessor
summary
This article lists 14 scenarios of spring transaction failure. In fact, many of them are caused by the same kind of problems in the final analysis, such as dynamic agent, method qualifier, exception type, etc
demo link
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-transaction-invalid-case