Transaction is not getting rolled back though exception is thrown - java

Currently we are facing a spring transaction related issue in our application.
As you can see that in deleteRecord() we are doing a DB operation. But in the next line
a business exception is thrown.
Expected Behavior(As per my knowledge) :
The DB operation should be rolled back as exception is thrown from the next line
Actual Behavior :
Its not getting rolled back. Data is getting inserted to the table
Question :
Why transaction is not getting rolled back ? I dont think its because of the catch block
because deleteRecord() will be executed in new transaction. Please correct me if I am wrong
Code:
class A {
void myMethod() {
for(int i=0 ; i<count ; i++) {
try {
deleteRecord();
} catch(Exception e) {
log.error("Exception caught");
}
}
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
deleteRecord() throws Exception{
line 1 : deleting record
line 2 : Throwing business exception
}
}

The Spring documentation says the following:
While the EJB default behavior is for the EJB container to automatically roll back the transaction on a system exception (usually a runtime exception), EJB CMT does not roll back the transaction automatically on an application exception (that is, a checked exception other than java.rmi.RemoteException). While the Spring default behavior for declarative transaction management follows EJB convention (roll back is automatic only on unchecked exceptions), it is often useful to customize this.
And
In its default configuration, the Spring Framework’s transaction
infrastructure code only marks a transaction for rollback in the case
of runtime, unchecked exceptions; that is, when the thrown exception
is an instance or subclass of RuntimeException. ( Errors will also -
by default - result in a rollback). Checked exceptions that are thrown
from a transactional method do not result in rollback in the default
configuration
see in 16.5.3: https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html
This says that the default behavior of the transaction will only rollback for RuntimeExceptions. If you have a own business exception (could be a checked excpetion), you have to explicitly name the exception class the transaction should rollback for:
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = YOUREXCEPTION.class)

change to
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)

It is because of #Transaction method call by the method within the same class, does not work if you do not configured Spring to use AspectJ.
Spring #Transaction method call by the method within the same class, does not work?

Related

Cannot commit transactions in the exception block in JPA [duplicate]

