Spring transaction not rollbacked when an expcetion happen - java

I use spring 3.2 and have some transactions. When i get an exception, the rollback don't seem to be done.
My code
public class x{
#Transactional
public createX(){
try{
...
y.createY();
...
}
catch(Exception e){
....
}
}
}
public class y{
#Transactional
public createY(){
...
callYY();
...
}
#Transactional(propagation = Propagation.REQUIRED)
public void callYY(){
...
throw new Exception();
}
}
#Configuration
#EnableTransactionManagement
public class Configuration {
}
Basicaly, i have a class X, createX method start a transaction. It call createY who call callYY.
In this method an exception happen.
I was thinking then all the persistent operation since the createX would be rollbacked but it's not that who happen
I don't see transaction info in the log
any idea

Try define exception which cause rollback, for example:
#Transactional(rollbackFor = {Throwable.class, Exception.class})

Propagation.REQUIRED (which is default) means that no transaction opened in case of an open transaction exists.
That means that the transaction actually being opened upon calling x.createX method and nothing is done (in terms of transaction treatment) upon calling y.callY and y.callYY methods.
However you catch the exception and it doesn't reach the Spring transaction interceptor defined on x.createX method, which should translate it into the rollback.
So if x.createX don't have to to be transactional, removing #Transactional from it will make the rollbacks to happen.

It is very simple. You catch exception in method createX. If you want rollback you can't catch exception in transaction. To rollback transaction you have to throw exception without catch.

You never mention your tranaction manager. You will need to either define a transaction manager in your application context xml...
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
OR as an annotated declaration on your Bean Configuration class...
#Configuration
#EnableTransactionManagement()
public class MyConfiguration {...}

Related

Using #Transactional on service method, which updates multiple repositories

Spring boot, postgres, spring jpa.
Have a service, which is trying to store changes across multiple repositories:
class Service {
#Transactional
public void doStuff() {
repo1.delete(...);
repo2.saveAll(...);
repo1.save(...);
}
}
This operation requires to be rolled back if anything fails.
Here I struck into two things:
If I add a throw RuntimeException somewhere in the middle of that method, all things before it don't get rolled back.
In regular flow I get
Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
My configuration is:
#Configuration
#EnableTransactionManager
#EntityScan
#EnableJpaRepositories
public class DataConfig {}
Also trying to use the TransactionTemplate bean with it's execute method. Manage to overcome the first issue, but still fail with the second one.
You can try by adding #Transactional(rollbackFor = Exception.class) this will rollback even if you have an exception in your code
class Service {
#Transactional(rollbackFor = Exception.class)
public void doStuff() {
repo1.delete(...);
repo2.saveAll(...);
repo1.save(...);
}
}

Handling exceptions during a #Transactional method in Spring

I am trying to figure out how to best handle persistence (and potentially other) exceptions in combination with Spring's #Transactional.
For this post I am just going to take the simple example of a user registration, which can cause DataIntegrityViolationException due to duplicate username.
The following things I have tried and they are not really satisfactory to me:
1. Naive approach: Just catch the Exception
val entity = UserEntity(...)
try {
repo.save(entity)
} catch (e: DataIntegrityViolationException) {
// not included: some checks for which constraint failed
throw DuplicateUsername(username) // to be handled by the controller
}
This does not work when in a #Transactional method, since the persistence exceptions won't happen until the transaction is commited, which happens outside my service method in the spring transaction wrapper.
2. Flush the EntityManager before exiting
Explicitly call flush on the EntityManager at the end of my service methods. This will force the write to the database and as such trigger the exception. However it is potentially inefficient, as I now must take care to not flush multiple times during a request for no reason. I also better not ever forget it or exceptions will disappear into thin air.
3. Make two service classes
Put the #Transactional methods in a separate spring bean and try-catch around them in the main service. This is weird, as I must take care to do one part of my code in place A and the other in place B.
4. Handle DataIntegrityViolationException in the controller
Just... no. The controller has no business (hue hue hue) in handling exceptions from the database.
5. Don't catch DataIntegrityViolationException
I have seen several resources on the web, especially in combination with Hibernate, suggesting that catching this exception is wrong and that one should just check the condition before saving (i.e. check if the username exists with a manual query). This does not work in a concurrent scenario, even with a transaction. Yes, you will get consistency with a transaction, but you'll still get DataIntegrityViolationException when "someone else comes first". Therefor this is not an acceptable solution.
7. Do not use declarative transaction management
Use Spring's TransactionTemplate instead of #Transactional. This is the only somewhat satisfactory solution. However it is quite a bit more "clunky" to use than "just throwing #Transactional on the method" and even the Spring documentation seems to nudge you towards using #Transactional.
I would like some advice about how to best handle this situation. Is there a better alternative to my last proposed solution?
I use the following approach in my project.
Custom annotation.
public #interface InterceptExceptions
{
}
Bean and aspect at spring context.
<beans ...>
<bean id="exceptionInterceptor" class="com.example.ExceptionInterceptor"/>
<aop:config>
<aop:aspect ref="exceptionInterceptor">
<aop:pointcut id="exception" expression="#annotation(com.example.InterceptExceptions)"/>
<aop:around pointcut-ref="exception" method="catchExceptions"/>
</aop:aspect>
</aop:config>
</beans>
import org.aspectj.lang.ProceedingJoinPoint;
public class ExceptionInterceptor
{
public Object catchExceptions(ProceedingJoinPoint joinPoint)
{
try
{
return joinPoint.proceed();
}
catch (MyException e)
{
// ...
}
}
}
And finally, usage.
#Service
#Transactional
public class SomeService
{
// ...
#InterceptExceptions
public SomeResponse doSomething(...)
{
// ...
}
}
Voted to (3),like:
#Service
public class UserExtService extend UserService{
}
#Service
public class UserService {
#Autowired
UserExtService userExtService;
public int saveUser(User user) {
try {
return userExtService.save(user);
} catch (DataIntegrityViolationException e) {
throw DuplicateUsername(username);// GlobalExceptionHandler to response
}
return 0;
}
#Transactional(rollbackFor = Exception.class)
public int save(User user) {
//...
return 0;
}
}
You can use a class annotated with #ControllerAdvice or #RestControllerAdvice to handle the exceptions
When a controller throw a exception you can catch it at this class and change the response status to a suitable one or add an extra info of the exception
This method helps you to maintain a clean code
You have numerous examples:
https://www.javainuse.com/spring/boot-exception-handling
https://dzone.com/articles/best-practice-for-exception-handling-in-spring-boo

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

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

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.

Categories