Nested Transactions?

561 views
Skip to first unread message

Robert Simmons

unread,
Oct 29, 2014, 12:18:48 PM10/29/14
to eb...@googlegroups.com
Greetings, I am using Ebeans 3.3.4 inside of Play Framework and I am looking for nested transaction behavior for re-entrant functionality. Essentially what I want to do is make a service where I have many methods and then centralize transaction management. Assume the management function handles all transaction work and looks something like this in scala: 

withLock[A](userId: Long)(f: => A): A = {
try {
Ebean.beginTransaction()
val result = f
Ebean.commitTransaction()
result
} finally {
Ebean.endTransaction() // will rollback the transaction if it is not committed
}
}

Or Java its a bit more verbose in declaration and use: 

public <A> A withTxn(final Supplier<? extends A> f){
try {
Ebean.beginTransaction();
final A result = f.get();
Ebean.commitTransaction();
return result;
} finally {
Ebean.rollbackTransaction();
}
}


Such re-usable functions are very useful for making code more maintainable.The problem is when I have a function that calls another function these blocks of code will be re-enterd and a new beginTransaction will be called. From my point of view this is a good thing as I have atomicity in that either things work or fail and I can deal with failures in sub transactions without killing the whole main transaction. 

The problem is Ebeans is throwing a PersistenceException, specifically: "javax.persistence.PersistenceException: The existing transaction is still active?" So am I to gather that nested transactions are not allowed? Is there something I am missing? Some technique around this problem? 

Thanks in advance. 

Rob Bygrave

unread,
Oct 29, 2014, 4:19:34 PM10/29/14
to ebean@googlegroups

So in Ebean and EbeanServer there are the 'execute' [in a transaction] methods that take a TxScope:

execute(TxScope scope, TxCallable callable)
execute(TxScope scope, TxRunnable callable)


These methods are similar to the try finally block approach except they support controlling how nested transactions work.  TxScope has TxType's of REQUIRED, REQUIRES_NEW, MANDATORY, SUPPORTS, NOT_SUPPORTED, NEVER  (plus defining exceptions to rollbackFor, noRollbackFor, transaction isolation level and readOnly).


... if you want "Requires New" then you'd so something like:

ebeanServer.execute(TxScope.requiresNew(), {
  // closure
});


Notes:
- You could also use the @Transactional annotation to achieve the same result.
- Ebean.beginTransaction() can't do 'nested transactions' 
- In your finally block you should use Ebean.endTransaction() and not Ebean.rollbackTransaction()

Also note that the 'behaviour' to watch for is when you have the same transaction with nested 'transactional methods' and where you have RuntimeExceptions thrown in the child method and caught in a try catch block of the outer method (as sometimes you don't get the behaviour that you were looking for).  I'll look to find or create an example of this scenario because I think it can get confusing there (what exceptions cause a rollback and when).


Does that help answer your question?


Cheers, Rob.


--

---
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.

Robert Simmons

unread,
Oct 30, 2014, 12:14:50 PM10/30/14
to eb...@googlegroups.com
To some extent you have answered the question, thanks. However, I can't render our entire system as runnables. Not only would the engineering effort be massive, it would have little ROI to the business. However the question is what are the requirements for Ebeans to use those transactional annotations. A lot of our methods are just simply methods on the model objects. As a contrived example: 

class Account {
@Id
@Column(name = "id")
var id: Long = 0L

@Column(name = "usd_cents", nullable = false)
var usdCents: Long = 0L

def deposit(cents: Long): Unit = {
require(cents > 0)
Ebean.beginTransaction()
try {
usdCents += cents
} finally {
Ebean.endTransaction()
}
}
}


Now if I put the annotation on the deposit method, would that invoke the transactional behavior I am searching for and if so then would that allow me to remove the manual transaction handling from the method?

Thanks in advance. 

-- Robert

Rob Bygrave

unread,
Nov 2, 2014, 3:16:04 PM11/2/14
to ebean@googlegroups
I think I'm missing something with this example.

- The deposit method doesn't do a save() on the bean so that transaction doesn't do anything that I see

- If you ever do a save() or update() or insert() or delete() ... then Ebean will detect if an existing transaction is active and if there is no a current active transaction it will automatically create and commit (or rollback) a transaction for you.  In this deposit() method case it actually seems that is more what you want (in which case just remove the transaction demarcation code you have).  If you want transaction REQUIRES_NEW type functionality then this is not the way to do that (using Ebean.beginTransaction()) ... you need to pass in the 'requires new' TxScope.

At this stage I think I am misunderstanding what you are looking for in that I'd expect you to save the bean in the deposit method.


Cheers, Rob.




Reply all
Reply to author
Forward
0 new messages