I'm using Spring and Hibernate in one of the applications that I'm working on and I've got a problem with handling of transactions.
I've got a service class that loads some entities from the database, modifies some of their values and then (when everything is valid) commits these changes to the database. If the new values are invalid (which I can only check after setting them) I do not want to persist the changes. To prevent Spring/Hibernate from saving the changes I throw an exception in the method. This however results in the following error:
Could not commit JPA transaction: Transaction marked as rollbackOnly
And this is the service:
#Service
class MyService {
#Transactional(rollbackFor = MyCustomException.class)
public void doSth() throws MyCustomException {
//load entities from database
//modify some of their values
//check if they are valid
if(invalid) { //if they arent valid, throw an exception
throw new MyCustomException();
}
}
}
And this is how I invoke it:
class ServiceUser {
#Autowired
private MyService myService;
public void method() {
try {
myService.doSth();
} catch (MyCustomException e) {
// ...
}
}
}
What I'd expect to happen: No changes to the database and no exception visible to the user.
What happens: No changes to the database but the app crashes with:
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction;
nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
It's correctly setting the transaction to rollbackOnly but why is the rollback crashing with an exception?
My guess is that ServiceUser.method() is itself transactional. It shouldn't be. Here's the reason why.
Here's what happens when a call is made to your ServiceUser.method() method:
the transactional interceptor intercepts the method call, and starts a transaction, because no transaction is already active
the method is called
the method calls MyService.doSth()
the transactional interceptor intercepts the method call, sees that a transaction is already active, and doesn't do anything
doSth() is executed and throws an exception
the transactional interceptor intercepts the exception, marks the transaction as rollbackOnly, and propagates the exception
ServiceUser.method() catches the exception and returns
the transactional interceptor, since it has started the transaction, tries to commit it. But Hibernate refuses to do it because the transaction is marked as rollbackOnly, so Hibernate throws an exception. The transaction interceptor signals it to the caller by throwing an exception wrapping the hibernate exception.
Now if ServiceUser.method() is not transactional, here's what happens:
the method is called
the method calls MyService.doSth()
the transactional interceptor intercepts the method call, sees that no transaction is already active, and thus starts a transaction
doSth() is executed and throws an exception
the transactional interceptor intercepts the exception. Since it has started the transaction, and since an exception has been thrown, it rollbacks the transaction, and propagates the exception
ServiceUser.method() catches the exception and returns
Could not commit JPA transaction: Transaction marked as rollbackOnly
This exception occurs when you invoke nested methods/services also marked as #Transactional. JB Nizet explained the mechanism in detail. I'd like to add some scenarios when it happens as well as some ways to avoid it.
Suppose we have two Spring services: Service1 and Service2. From our program we call Service1.method1() which in turn calls Service2.method2():
class Service1 {
#Transactional
public void method1() {
try {
...
service2.method2();
...
} catch (Exception e) {
...
}
}
}
class Service2 {
#Transactional
public void method2() {
...
throw new SomeException();
...
}
}
SomeException is unchecked (extends RuntimeException) unless stated otherwise.
Scenarios:
Transaction marked for rollback by exception thrown out of method2. This is our default case explained by JB Nizet.
Annotating method2 as #Transactional(readOnly = true) still marks transaction for rollback (exception thrown when exiting from method1).
Annotating both method1 and method2 as #Transactional(readOnly = true) still marks transaction for rollback (exception thrown when exiting from method1).
Annotating method2 with #Transactional(noRollbackFor = SomeException) prevents marking transaction for rollback (no exception thrown when exiting from method1).
Suppose method2 belongs to Service1. Invoking it from method1 does not go through Spring's proxy, i.e. Spring is unaware of SomeException thrown out of method2. Transaction is not marked for rollback in this case.
Suppose method2 is not annotated with #Transactional. Invoking it from method1 does go through Spring's proxy, but Spring pays no attention to exceptions thrown. Transaction is not marked for rollback in this case.
Annotating method2 with #Transactional(propagation = Propagation.REQUIRES_NEW) makes method2 start new transaction. That second transaction is marked for rollback upon exit from method2 but original transaction is unaffected in this case (no exception thrown when exiting from method1).
In case SomeException is checked (does not extend RuntimeException), Spring by default does not mark transaction for rollback when intercepting checked exceptions (no exception thrown when exiting from method1).
See all scenarios tested in this gist.
For those who can't (or don't want to) setup a debugger to track down the original exception which was causing the rollback-flag to get set, you can just add a bunch of debug statements throughout your code to find the lines of code which trigger the rollback-only flag:
logger.debug("Is rollbackOnly: " + TransactionAspectSupport.currentTransactionStatus().isRollbackOnly());
Adding this throughout the code allowed me to narrow down the root cause, by numbering the debug statements and looking to see where the above method goes from returning "false" to "true".
As explained #Yaroslav Stavnichiy if a service is marked as transactional spring tries to handle transaction itself. If any exception occurs then a rollback operation performed. If in your scenario ServiceUser.method() is not performing any transactional operation you can use #Transactional.TxType annotation. 'NEVER' option is used to manage that method outside transactional context.
Transactional.TxType reference doc is here.
Save sub object first and then call final repository save method.
#PostMapping("/save")
public String save(#ModelAttribute("shortcode") #Valid Shortcode shortcode, BindingResult result) {
Shortcode existingShortcode = shortcodeService.findByShortcode(shortcode.getShortcode());
if (existingShortcode != null) {
result.rejectValue(shortcode.getShortcode(), "This shortode is already created.");
}
if (result.hasErrors()) {
return "redirect:/shortcode/create";
}
**shortcode.setUser(userService.findByUsername(shortcode.getUser().getUsername()));**
shortcodeService.save(shortcode);
return "redirect:/shortcode/create?success";
}

Transaction RollBack after catching exception

Is there any way to rollback the transaction after catch the exception using declarative transaction management. I have this piece of code.
#Component
#Transactional(rollbackFor = EvictionException.class)
Public class Eviction{
#Autowired
private Alerter alerter;
#Scheduled(cron = "${evictor.cron.expression}")
public void evictObjectFromDatabase(){
try{
....
DO SOME DELETION QUERIES
}catch(Exception ex){
alerter.produceAlert("Failed to delete entries from database");
}
}
}
If a exception is produced while deletion, i need to raise an alert which another team monitors of a swing UI. Also i need to rollback the transaction but using the rollBackFor = Exception.class does not works.
You need to annotate your method with #Transactional(rollbackFor = Exception.class) and in catch block throw the exception(So that transactional proxy can detect the exception and Hence rollback) E.g.
try{
....
DO SOME DELETION QUERIES
}catch(Exception ex){
alerter.produceAlert("Failed to delete entries from database");
throw ex;// this is important
}
#Ambuj Jauhari if you look into the documentation of #transactional the roll back occurs either for error or runtime exception.Most probably in your case roll back is happening when you are not providing rollback for exception .class because exception being thrown is of runtime exception and not any checked exception.If the type of exception thrown had been any checked exception then roll back wouldn't have happened if you wouldn't have provided rollback for exception.class attribute
use SqlTransaction and RollBack

Could not commit JPA transaction: Transaction marked as rollbackOnly

