I have a Spring and hibernate application (both latest version) and I have 2 beans as mentioned below
#Component
public class Bean1{
#Autowired
Bean2 bean2;
#Transactional(propagation = Propagation.REQUIRED)
public void foo()
{
bean2.bar();
}
#Component
public class Bean2{
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void bar()
{
try{
// Do something which throws exception
}
catch (Exception e) {
log & eat the exception here only.
But inspite of this the outer transaciton gets rolled back
}
}
The issue is that when bean2.bar causes any exception (e.g. foreign Key ConstraintViolationException) then it rolls back the outer transaction as well saying " Transaction rolled back because it has been marked as rollback-only","moreInfo":""}"
On seeing hibernate logs I found only one line for "new transaction"
D| o.s.o.h.HibernateTransactionManager- Creating new transaction with name ... PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
which means no new transaction is getting created for the inner bean2.bar();
I am not able to find out what's wrong here? Any help is greatly appreciated.
REQUIRES_NEW applies to JTA transaction Managers only.
Refer Spring Doc here
REQUIRES_NEW
public static final Propagation REQUIRES_NEW
Execute non-transactionally, suspend the current transaction if one
exists. Analogous to EJB transaction attribute of the same name. Note:
Actual transaction suspension will not work on out-of-the-box on all
transaction managers. This in particular applies to
JtaTransactionManager, which requires the
javax.transaction.TransactionManager to be made available it to it
(which is server-specific in standard J2EE).
Related
Let's say I have a #KafkaListener class with a #KafkaHandler method inside that processes any received messages and does some DB operations.
I want to have fine-grained control over how and when to commit (or rollback) the database changes (i.e. manually manage the DB transaction) in this class. The consumed message offset can be committed regardless of the DB transaction result.
Here is a simplified version of what I have:
#Service
#RequiredArgsConstructor
#KafkaListener(
topics = "${kafka.topic.foo}",
groupId = "${spring.kafka.consumer.group-id-foo}",
containerFactory = "kafkaListenerContainerFactoryFoo")
public class FooMessageConsumer {
// ...
private final EntityManager entityManager;
#KafkaHandler
public void handleMessage(FooMessage msg) {
// ...
handleDBOperations(msg);
// ...
}
void handleDBOperations(msg) {
try {
entityManager.getTransaction().begin();
// ...
entityManager.getTransaction().commit();
} catch (Exception e) {
log.error(e.getLocalizedMessage(), e);
entityManager.getTransaction().rollback();
}
}
}
When a message is received and entityManager.getTransaction().begin(); is invoked, this results in an exception:
java.lang.IllegalStateException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead
Why am I not allowed to create a transaction here?
And if I remove EntityManager and add #Transactional annotations to methods with DB operations (though this is not exactly what I want), then it results in another exception:
TransactionRequiredException Executing an update/delete query
It seems like it completely ignores the annotation. Is this somehow related to Kafka consumer having its own transaction management?
In short, what am I doing wrong here and how can I manage DB transactions in a #KafkaHandler method?
Any help is appreciated.
Thanks in advance.
Try using Springs TransactionTemplate:
https://docs.spring.io/spring-framework/docs/3.0.0.M4/reference/html/ch10s06.html
If your use case is (that) simple, Springs declarative transaction management should also let you achieve the behavior you ask for:
https://docs.spring.io/spring-framework/docs/3.0.0.M3/reference/html/ch11s05.html
It's not exactly as the title says, but close to. Consider these Spring beans:
#Bean
class BeanA {
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = EvilException.class)
public void methodA() {
/* ... some actions */
if (condition) {
throw new EvilException();
}
}
}
#Bean
class BeanB {
#Autowired private BeanA beanA;
final int MAX_TRIES = 3;
#Transactional(propagation = Propagation.NESTED)
public void methodB() {
// prepare to call Bean A
try {
beanA.methodA();
/* maybe do some more things */
}
catch (EvilException e) {
/* recover from evil */
}
}
}
#Bean
class MainWorkerBean {
#Autowired private BeanB beanB;
#Autowired private OtherBean otherBean;
#Transactional(propagation = Propagation.REQUIRED)
public void doSomeWork() {
beanB.methodB();
otherBean.doSomeWork();
}
}
Important note: I'm using JDBC transaction manager that supports savepoints.
What I'm expecting this to do is, when EvilException is thrown, the transaction of the BeanA is rolled back, which with this setup happens to be the savepoint created by starting methodB. However, this appears to not be the case.
When going over with debugging tools, what I'm seeing is this:
When doSomeWork of MainWorkerBean starts, new transaction is created
When methodB starts, transaction manager properly initializes a savepoint and hands it to TransactionInterceptor
When methodA starts, transaction manager sees Propagation.REQUIRED again, and hands out a clean reference to the actual JDBC transaction again, that has no knowledge of the savepoint
This means that when exception is thrown, TransactionStatus::hasSavepoint return false, which leads to roll back of the whole global transaction, so recovery and further steps are as good as lost, but my actual code has no knowledge of the rollback (since I've written recovery for it).
For now, I can't consider changing BeanA's transaction to Propagation.NESTED. Admittedly, looks like it's going to allow me to have the more local rollback, but it's going to be too local, because as I understand it, Spring then will have two savepoints, and only roll back the BeanA savepoint, not BeanB one, as I'd like.
Is there anything else I'm missing, such as a configuration option, that would make internal transaction with Propagation.REQUIRED consider that it is running inside a savepoint, and roll back to savepoint, not the whole thing?
Right now we're using Spring 4.3.24, but I already crawled through their code and can't spot any relevant changes, so I don't think upgrading will help me.
As described in this bug ticket: https://github.com/spring-projects/spring-framework/issues/11234
For spring versions < 5.0, in the situation described, the global transaction is set to 'rollback-only'.
In this transaction I am processing several tasks. If an error should occur during a single task, I do not want the whole transaction to be rolled back, therefore I wrap the task processing in another transaction boundary with a propagation of PROPAGATION_NESTED.
The problem comes when, during task processing, calls are made to existing service methods defined with a transaction boundary of PROPAGATION_REQUIRED. Any runtime exceptions thrown from these methods cause the underlying connection to be marked as rollback-only, rather than respecting the current parent transaction nested propagation.
[...]
As of Spring Framework 5.0, nested transactions resolve their rollback-only status on a rollback to a savepoint, not applying it to the global transaction anymore.
On older versions, the recommended workaround is to switch globalRollbackOnParticipationFailure to false in such scenarios.
However, even for Spring5, I noticed when reproducing the problem, that the nested transaction may be rolled back, including all things done in the catch block of methodB(). So your recover code might not work inside methodB(), depending on what your recovery looks like. If methodA() was not transactional, that would not happen. Just something to watch out for.
Some more details to be found here: https://github.com/spring-projects/spring-framework/issues/8135
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";
}
I have spring injecting a service into itself to allow the service to make transactional calls to itself. Unfortunately, I'm finding that a requires_new method that is throwing a NullPointerException and being caught is not rolling back the new transaction. The outer transaction is not interrupted which is what I want, but I'm having trouble explaining why the requires new transaction isn't rolled back. Any ideas?
#Service(value="orderService")
#Transactional
public class OrderServiceImpl implements OrderService {
#Resource
private OrderService orderService; // transactional reference to this service
public void requiredTransMethod(){
try {
orderService.requiresNewTransMethod();
}catch(Throwable t){
LOG.error("changes from requiresNewTransMethod call should be rolled back right?", t);
}
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewTransMethod() {
// database changes that are NOT rolled back
throw new NullPointerException("bla bla bla");
}
}
This could be an instance of transactional annotations not working because you are calling them from within the same class.
The way Spring's AOP implementation works (by default) is by using proxy classes, which will not work as expected for method invocations from within the same class.
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";
}