Strange behaviour with #Transactional(propagation=Propagation.REQUIRES_NEW) - java

Here is my problem :
I'm running a batch on a Java EE/Spring/Hibernate application. This batch calls a method1. This method calls a method2 which can throw UserException (a class extending RuntimeException). Here is how it looks like :
#Transactional
public class BatchService implements IBatchService {
#Transactional(propagation=Propagation.REQUIRES_NEW)
public User method2(User user) {
// Processing, which can throw a RuntimeException
}
public void method1() {
// ...
try {
this.method2(user);
} catch (UserException e) {
// ...
}
// ...
}
}
The exception is catched as the execution continues, but at the end of method1 when the transaction is closed a RollbackException is thrown.
Here is the stack trace :
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:476)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at $Proxy128.method1(Unknown Source)
at batch.BatchController.method1(BatchController.java:202)
When method2 is not throwing this exception, it works well.
What I have tried:
Setting #Transactional(noRollbackFor={UserException.class})) on method1
Try and catch in method2
But it didn't change anything.
As the exception is thrown in a different transaction where a rollback happened I don't understand why it doesn't work. I had a look at this : Jpa transaction javax.persistence.RollbackException: Transaction marked as rollbackOnly but it didn't really help me.
I will be very greatful if someone could give me a clue.
Update
I've made it work by setting propagation=Propagation.REQUIRES_NEW on the method called by method2 (which is actually the one which is sending the exception). This method is defined in a class very similar to my BatchService. So I don't see why it works on this level and not on method2.
I've set method2 as public as the annotation #Transactional is not taken into account if the method is private as said in the documentation :
The #Transactional annotation may be placed before an interface
definition, a method on an interface, a class definition, or a public
method on a class.
I also tried to use Exception instead of RuntimeException (as it is more appropriate) but it also didn't change anything.
Even if it is working the question remains open as it has a strange behaviour and I would like to understand why it's not acting like it should be.

Spring transactions, by default, work by wrapping the Spring bean with a proxy which handles the transaction and the exceptions. When you call method2() from method1(), you're completely bypassing this proxy, so it can't start a new transaction, and you're effectively calling method2() from the same transaction as the one opened by the call to method1().
On the contrary, when you call a method of another injected bean from method1(), you're in fact calling a method on a transactional proxy. So if this alien method is marked with REQUIRES_NEW, a new transaction is started by the proxy, and you're able to catch the exception in method1() and resume the outer transaction.
This is described in the documentation.

Related

Spring UnexpectedRollbackException in nested #Transactional method

I have a dao class (MyDao) which is marked with #Transactional annotaion (on class level) with no additional parameters. In this dao class I have a method which in some case needs to throw a checked exception and perform a transaction rollback. Something like this:
#Transactional
public class MyDao {
public void daoMethod() throws MyCheckedException() {
if (somethingWrong) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw new MyCheckedException("something wrong");
}
}
This works perfectly fine. However, this dao method is called from a service method, which is also marked as #Transactional:
public class MyService {
#Autowired
private MyDao myDao;
#Transactional
public void serviceMethod() throws MyCheckedException {
myDao.daoMethod();
}
}
The problem is that when daoMethod() is called from serviceMethod() and marks the transaction as rollback only, I get an UnexpectedRollbackException.
Under the hood, Spring creates two transactional interceptors: one for MyDao and one for MyService. When daoMethod() marks the transaction for rollback, the interceptor for MyDao performs the rollback and returns. But then the stack moves to the interceptor for MyService, which finds out about the previous rollback, and throws the UnexpectedRollbackException.
One solution would be to remove the #Transactional annotation from MyDao. But this is not feasible right now because MyDao is used at a lot of places and this could cause errors.
Another solution would be to not set the transaction as rollback only in daoMethod() but rather mark serviceMethod() to revert the transaction on MyCheckedException. But I don't like this solution because I have a lot of these "service methods" and I would have to mark all of them explicitly to rollback on that exception. Also everyone who would add a new service method in the future would have to think of this and therefore it creates opportunities for errors.
Is there a way I could keep setting the transaction as rollback only from daoMethod() and prevent Spring from throwing the UnexpectedRollbackException? For instance, with some combination of parameters isolation and propagation?
I figured it out. I have to explicitly tell also the "outer" interceptor, that I want to rollback the transaction. In other words, both interceptors need to be "informed" about the rollback. This means either catching MyCheckedException in serviceMethod() and setting the transaction status to rollback only, or to mark serviceMethod() like this #Transactional(rollbackFor=MyCheckedException.class).
But as I mentioned in the OP, I want to avoid this because it's prone to errors. Another way is to make #Transactional rollback on MyCheckedException by default. But that's a completely different story.
Throwing an exception inside the transaction already trigger a rollback, using setRollbackOnly() is redundant here and that's probably why you have that error.
If the Transactionnal on the DAO is set up with Propagation.REQUIRED which is the default, then it will reuse an existing transaction if there is already one or create one if there is none. Here it should reuse the transaction created at the service layer.

