It seems that when a transactional spring method with propagation of NESTED calls another transactional method with propagation REQUIRED, the inner transaction can force the rollback of the outer logical transaction.
For example, if the transactional method ClassA.methodA (which has propagation = REQUIRED) calls the transactional method ClassB.methodB (which has propagation = NESTED) which in turn calls transactional method ClassC.methodC (which has propagation = REQUIRED) then if ClassC.methodC initiates a rollback, then the outer logical transaction begun by ClassA.methodA will get an UnexpectedRollbackException if it attempts to commit.
It makes sense that the logical nested transaction owned by B should be forced to be rolled back if C fails, but it doesn't make sense that the logical transaction owned by C, and called from inside B which by itself can't affect A's logical transaction, should be able to affect A's logical transaction. It seems to create a situation in which methodA cannot treat methodB as a black box, since on the face of it, it seems that method B shouldn't be able to impact A's transaction, but depending on what it does internally, it may be able to violate the nested transaction boundary. Why is this possible? Why did spring choose to implement it this way?
Here's the code example discussed above:
public class ClassA {
private ClassB classB;
#Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
try {
classB.methodB();
} catch (RuntimeException re) {
// do nothing we don't want to fail, but alas we will get an UnexpectedRollbackException
}
}
}
public class ClassB {
private ClassC classC;
#Transactional(propagation = Propagation.NESTED)
public void methodB() {
classC.methodC();
}
}
public class ClassC {
#Transactional(propagation = Propagation.REQUIRED)
public void methodC() {
// If this method weren't transactional this would just return to the save point created by method B
// I Want to just fail the NESTED transaction, but alas I'm going to fail everything
throw new RuntimeException("Oh, woe is me!");
}
}
I'm not sure why it works like that, but I found a workaround for this:
public class ClassA {
private ClassB classB;
public void methodA() {
try {
classB.methodB();
} catch (RuntimeException re) {
// do nothing we don't want to fail, but alas we will get an UnexpectedRollbackException
}
}
}
public class ClassB {
private ClassC classC;
#Transactional(propagation = Propagation.NESTED)
public void methodB() {
classC.methodC();
}
}
public class ClassC {
private TransactionTemplate requiredTransaction = createRequiredTransactionTemplate();
public void methodC() {
if (TransactionSynchronizationManager.isActualTransactionActive()) {
// We are already in transaction, do not wrap in transaction
_methodC();
} else {
requiredTransaction.execute(cb -> {
_methodC();
return null;
});
}
}
private void _methodC() {
throw new RuntimeException("Oh, woe is me!");
}
}
Related
I have two classes:
#Service
#Transaction
class A {
public void method1() {
private B;
try {
save1()
b.method2()
} catch (SqlException e) {
doSomeThing();
}
#Autowired
public setB(){
this.B = B;
}
}
}
#Service
class B {
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void method2(){
save2()
throw new SqlException();
}
}
I got an SqlException as expected, but also an UnexpectedRollBackException, and the program stops.
I want to know why the data persisted by save2() is not rolled back?
Is it a problem with outer transaction?
UPDATE: I tried catching UnexpectedRollBackException in class A and everything works fine. But I still need some kind of explanation why I get the exception? I suppose the outer transaction should be suspended when the inner transaction begins, so why the rollback is unexpected for the outer transaction?
Thanks.
First of all: you are not injecting the instance of B into class A via spring. i.e. your b is not managed by spring => this leads to the behaviour: Spring is ignoring the #Transactional annotation on method.
Second if you don't want to rollback on SqlException you have to specify noRollbackFor=SqlException.class
UPDATE: after clarification, the call is happening on managed bean. But the problem that expected behaviour - transaction inside of transaction is not supported in general by the transaction management system out of the box. https://docs.spring.io/spring/docs/4.3.11.RELEASE/javadoc-api/org/springframework/transaction/annotation/Propagation.html#REQUIRES_NEW
Unless the details on that system are provided it is impossible to step forward. Out of content the NESTED transaction propagation could be better, then REQUIRES_NEW, because it is making a rollback point for the external transaction and starts new one.
I have a simliar scenario like this and resolved it using propagation = Propagation.NESTED from where the second method is calling. rewrite the code as mentioned below and try.
#Service
class A {
#Transactional(rollbackFor = { HibernateException.class}, propagation = Propagation.NESTED)
public void method1() {
private B;
try {
save1()
b.method2()
} catch (SqlException e) {
doSomeThing();
}
#Autowired
public setB(){
this.B = B;
}
}
}
Note: Transactional is used in method level for class A with propagation nested . Class B shown below.
#Service
class B {
#Transactional(rollbackFor = {Exception.class}, propagation = Propagation.REQUIRED)
public void method2(){
save2()
throw new SqlException();
}
}
This has resolved the issue in my case.
I'm not sure where to open my Transaction object. Inside the service layer? Or the controller layer?
My Controller basically has two services, let's call them AService and BService. Then my code goes something like:
public class Controller {
public AService aService = new AService();
public BService bService = new BService();
public void doSomething(SomeData data) {
//Transaction transaction = HibernateUtil.getSession().openTransaction();
if (data.getSomeCondition()) {
aService.save(data.getSomeVar1());
bService.save(data.getSomeVar2());
}
else {
bService.save(data.getSomeVar2());
}
//transaction.commit(); or optional try-catch with rollback
}
}
The behavior I want is that if bService#save fails, then I could invoke a transaction#rollback so that whatever was saved in aService would be rolled back as well. This only seems possible if I create one single transaction for both saves.
But looking at it in a different perspective, it looks really ugly that my Controller is dependent on the Transaction. It would be better if I create the Transaction inside the respective services, (something like how Spring #Transactional works), but if I do it that way, then I don't know how to achieve what I want to happen...
EDIT: Fixed code, added another condition. I am not using any Spring dependencies so the usage of #Transactional is out of the question.
You can accomplish what you're asking with another layer of abstraction and using composition.
public class CompositeABService {
#Autowired
private AService aservice;
#Autowired
private BService bservice;
#Transactional
public void save(Object value1, Object value2) {
aservice.save( value1 );
bservice.save( value2 );
}
}
public class AService {
#Transactional
public void save(Object value) {
// joins an existing transaction if one exists, creates a new one otherwise.
}
}
public class BService {
#Transactional
public void save(Object value) {
// joins an existing transaction if one exists, creates a new one otherwise.
}
}
This same pattern is typically used when you need to interact with multiple repositories as a part of a single unit of work (e.g. transaction).
Now all your controller needs to depend upon is CompositeABService or whatever you wish to name it.
Consider the following situation:
#Stateless
#Clustered
public class FacadeBean implements Facade {
#EJB
private Facade facade;
#Override
public void foo(List<Integer> ids) {
// read specific id's from the db
for (Integer id : ids) {
facade.bar(id);
}
}
#Override
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void bar(Integer id) {
// save something to the db
}
}
Method foo gets called from outside the ejb. I want each id to be executed in its own transaction, so that data get's persisted directly to the database. It is not possible to have the foreach outside of this class. I am wondering what is the best way to do this? At the moment I am injecting the interface again, to step over the ejb boundaries (so that the TransactionAttribute get's evaluated).
Your approach as to circular reference is perfectly fine. Circular reference in EJBs is allowed. This is even mandatory in order to start out a new transaction, or an #Asynchronous thread (otherwise the current thread would still block).
#Stateless
public class SomeService {
#EJB
private SomeService self; // Self-reference is perfectly fine.
// Below example starts a new transaction for each item.
public void foo(Iterable<Long> ids) {
for (Long id : ids) {
self.fooInNewTransaction(id);
}
}
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void fooInNewTransaction(Long id) {
// ...
}
// Below example fires an async thread in new transaction.
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void bar(Iterable<Long> ids) {
for (Long id : ids) {
self.fooAsynchronously(id);
}
}
#Asynchronous
public void fooAsynchronously(Long id) {
// ...
}
}
Only in older containers, this did not work, most notably JBoss AS 5 with the ancient EJB 3.0 API. That's why people invented workarounds like SessionContext#getBusinessObject() or even manually grabbing via JNDI.
Those are unnecessary these days. Those are workarounds not solutions.
I'd personally only do it the other way round as to transactions. The foo() method is clearly never intented to be transactional.
#Stateless
public class SomeService {
#EJB
private SomeService self;
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void foo(Iterable<Long> ids) {
for (Long id : ids) {
self.foo(id);
}
}
public void foo(Long id) {
// ...
}
}
Depending on the concrete functional requirement, you could even make the foo(Long id) #Asynchronous, hereby speeding up the task.
Do you really have to have both methods in one class? You can move bar() to an own bean and make it transactional. Then you don't have to use this kind of self-injection.
You can also try to use SessionContext#getBusinessObject() method.
#Resource
SessionContext sessionContext;
#Override
public void foo(List<Integer> ids) {
Facade facade = sessionContext.getBusinessObject(Facade.class);
// read specific id's from the db
for (Integer id : ids) {
facade.bar(id);
}
}
Given this example code:
public class MyServiceImpl implements MyService {
#Transactional
public void myTransactionalMethod() {
List<Item> itemList = itemService.findItems();
for (Item anItem : itemList) {
try {
processItem(anItem);
catch (Exception e) {
// dont rollback here
// rollback just one item
}
}
}
#Transactional
public void processItem(Item anItem) {
anItem.setSomething(new Something);
anItem.applyBehaviour();
itemService.save(anItem);
}
}
Here is what I want to achieve:
Only processItem(anItem); should rollback if exception occurs inside it.
If exception occurs, myTransactionalMethod should continue, that means the for-each should end.
If exception occurs inside myTransactionalMethod but not in processItem(anItem), myTransactionalMethod should rollback completely.
Is there a solution that doesn't involve managing transactions manually (without annotations)?.
Edit: I was thinking of using #Transactional(PROPAGATION=REQUIRES_NEW), don't know if it will work within the same bean though.
This is a common misunderstanding. Spring Transactions are implemented through proxies. Proxies are a wrapper around your class. You are accessing the processItem method from the same class, i.e. you don't go through the proxy, so you don't get any transactions. I explained the mechanism in this answer some years ago.
Solution: you need two separate Spring beans if you want nested transactions, both of them must be proxied by #Transactional.
It looks like a case for NESTED transaction. NESTED transaction starts a subtransaction with in the outer transaction with savepoint, allowing it rollback to that savepoint. Since it is a nested transactions they committed at the end of outer transation.
public class MyServiceImpl implements MyService {
#Transactional
public void myTransactionalMethod() {
List<Item> itemList = itemService.findItems();
for (Item anItem : itemList) {
try {
// If you want to call this method directly configure your transaction use to aspectJ for transaction handling or refactor the code. Refer - [http://stackoverflow.com/questions/3423972/spring-transaction-method-call-by-the-method-within-the-same-class-does-not-wo][1]
processItem(anItem);
catch (Exception e) {
// dont rollback here
// rollback just one item
}
}
}
#Transactional(PROPAGATION = PROPAGATION.NESTED)
// Throw some runtime exception to rollback or some checkedException with rollbackFor attribute set in the above annotation
public void processItem(Item anItem) {
anItem.setSomething(new Something);
anItem.applyBehaviour();
itemService.save(anItem);
}
}
Note that, I have not yet tried this below code see if that helps. You might have to tweak it, if needed. In fact I would love to give this code a try myself sometime soon.
Below is a quick outline of what I'm trying to do. I want to push a record to two different tables in the database from one method call. If anything fails, I want everything to roll back. So if insertIntoB fails, I want anything that would be committed in insertIntoA to be rolled back.
public class Service {
MyDAO dao;
public void insertRecords(List<Record> records){
for (Record record : records){
insertIntoAAndB(record);
}
}
#Transactional (rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void insertIntoAAndB(Record record){
insertIntoA(record);
insertIntoB(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoA(Record record){
dao.insertIntoA(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoB(Record record){
dao.insertIntoB(record);
}
public void setMyDAO(final MyDAO dao) {
this.dao = dao;
}
}
Where MyDAO dao is an interface that is mapped to the database using mybatis and is set using Spring injections.
Right now if insertIntoB fails, everything from insertIntoA still gets pushed to the database. How can I correct this behavior?
EDIT:
I modified the class to give a more accurate description of what I'm trying to achieve. If I run insertIntoAAndB directly, the roll back works if there are any issues, but if I call insertIntoAAndB from insertRecords, the roll back doesn't work if any issues arise.
I found the solution!
Apparently Spring can't intercept internal method calls to transactional methods. So I took out the method calling the transactional method, and put it into a separate class, and the rollback works just fine. Below is a rough example of the fix.
public class Foo {
public void insertRecords(List<Record> records){
Service myService = new Service();
for (Record record : records){
myService.insertIntoAAndB(record);
}
}
}
public class Service {
MyDAO dao;
#Transactional (rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void insertIntoAAndB(Record record){
insertIntoA(record);
insertIntoB(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoA(Record record){
dao.insertIntoA(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoB(Record record){
dao.insertIntoB(record);
}
public void setMyDAO(final MyDAO dao) {
this.dao = dao;
}
}
I think the behavior you encounter is dependent on what ORM / persistence provider and database you're using. I tested your case using hibernate & mysql and all my transactions rolled back alright.
If you do use hibernate enable SQL and transaction logging to see what it's doing:
log4j.logger.org.hibernate.SQL=DEBUG
log4j.logger.org.hibernate.transaction=DEBUG
// for hibernate 4.2.2
// log4j.logger.org.hibernate.engine.transaction=DEBUG
If you're on plain jdbc (using spring JdbcTemplate), you can also debug SQL & transaction on Spring level
log4j.logger.org.springframework.jdbc.core=DEBUG
log4j.logger.org.springframework.transaction=DEBUG
Double check your autocommit settings and database specific peciular (eg: most DDL will be comitted right away, you won't be able to roll it back although spring/hibernate did so)
Just because jdk parses aop annotation not only with the method, also parse annotation with the target class.
For example, you have method A with #transactional, and method B which calls method A but without #transactional, When you invoke the method B with reflection, Spring AOP will check the B method with the target class has any annotations.
So if your calling method in this class is not with the #transactional, it will not parse any other method in this method.
At last, show you the source code:
org.springframework.aop.framework.jdkDynamicAopProxy.class
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
......
// Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping orfancy proxying.
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
}