@Transactional annotation does not rollback RuntimeException even if @EnableTransactionManagement is provided

旧巷老猫 提交于 2021-02-11 12:54:26

问题


I have the following application setup:

@SpringBootApplication
@EnableTransactionManagement
public class MyApp extends SpringBootServletInitializer {
    ...
}

with a class which has the following:

public class DoStaff {

    public void doStaffOnAll(List<MyObject> myObjects) {
        for (int i=0; i<myObjects.size(); i++) {
            try {
                doStaffOnSingle(myObjects.get(i), i);
            } catch (Exception e) {
                e.printStrackTrace();
            }
        }
    }

    @Transactional
    public void doStaffOnSingle(MyObject myObject, int i) {
        repository.save(myObject);
        if (i%2==0) {
            throw new RuntimeException();
        }
    }
    
}

So if I call DoStaff.doStaffOnAll with a list of MyObjects, the code saves all element from the list but also throws a runtime exception for every second element.

Since the doStaffOnSingle has @Transactional annotation, I would expect that every second element will be rolled back. But if I run this code, every element is saved in the DB successfully. Why is that? What am I doing wrong?


回答1:


Quoting Spring Documentation:

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation (in effect, a method within the target object calling another method of the target object) does not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional. Also, the proxy must be fully initialized to provide the expected behavior, so you should not rely on this feature in your initialization code (that is, @PostConstruct).

Move the doStaffOnAll() to a different Spring component, and it'll work.

Or change to aspectj mode.

I would recommend moving the method, and design the code so transaction boundaries are clear and distinct, i.e. all public methods on the class starts a transaction, or no methods on the class starts a transaction.

It should always be very clear where your transaction boundaries are, e.g. in a layered design, you would normally make the @Service layer also be the transaction layer, i.e. any call from a higher layer to the service layer is an atomic transaction.




回答2:


@Transactional annotation is able to do the magic because of a proxy object.

Since you call the method directly you don't get that magic. In doStaffOnAll method you are directly invoking doStaffOnSingle method. So, nothing of Transactional behaviour gets added.

Try invoking the method using self invocation.

@Service
public class DoStaff {
    @Autowired
    private DoStaff doStaff;

    public void doStaffOnAll(List<MyObject> myObjects) {
        for (int i=0; i<myObjects.size(); i++) {
           doStaff.doStaffOnSingle(..) // invoke like this
        }
    }

    @Transactional
    public void doStaffOnSingle(MyObject myObject, int i) {
       
    }
}

Since the doStaffOnSingle has @Transactional annotation, I would expect that every second element will be rolled back.

The default Transactional mode will commit everything or nothing. I think you would want to use REQUIRES_NEW Propagation.

Look here for supported propagation types.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Propagation.html#REQUIRED



来源:https://stackoverflow.com/questions/64610390/transactional-annotation-does-not-rollback-runtimeexception-even-if-enabletran

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!