问题
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 MyObject
s, 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