I'm using Spring and Hibernate in one of the applications that I'm working on and I've got a problem with handling of transactions.
I've got a service class that loads some entities from the database, modifies some of their values and then (when everything is valid) commits these changes to the database. If the new values are invalid (which I can only check after setting them) I do not want to persist the changes. To prevent Spring/Hibernate from saving the changes I throw an exception in the method. This however results in the following error:
Could not commit JPA transaction: Transaction marked as rollbackOnly
And this is the service:
#Service
class MyService {
#Transactional(rollbackFor = MyCustomException.class)
public void doSth() throws MyCustomException {
//load entities from database
//modify some of their values
//check if they are valid
if(invalid) { //if they arent valid, throw an exception
throw new MyCustomException();
}
}
}
And this is how I invoke it:
class ServiceUser {
#Autowired
private MyService myService;
public void method() {
try {
myService.doSth();
} catch (MyCustomException e) {
// ...
}
}
}
What I'd expect to happen: No changes to the database and no exception visible to the user.
What happens: No changes to the database but the app crashes with:
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction;
nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
It's correctly setting the transaction to rollbackOnly but why is the rollback crashing with an exception?
My guess is that ServiceUser.method() is itself transactional. It shouldn't be. Here's the reason why.
Here's what happens when a call is made to your ServiceUser.method() method:
the transactional interceptor intercepts the method call, and starts a transaction, because no transaction is already active
the method is called
the method calls MyService.doSth()
the transactional interceptor intercepts the method call, sees that a transaction is already active, and doesn't do anything
doSth() is executed and throws an exception
the transactional interceptor intercepts the exception, marks the transaction as rollbackOnly, and propagates the exception
ServiceUser.method() catches the exception and returns
the transactional interceptor, since it has started the transaction, tries to commit it. But Hibernate refuses to do it because the transaction is marked as rollbackOnly, so Hibernate throws an exception. The transaction interceptor signals it to the caller by throwing an exception wrapping the hibernate exception.
Now if ServiceUser.method() is not transactional, here's what happens:
the method is called
the method calls MyService.doSth()
the transactional interceptor intercepts the method call, sees that no transaction is already active, and thus starts a transaction
doSth() is executed and throws an exception
the transactional interceptor intercepts the exception. Since it has started the transaction, and since an exception has been thrown, it rollbacks the transaction, and propagates the exception
ServiceUser.method() catches the exception and returns
Could not commit JPA transaction: Transaction marked as rollbackOnly
This exception occurs when you invoke nested methods/services also marked as #Transactional. JB Nizet explained the mechanism in detail. I'd like to add some scenarios when it happens as well as some ways to avoid it.
Suppose we have two Spring services: Service1 and Service2. From our program we call Service1.method1() which in turn calls Service2.method2():
class Service1 {
#Transactional
public void method1() {
try {
...
service2.method2();
...
} catch (Exception e) {
...
}
}
}
class Service2 {
#Transactional
public void method2() {
...
throw new SomeException();
...
}
}
SomeException is unchecked (extends RuntimeException) unless stated otherwise.
Scenarios:
Transaction marked for rollback by exception thrown out of method2. This is our default case explained by JB Nizet.
Annotating method2 as #Transactional(readOnly = true) still marks transaction for rollback (exception thrown when exiting from method1).
Annotating both method1 and method2 as #Transactional(readOnly = true) still marks transaction for rollback (exception thrown when exiting from method1).
Annotating method2 with #Transactional(noRollbackFor = SomeException) prevents marking transaction for rollback (no exception thrown when exiting from method1).
Suppose method2 belongs to Service1. Invoking it from method1 does not go through Spring's proxy, i.e. Spring is unaware of SomeException thrown out of method2. Transaction is not marked for rollback in this case.
Suppose method2 is not annotated with #Transactional. Invoking it from method1 does go through Spring's proxy, but Spring pays no attention to exceptions thrown. Transaction is not marked for rollback in this case.
Annotating method2 with #Transactional(propagation = Propagation.REQUIRES_NEW) makes method2 start new transaction. That second transaction is marked for rollback upon exit from method2 but original transaction is unaffected in this case (no exception thrown when exiting from method1).
In case SomeException is checked (does not extend RuntimeException), Spring by default does not mark transaction for rollback when intercepting checked exceptions (no exception thrown when exiting from method1).
See all scenarios tested in this gist.
For those who can't (or don't want to) setup a debugger to track down the original exception which was causing the rollback-flag to get set, you can just add a bunch of debug statements throughout your code to find the lines of code which trigger the rollback-only flag:
logger.debug("Is rollbackOnly: " + TransactionAspectSupport.currentTransactionStatus().isRollbackOnly());
Adding this throughout the code allowed me to narrow down the root cause, by numbering the debug statements and looking to see where the above method goes from returning "false" to "true".
As explained #Yaroslav Stavnichiy if a service is marked as transactional spring tries to handle transaction itself. If any exception occurs then a rollback operation performed. If in your scenario ServiceUser.method() is not performing any transactional operation you can use #Transactional.TxType annotation. 'NEVER' option is used to manage that method outside transactional context.
Transactional.TxType reference doc is here.
Save sub object first and then call final repository save method.
#PostMapping("/save")
public String save(#ModelAttribute("shortcode") #Valid Shortcode shortcode, BindingResult result) {
Shortcode existingShortcode = shortcodeService.findByShortcode(shortcode.getShortcode());
if (existingShortcode != null) {
result.rejectValue(shortcode.getShortcode(), "This shortode is already created.");
}
if (result.hasErrors()) {
return "redirect:/shortcode/create";
}
**shortcode.setUser(userService.findByUsername(shortcode.getUser().getUsername()));**
shortcodeService.save(shortcode);
return "redirect:/shortcode/create?success";
}