Transaction not rollbacked

I have a set of operation i would like to be rollbacked if there are an error.
My class
public class BSException extends RuntimeException{
...
}
public class saleFacade{
public update(){
for (){
try{
renewSale();
}
catch(BSException){
logger.error();
}
}
}
#Transactional
public renewSale(){
try{
findSale(); // read only Transactional
xxx.renewSpecialSale();
}
catch(Exception e){
logger.error(...);
}
}
}
public class xxx(){
public void renewSpecialSale(){
payFee(); //write to db
if(error){
throw new BSException();
}
}
#Transactional(propagation = Propagation.REQUIRED)
public payFee(){
try{
...
}
catch(BsException e){
...
}
catch(Exception e){
...
}
}
}
#Configuration
#EnableTransactionManagement
public class DBConfiguration{
#Bean(name = "dataSource")
public BasicDataSource dataSource(){
...
}
}
Inn renewSpecialSale error is throw.
In the renewSale method, if there is an error, i would like to rollback.
Right now nothing is rollbacked
any idea?
If you catch the exception before it leaves the method, then there is no way the proxy wrapping the method can know that an exception was thrown.
Either remove the try-catch entirely or rethrow the exception so that the exception can leave the method that you marked #Transactional (and get intercepted by the proxy), and the rollback will take place.
I recommend removing exception-handling from all these methods. Set up a central exception handler so that anything thrown from the controllers gets caught and logged, and otherwise let exceptions get thrown.
Make sure each of these classes that does something transactional is annotated separately, if you annotate on the method-level then each method that does something transactional should be annotated. Calling a transactional method from a non-transactional method on the same object doesn't go through the proxy (the proxy intercepts only those calls coming in from outside the object, and only then if that method is marked as transactional) so it isn't part of a transaction (+1 to Peter's answer for pointing this out).
I have no idea what your error flag is doing, it seems odd. Spring services shouldn't have state. If you fix the exception-handling you shouldn't need error flags.
The problem comes from your nesting of Method calls and the usage of #Transactional. By default Springs Transaction Management
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, will not lead to an actual transaction at runtime even if the invoked method is marked with #Transactional. Spring Transaction Management
This means, that the call of
xxx.payFee()
is not surrounded by a transaction when it's called through
saleFacade.update() -> saleFacade.renewSale() -> xxx.renewSpecialSale()
As far as I got it, you have at least these options
Mark xxx.renewSpecialSale() as #Transactional
Mark saleFacade.update() as #Transactional
Mark both classes xxx and saleFacade #Transactional
Also you can create your own custom exception class and throw it.
Just throw any RuntimeException from a method marked as #Transactional.
By default all RuntimeExceptions rollback transaction whereas checked exceptions don't. This is an EJB legacy. You can configure this by using rollbackFor() and noRollbackFor() annotation parameters:
#Transactional(rollbackFor=Exception.class)
This will rollback transaction after throwing any exception.
#Transactional(rollbackFor = MyCheckedException.class)
public void foo() {
throw new RuntimeException();
}
remove try catch block from your method and put below line of code
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
use this line of code insted of
#Transactional(propagation = Propagation.REQUIRED)
In above code spring container handle whole transaction management and here if we provide rollback attribute it automatically manage if any kind of exception occur it will rollback your transaction
and make sure that you have entry of transaction in your configuration file as below
<tx:annotation-driven />
and also
I hope it will sure help you

Spring #Transactional anotation questions

I have two methods both anototates as #Transactional, and both performing some database operations, mostly insert.
I invoke methodB from methodA
#Transactional
public void methodA(){
methodB();
// Some database Operations
// some error conditions....
throw exception;
}
#Transactional
public void methodB(){
// Some database Operations
}
So I want to commit the database transactions for MethodA and MethodB only if all operations are successful.
So will changes done in methodB still be commited even if an exception is encountered in methodA.
I want changes of MethodB to rollback if error is encountered in methodA.
Do I need to do something else.
All the methods called within methodA() will be transactional, as the #Transactional annotation has a propagation of required by default. So #Transactional in the methodB() is not exactly required.
When methodB gets called from methodA, it does not create a new transaction as you are not calling the spring proxy that sorts out all the transaction creation and rollback, so the inserts from methodA AND methodB will be executed in the same transaction.
So no, you don't need to do anything else...

Nested Transaction and EJBContext's setRollbackOnly()

I am reading the Transaction Management of Java EE 7 and I get confused by the concept of nested transaction and the functionality of EJBContext#setRollbackOnly().
Say I have two Session Beans, Bean1Impl and Bean2Impl and their signatures are:
#Stateless
#TransactionManagement(TransactionManagementType.CONTAINER)
public class Bean1Impl implements Bean1 {
#Resource
private EJBContext context;
#TransactionAttribute(REQUIRED)
public void method1() {
try {
//some operations such as persist(), merge() or remove().
}catch(Throwable th){
context.setRollbackOnly();
}
}
}
#Stateless
#TransactionManagement(TransactionManagementType.CONTAINER)
public class Bean2Impl implements Bean2 {
#Resource
private EJBContext context;
#TransactionAttribute(REQUIRED)
public void method2() {
try {
//some operations such as persist(), merge() or remove().
//an exception has been thrown
}catch(Throwable th){
context.setRollbackOnly();
}
}
}
As stated in Java EE 7 Tutorial:
51.3.1.1 Required Attribute
If the client is running within a transaction and invokes the enterprise bean's method, the method
executes within the client's transaction. If the client is not
associated with a transaction, the container starts a new transaction
before running the method.
The Required attribute is the implicit transaction attribute for all
enterprise bean methods running with container-managed transaction
demarcation. You typically do not set the Required attribute unless
you need to override another transaction attribute. Because
transaction attributes are declarative, you can easily change them
later.
In this case I don't need to specify #TransactionAttribute(REQUIRED) annotation declaration in the methods Bean1Impl#method1() and Bean2Impl#method2(). Am I right?
So in the above code the transaction of Bean2Impl#method2() would be running within the transaction of Bean1Impl#method1().
Can I consider it as a Nested Transaction?
If there is an Exception has been thrown inside the method Bean2Impl#method2() which eventually would lead to a call to the method EJBContext.setRollbackOnly() from the catch block and as expected it should roll back the operations performed within the try block of this method. In this case what would happen to the transaction and as well as the Bean1Impl#method1(). Would it be rolled back as well? What I mean is:
What would happen if there is a call of EJBContext.setRollbackOnly() from Bean2Impl#method2() and
Bean2Impl#method2() is called from the method Bean1Impl#method1() before any database operation like persist, merge or remove.
Bean2Impl#method2() is called from the method Bean1Impl#method1() after any database operation like persist, merge or remove.
And lastly what would happen if the method Bean2Impl#method2() get executed successfully but EJBContext.setRollbackOnly() is called from Bean1Impl#method1() after successful return of Bean2Impl#method2()?
To add to #Philippe Marshall's correct answer and your comment - REQUIRES_NEW will create a new transaction, independent from the first one. They are not nested. The first transaction is suspended while the second is active. Once the second transaction commits, the first one is resumed.
You do not have to setRollbackOnly() manually. Most PersistenceExceptions will do that if needed. You will gain nothing by rolling back transactions that you don't have to. For example, when querying data, you may get a NoResultException or a NonUniqueResultException. They do not cause a transaction to be rolled back as there is no risk of inconsistencies between the persistence context and the DB.
You do not need to specify neither #TransactionAttribute(REQUIRED) nor #TransactionManagement(TransactionManagementType.CONTAINER)- both are the default settings.
EDIT: to answer your further questions:
I am assuming #TransactionAttribute(REQUIRES_NEW) on method2 and therefore two separate transactions.
If there is an Exception that leads to the rollback of the transaction in method2, the transaction from method1 will not be rolled back if the Exception is caught. If the Exception is not caught, both transactions will be rolled back.
When setting the rollback flag on a transaction, it does not matter whether it happens before or after DB operations, since the entire transaction is rolled back.
Once method2 returns, it's transaction is committed. Rolling back or committing the transaction from method1 afterwards has no influence on the results of the first transaction.
A general advice - do not catch Throwable - it is much too broad and you might swallow exceptions which you would rather let propagate to the surface.
This is not nested transactions, JavaEE / JTA does not support nested transaction. If #method2() is called from #method1() it runs in the same transaction. If you want to have a different transaction you need #REQUIRES_NEW. EJBContext.setRollbackOnly() only works on the current transaction. Note there is a chance that after calling EJBContext.setRollbackOnly() all operations on the transactional resource including reads will throw an exception (JBoss AS 5.1 did this, don't know the current behaviour).
Update:
}catch(Throwable th){
context.setRollbackOnly();
}
You don't need this for runtime exceptions, this is the default behaviour of EJBs.

