3

foreword

You may have read the spring transaction failure scenario in many articles, so today I will write an article to see if you can harvest something different. go directly to the topic

Spring transaction failure scenarios and reasons

1. Scenario 1: The service is not hosted by 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已经被spring托管");
        }else{
            tranInvalidCaseWithoutInjectSpring = new TranInvalidCaseWithoutInjectSpring(userService);
            System.out.println("service没被spring托管");
        }

        boolean isSuccess = tranInvalidCaseWithoutInjectSpring.add(user);
        Assert.assertTrue(isSuccess);

    }

Failure reason: The premise for spring transaction to take effect is that service must be a bean object
solution: inject service into spring

2. Scenario 2: Throwing a checked 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("配置@Transactional(rollbackFor = Exception.class)");
            isSuccess = tranInvalidCaseByThrowCheckException.save(user);
        }else{
            System.out.println("配置@Transactional");
            tranInvalidCaseByThrowCheckException.add(user);
        }

        Assert.assertTrue(isSuccess);

    }

failure reason: spring will only roll back unchecked exceptions and error exceptions by default
solution: configure rollbackFor

3. Scenario 3: The business catches the exception itself

 @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("将异常原样抛出");
                tranInvalidCaseByThrowCheckException.save(user);
            }else{
                System.out.println("设置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();");
                tranInvalidCaseByThrowCheckException.addWithRollBack(user);
            }
        }else{
            System.out.println("业务自己捕获了异常");
            tranInvalidCaseByThrowCheckException.add(user);
        }

        Assert.assertTrue(isSuccess);

    }

Failure reason: Spring transaction can only carry out subsequent processing if it catches the exception thrown by the business. If the business itself catches the exception, the transaction cannot be sensed
solution:
1. Throw the exception as it is;
2. Set TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

4. Scenario 4: The sequence of facets 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("这是一个切面");
           return pjp.proceed();
        } catch (Throwable throwable) {
            log.error("{}",throwable);
        }

        return null;
    }
}

Reason for failure of : Spring transaction aspect has the lowest priority order, but if the custom aspect has the same priority and the customized aspect does not handle exceptions correctly, it will be the same as the scenario where the business itself catches exceptions
solution:
1. Throw the exception as it is in the aspect;
2. Set TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() in the aspect;

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: Spring transaction takes effect by default, all method permissions must be public

solution:
1. Change the method to public;
2. Modify TansactionAttributeSource and change publicMethodsOnly to false [this is concluded from source code tracking]
3. Turn on the AspectJ proxy mode [drawing conclusions from the spring documentation]

文档如下
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 plugins in 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 is running on idea, you need to do the following configuration
在这里插入图片描述
4. Use TransactionTemplate directly

Example:

    @Autowired
    private TransactionTemplate transactionTemplate;

    private void process(){
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                processInTransaction();
            }
        });

    }

6. Scene 6: Parent-child container

Failure reason: sub-container scan range is too large, and the serivce without transaction configuration is scanned in

Solution:
1. The scope of the parent and child containers to be scanned;
2. No parent-child container, all beans are managed by the same container

Note: Because the example uses springboot, and springboot has no parent-child container by default, only one container, so the example is demonstrated for this scenario.

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 is decorated with final, the proxy class cannot rewrite the target method and implant the transaction function

solution:
1. Do not modify methods with final

8. Scenario 8: The method is decorated 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: reason is the same as final

Solution:
1. Do not modify the method with static

9. Scenario 9: Call this class 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 type of method does not go through the proxy and cannot be enhanced

Solution:
1. Inject yourself to call;
2. Use @EnableAspectJAutoProxy(exposeProxy = true) + AopContext.currentProxy()

10. Scenario 10: Multi-threaded 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 spring's transaction is realized through database connection, and the database connection spring is placed in threadLocal. The same transaction can only use the same database connection. In the multi-threaded scenario, the database connections obtained are different, that is, they belong to different transactions

11. Scenario Eleven: Wrong Propagation 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 by does not support transactions

12. Scenario 12: A storage engine that does not support transactions is used

Failure Reason: uses a storage engine that does not support transactions. Such as MyISAM in mysql

13. Scenario 13: The data source is not configured with a transaction manager

Note: has enabled the transaction manager by default because of springboot. org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration. So the example is skipped

14. Scenario 14: Premature instantiation of the proxied 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 instance of the proxy class is earlier than the AbstractAutoProxyCreator post-processor, it cannot be enhanced by the AbstractAutoProxyCreator post-processor

Summarize

This article lists 14 scenarios of spring transaction failure. In fact, many of these 14 are caused by the same type of problems, such as dynamic proxy reasons, method qualifier reasons, exception type reasons, etc.

demo link

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-transaction-invalid-case


linyb极客之路
336 声望193 粉丝