Spring transaction: rollback on Exception or Throwable

I wonder whether it makes sense to use instead of
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
to use Throwable
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
As I understand catching Error will help us behave correctly even when something really bad happen. Or maybe it wouldn't help?
As I understand catching Error will help us behave correctly even when something really bad happen. Or maybe it wouldn't help?
You don't need to explicitly specify rollbackFor = Throwable.class, because spring will by default rollback the transaction if an Error occurs.
See 1.4.3. Rolling Back a Declarative Transaction
In its default configuration, the Spring Framework’s transaction infrastructure code marks a transaction for rollback only in the case of runtime, unchecked exceptions. That is, when the thrown exception is an instance or subclass of RuntimeException. (Error instances also, by default, result in a rollback). Checked exceptions that are thrown from a transactional method do not result in rollback in the default configuration.
Or take a look at the DefaultTransactionAttribute
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
Since you are using #Transactional, we can safely assume you are doing your database operations through Spring, Hibernate, or other JDBC wrappers. These JDBC wrappers don't typically throw checked exceptions, they throw runtime exceptions that wrap the JDBC SQLException types.
#Transactional is setup to rollback, by default, only when an unchecked exception is thrown.
Consider a use case like so
#Transactional
public void persistAndWrite(Bean someBean) throws IOException {
// DB operation
getSession().save(someBean);
// File IO operation which throws IOException
someFileService.writeToFile(someBean);
}
You wouldn't necessarily want to rollback the DB operation just because we couldn't write something to a file.
Similarly
#Transactional
public void persistAndThrowOutOfMemory(Bean someBean) {
// DB operation
getSession().save(someBean);
// consumes all memory, throws OutOfMemoryError
someService.hugeOperationThrowsOutOfMemoryError();
}
You wouldn't necessarily want to rollback the saved entity just because some service cause too much memory to be consumed.
#Transactional gives you the option. Use it where appropriate.
Default value of rollback is register on Error Exception but when u register try{}catch{} manually it overriding the error, so in this case use
catch {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
to do it manually or remove try catch
also you may register exception type in transactional annotation such:
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
I don't know is it possible or not but handling Throwables like Errors is a bad style of programming, it is not the developers responsibility to handle this kind of fatal errors. There always can happen bad things which You cannot handle. You should handle checked exceptions if necessary, which are known to your system like some type of logical errors.

Roll back transaction after exception in JPA + Spring

I'm using Spring and JPA with HIbernate underneath. When a PersistenceException is thrown, I want to catch it and return the error message so that it is not propagated up to the caller.
#Transactional
public String save(Object bean) {
String error = null;
try {
EntityManager entityManager = getEntityManager();
for (int i = 0, n = entities.size(); i < n; i ++) {
entityManager.merge(entities.get(i));
}
}
catch (PersistenceException e) {
error = e.getMessage();
}
return error;
}
But I get an exception saying that javax.persistence.RollbackException: Transaction marked as rollbackOnly. I get that the transaction needs to be rolled back after an exception but how do I roll it back when I've catched the exception and do not want to re-throw it?
By using #Transactional if there are any RuntimeExceptions thrown in the method, it will automatically perform the rollback. You don't need to manually do it. You probably shouldn't be catching that exception at all and instead let it pass to a higher level ExceptionHandler that shows some standard error page to the user (not the stack trace). Also your method is marked void but you are returning a String.
You can use Spring's Exception Translation with a custom PersistenceExceptionTranslator to translate PersistenceException into something useful.
Oh, btw, you shouldn't use #Transactional at the DAO level. Transactions should be started at the service level.
It appears that there is no way to roll back a failed transaction managed by Spring ORM. The code shown in the question is a service class. Extracting its persistence routine to a separate DAO class and having the service class handle PersistenceExceptions did the trick.
Use #Transactional(noRollbackFor={PersistenceException.class}) on the method that is throwing the exception.

Categories

Resources