Seam #Transactional annotation not working?

I'm using the #Transactional annotation on a seam component similar to:
#Name( "myComponent" )
#AutoCreate
public class MyComponent
{
public void something() {
...
doWork();
}
...
#Transactional
protected void doWork() {
try {
log.debug( "transaction active: " + Transaction.instance().isActive() );
} catch (Exception ignore) {}
// some more stuff here that doesn't appear to be inside a transaction
}
}
In the "some more stuff" section, I'm modifying some Hibernate entities and then had a bug where an Exception was thrown. I noticed that the Exception wasn't causing the transaction to be rolled back (the modified entities were still modified in the db) so I added the "transaction active" logging. When this code executes, isActive() returns false.
Is there something I'm missing? Why isn't the transaction active?
In case it matters, I'm using the Seam component from inside another component that is using RESTEasy annotations to trigger my method calls.
I'm not familiar with how Seam works so my apologies in advance if this answer does not apply.
I noticed that the method that is #Transactional is protected. This implies to me that it is being called by another internal method.
With Spring's AOP, you mark the public methods with #Transactional which are wrapped and replaced with a transaction proxy. When an external class calls the public method, it is calling the proxy which forms the transaction. If the external class calls another public method that is not marked with #Transactional which then calls an internal method which is, there will be no transaction created because the proxy is not being called at all.
In Spring, even if you change your doWork() method to be public, the same problem would happen. No transaction because the proxy object is not being called. Method calls made inside of the class are not making calls to the proxy object.
A quick read of some documentation seems to indicate that, like Spring AOP, Seam is using CGLib proxying. The question is if it is able to proxy all methods -- even if they are called from within the proxied object. Sorry for wasting your time if this answer does not apply.

Categories