@Transactional doesn't work at the service layer for nested transactions

917 views
Skip to first unread message

Pavan

unread,
Jun 15, 2016, 2:48:40 PM6/15/16
to eb...@googlegroups.com

Hi Rob/Community 

I have found issues with getting the nested transactions to work with ebean. It will be of great help if you guys can kindly advice on how best to handle nested transactions ! 

Ebean transaction page (http://ebean-orm.github.io/docs/transactions/) says : You must be using ENHANCEMENT for @Transactional to work. That means you must be enhancing the classes via IDE Plugin, Ant Task or javaagent. Refer to the user guide for more details on enhancement.

This means that we cannot be using the @Transactional annotation in the service layer ?

Service.java

@Override
@Transactional(type = TxType.REQUIRED)
public void method1(Product product1) {
removeProductAndAssociations(product1)
}

public void removeProductAndAssociations(Product product1) {
EbeanServer server = productDAO.getServer();
server.beginTransaction();
try {
productDAO.update(product1);
// further logic
productOptionsDAO.update(options1);
server.commitTransaction();
} finally {
server.endTransaction();
}
}


In case of 500, I see that it doesn't completely rollback... the Product model classes are enhanced by ebean, service classes are not. How would one best do nested transactions, so that even in case of multiple methods annotated with @Transactional(type = TxType.REQUIRED) that are callled, it joins the exisiting transaction and doesn't start a new one. On an error it should completely rollback. 

We are on Play 2.3.8, hence using 3.3.4 version of Ebean.
Let me know if there any other major changes to be aware of in regard to transaction management in the more recent version of Ebean ! It would help us evaluate if it is worth the upgrade !
Message has been deleted

Rob Bygrave

unread,
Jun 15, 2016, 3:48:50 PM6/15/16
to ebean@googlegroups
This means that we cannot be using the @Transactional annotation in the service layer ?

No. It means there are 2 types of enhancement that are done by the agent.  One type of enhancement is "entity bean enhancement" which enhances the entity beans for dirty checking and lazy loading.  The second type of enhancement the agent does is "transactional enhancement" ... where it finds classes with @Transactional (at class level or method level) and adds in the transactional support.

If you set the debug level on the enhancement it will log out what is being enhanced.  debug=1 for not much logging through to debug=9 for full noise.



 hence using 3.3.4 version of Ebean. Let me know if there any major changes to be aware of in the more recent version of Ebean ! 

Well yes, massive changes and more than I can list. There is an architectural jump from 3.x to 4.x with a change to the enhancement and treatment of partial objects (so effected most of the code base).

We are now on 7.13.1 ... so ElasticSearch integration, L2 caching plugins, DB Migration, SQL2011 History support via @History, @ChangeLog, @ReadAudit, @WhoCreated/@WhenModified, @Draftable etc.

Work backwards through https://github.com/ebean-orm/avaje-ebeanorm/releases ... but it is going to take awhile - 3.x is effective very very old now.

Another change in there though is how server.beginTransaction() works - in that before it didn't check for an existing transaction in scope but there was a request to change it so that it does.

Looking at your code which mixes @Transactional and server.beginTransaction() it looks like that is something you want (but it won't work like that in 3.x).

You should put on the SQL and Transaction logging as per: http://ebean-orm.github.io/docs/setup/logging (but perhaps the logging is a little different in 3.x - I really can't remember now).




Cheers, Rob.





On 16 June 2016 at 06:48, Pavan <ptalla...@recurly.com> wrote:

Hi Rob/Community 

I have found issues with getting the nested transactions to work with ebean. It will be of great help if you guys can kindly advice on how best to handle nested transactions ! 

Ebean transaction page (http://ebean-orm.github.io/docs/transactions/) says : You must be using ENHANCEMENT for @Transactional to work. That means you must be enhancing the classes via IDE Plugin, Ant Task or javaagent. Refer to the user guide for more details on enhancement.

This means that we cannot be using the @Transactional annotation in the service layer ?

Service.java

@Override
@Transactional(type = TxType.REQUIRED)
public void method1(Product product1) {
removeProductAndAssociations(product1)
}

public void removeProductAndAssociations(Product product1) {
EbeanServer server = productDAO.getServer();
server.beginTransaction();
try {
productDAO.update(product1);
// further logic
productOptionsDAO.update(options1);
server.commitTransaction();
} finally {
server.endTransaction();
}
}


In case of 500, I see that it doesn't completely rollback... the Product model classes are enhanced by ebean, service classes are not. How would one best do nested transactions, so that even in case of multiple methods annotated with @Transactional(type = TxType.REQUIRED) that are callled, it joins the exisiting transaction and doesn't start a new one. On an error it should completely rollback. 

--

---
You received this message because you are subscribed to the Google Groups "Ebean ORM" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ebean+un...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Rob Bygrave

unread,
Jun 15, 2016, 3:51:04 PM6/15/16
to ebean@googlegroups
Note that when you are using Play it is Play that is controlling when Ebean enhancement is applied.  

I'm not sure if they turn off @Transactional (hopefully not but there is that option).

Pavan

unread,
Jun 15, 2016, 4:41:27 PM6/15/16
to Ebean ORM
Thanks Rob for coming back on this ! Appreciate that

We are evaluating the upgrade to latest ebean version, but that also requires us to upgrade play to 2.5. 
The workaround of TxRunnable and TxCallable which are programmatic equivalents seems to be the approach for nested Tx where service layer is not enhanced.

Is there a better approach you recommend for handling nested Tx in non-enhanced classes ?





Rob Bygrave

unread,
Jun 15, 2016, 5:19:04 PM6/15/16
to ebean@googlegroups
The workaround of TxRunnable and TxCallable  ... Is there a better approach you recommend 

No, I think you are right with TxRunnable and TxCallable. 

In later versions we have:  ebeanServer.beginTransaction(TxScope scope); ... which is the alternative for the nested transaction use cases (in "try / finally style").  So yes, I think you  are right with TxRunnable and TxCallable. 


Cheers, Rob.


Pavan

unread,
Jun 15, 2016, 6:30:15 PM6/15/16
to Ebean ORM
Thank you very much Rob ! 

This piece : Another change in there though is how server.beginTransaction() works - in that before it didn't check for an existing transaction in scope but there was a request to change it so that it does.

So prior, if we manually begin a transaction, it wouldn't hook on to the already existing transaction and would start a new transaction . Is that what you meant ? 

Then this piece of nested TX would never work ? 

Service.java

@Override
public void method1(Product product1) {
server.beginTransaction();
try {
removeProduct(product1)
removeProductAssociations(productOption1)
server.commitTransaction();
} finally {
server.endTransaction();
}
}

public void removeProduct(Product product1) {
EbeanServer server = productDAO.getServer();
server.beginTransaction();
try {
productDAO.update(product1);
// business logic
server.commitTransaction();
} finally {
server.endTransaction();
}
}

public void removeProductAssociations(ProductOption productOption1) {
EbeanServer server = productOptionsDAO.getServer();
server.beginTransaction();
try {
productOptionsDAO.update(productOption1);
server.commitTransaction();
} finally {
server.endTransaction();
}
}

// So if an error occured in removeProductAssociations, t wouldn't rollback the update on the product ?

Rob Bygrave

unread,
Jun 15, 2016, 7:00:07 PM6/15/16
to ebean@googlegroups
So prior, if we manually begin a transaction, it wouldn't hook on to the already existing transaction and would start a new transaction . Is that what you meant ? 

Yes.



> Then this piece of nested TX would never work ? 

In 3.x it would not work (as you intended) - correct.  You can confirm that by turning on the transaction logging and throwing an error around productOptionsDAO.update(productOption1); ...




Pavan

unread,
Jun 20, 2016, 7:36:36 PM6/20/16
to eb...@googlegroups.com
Hi Rob

I have been trying out some permutations and combinations, to get the nested tx's to work ... The behavior when using TxRunnable approach seems to be working in few cases where a complete rollback happens and not in other cases. 

Below is a workflow which doesn't behave as expected : 


Following is a unit test which WORKS : 

public void testCreateRollback() {
EbeanServer ebeanServer = Ebean.getServer("default");
Integer testInt = null;
ebeanServer.beginTransaction();
try {
someDAO.create(someModel1);
// throw a 500
if (testInt == null) {
throw new NullPointerException("Something unexpected happened");
}
someDAO.create(someModel2);
ebeanServer.commitTransaction();
} catch (Exception e) {
Logger.info("testCreateRollback for Some Models threw exception.");
} finally {
ebeanServer.endTransaction();
}

SomeModel model1 = someDAO.server.find(SomeModel.class)
.where().eq("id", 1L).findUnique();
assertEquals(null, model1);
}

Result : Transaction actually rolled back and test is a success 

Following is a unit test which DOESN'T WORK ( As you can see, all I did was to swap out with TxRunnable Stuff) : 

 @Test
public void testCreateRollback() {
EbeanServer ebeanServer = Ebean.getServer("default");

Integer testInt = null;

ebeanServer.execute(new TxRunnable() {
@Override
public void run() {
try {
someDAO.create(someModel1);
if (testInt == null) {
throw new NullPointerException("Something unexpected happened");
}
someDAO.create(someModel2);
} catch (Exception e) {
Logger.info("testCreateRollback for Some Models threw exception.");
}
}
});

    SomeModel model1 = someDAO.server.find(SomeModel.class)
.where().eq("id", 1L).findUnique();
assertEquals(null, model1);
}

Result : Transaction actually  didn't roll back and test fails


We are on Play 2.3.8 and were using H2 Embedded DB for the tests in MySQL compat mode
   additionalConfig.put("db.default.driver", "org.h2.Driver");
additionalConfig.put("db.default.url", "jdbc:h2:mem:play;MODE=MYSQL");
additionalConfig.put("db.default.user", "sa");
additionalConfig.put("db.default.password", "");

Could you kindly advice what could be different between ebeanServer.beginTransaction() and using TxRunnable approach, that one would rollback and other doesn't ?

Rob Bygrave

unread,
Jun 21, 2016, 6:58:54 PM6/21/16
to ebean@googlegroups
Hi, I should get time to look at this tonight.

Cheers, Rob.

On 21 June 2016 at 11:36, Pavan <ptalla...@recurly.com> wrote:
Hi Rob

I have been trying out some permutations and combinations, to get the nested tx's to work ... The behavior when using TxRunnable approach seems to be working in few cases where a complete rollback happens and not in other cases. 


Pavan

unread,
Jun 22, 2016, 2:47:15 PM6/22/16
to Ebean ORM
Hey Rob

Thank you ! 

We found the issue. pretty rudimentary that we had a try catch block inside of the run method. so the exception would never propagate up to go into the catch block below in DefaultServer.

public void execute(TxScope scope, TxRunnable r) {
ScopeTrans scopeTrans = createScopeTrans(scope);
try {
r.run();

} catch (Error e) {
throw scopeTrans.caughtError(e);

} catch (RuntimeException e) {
throw scopeTrans.caughtThrowable(e);

} finally {
scopeTrans.onFinally();
}
} 

Rob Bygrave

unread,
Jun 22, 2016, 5:29:32 PM6/22/16
to ebean@googlegroups
Cool. Great to hear you're all good.

Cheers, Rob.

Pavan

unread,
Jun 22, 2016, 6:28:24 PM6/22/16
to Ebean ORM
All looked great till we hit another test, where we saw something new again :) Way of life :)

When we use a mock ebean server, the code inside the run method never gets executed ... 

Some code using mockito: 

@Before
public void setup() {
someDAO = mock(SomeDAO.class);
when(someDAO.getServer()).thenReturn(mock(EbeanServer.class));
}

@Test
public void check() {
apply()
//asserts
}

public void apply() {
    EbeanServer server = someDAO.getServer();
    // server being returned is a mock server ! 
TxScope txScope = TxScope
.
required()
.setIsolation(TxIsolation.
READ_COMMITED);

server.execute(
new TxRunnable() {
@Override
public void run() {
//
This code never gets called.
}
});

}

So with a mock EbeanServer, seems the behavior s different ! instantiating and using DefaultServer Implementation instead of mock seemed way off approach

Any thoughts you can kindly share ?

Rob Bygrave

unread,
Jun 22, 2016, 6:49:24 PM6/22/16
to ebean@googlegroups
> Thoughts

Ebean does provide a test double for EbeanServer:

- https://github.com/ebean-orm/avaje-ebeanorm-mocker   ... specifically DelegateEbeanServer  (and TDEbeanServer but that is not super useful)


- Has a series of testing videos at ... http://ebean-orm.github.io/videos so you can watch that to find out what DelegateEbeanServer is about.


My thought is that there is a desire to have a test double EbeanServer (like DelegateEbeanServer) that doesn't need/use an underlying database so transactions are test doubles ... but runs the TxRunnable / TxCallable code.

... and I don't think DelegateEbeanServer does that at the moment (but perhaps it should).


Pavan

unread,
Jun 23, 2016, 1:29:00 PM6/23/16
to Ebean ORM

Thanks Rob, couldn't get the mockibean to work, but found a workaround for now with Mockito to run anonymous chunks : 

Ex. 

EbeanServer ebeanServer =  mock(EbeanServer.class);
when(someDAO.getServer()).thenReturn(ebeanServer);
doAnswer((InvocationOnMock invocation) -> {
Object[] args = invocation.getArguments();
TxRunnable arg = (TxRunnable)args[0];
arg.run();
return arg;
}).when(ebeanServer).execute(Mockito.<TxRunnable>any());

Rob Bygrave

unread,
Jun 23, 2016, 3:21:15 PM6/23/16
to ebean@googlegroups
Nice little snippet of code - thanks!!
Reply all
Reply to author
Forward
0 new messages