Need some help here, I'm not able to understand why my transactions are not getting rolled back in an event of exception.
I will try to put my code as close to as It is on the project (cannot share on the internet)
This is my Service
#Service
#SL4j
#Transactional(propagation = propagation.SUPPORTS, readOnly=true)
public class PublicationServiceImpl implements PublicationService{
#Autowired
private PartnerRepo partnerRepo;
#Autowired
private FlowRepo flowRepo;
#Autowired
private PubRepo pubRepo;
#Override
#Transactional(propagation = propagation.REQUIRED, rollbackFor=Exception.class)
public int save(Request request) {
try{
int pk_id_partner = partnerRepo.save(request);
int pk_id_flow = flowRepo.save(request);
String publicationCode = generatePubCode(request);
int publicationCode= pubRepo.save(pk_id_partner, pk_id_flow, request);
}
catch(Exception e){
log.error("Exception in saving");
}
return 0;
}
}
This is my Repository (example of 1 , all 3 repos follow same coding standards)
#Repository
#Slf4j
public class PartnerRepo implemets PartnerRepo{
#Autowired
private NamedParamaterJDBCTemplate namedParamaterJDBCTemplate;
//String Declarations .....
private MapSqlParameterSource sqlParameterSource;
#Override
public int save(Request request){
sqlParamatersSource = new MapSqlParameterSource();
//sqlParamatersSource.addValue(.....)
//sqlParamatersSource.addValue(.....)
//sqlParamatersSource.addValue(.....)
return executeQuery();
}
private int executeQuery(){
try{
keyHolder = new GenerateKeyHolder();
namedParamaterJDBCTemplate.update(getInsertQuery(), sqlParamaterSource , kekHolder, new String[]{"pk_id"})
return keyHolder.getKey().intValue();
}catch(Exception e){
log.error("Exception while saving");
return 0;
}
}
}
So the problem is , Consider there is an exception in the method generatePubCode(request); , ideally since I have used #Transactional at class level and method level , The previous 2 repo transactions () should be rolled back right? However it isn't happening, Even After the code is finished execution I can see the records in DB (Postgres DB v10).
Please help figure out this issue , Am I doing something fundamentally wrong ?
Please do let me know in case you need further information that might help here!
P.S: I have tried all permutations of #Transactional , nothing works : ONLY having this in the catch block works! TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
I wonder If its the right approach for a springBoot project
Thanks in advance for the help!
Edit: as per suggestion made the PublicationServiceSaverImpl.save() public
Best reagards,
Bhargav.
There are several things that break proper transactions in Spring
Your service method is private
You are catching and swallowing exceptions
private method
The fact that your PublicationServiceImpl save method is private basically makes the #Transactional on that method useless. As a private method cannot be proxied, no transactions will apply. Even if it would be public it wouldn't work as you are calling the method from within the same object, hence the transactionality of that method applies.
To fix, make your method public and call the save method from an other class (or make the actual method that is calling save have the proper #Transactional.
The fact that is doesn't work is due to the type op AOP being used, by default Spring will use proxies and this is a drawback of using proxy based AOP.
Another solution to make it work with private methods is to switch to full-blown AspectJ with either compile-time or load-time weaving of the classes. Both require additional setup and that can be tedious.
Catch and swallow exceptions
You have in both your repository as well as your service a try/catch block. Each of those catches and swallows the exceptions (they are logged but not re-thrown).
For transactions to work properly it needs to see the exceptions. The fact that you are catching and swallowing them, makes the transaction aspect not see them and instead of doing a rollback, do a commit. For the transaction aspect everything is ok because there was no exception.
To fix, remove either the try/catch or rethrow the exceptions.
Annotations in general never work on methods called from the same class because of how proxies are created in Spring.
It has nothing to do with #Transaction in particular but with the fact that your methods is private and called from the within same object.
Please make the method public and move the #Transactional method in a separate class annoted with #Service and called it from outside of the instance of the class
#Service
public class PublicationServiceSaverImpl {
#Transactional
**public** int save(Request request) {
...
}
}
You must call the save method from outside of the class PublicationServiceSaverImpl, maybe from PublicationServiceImpl.
The method PublicationServiceImpl.save must be public if you want to use #Transactional.
As per Spring Documentation:
When you use transactional proxies with Spring’s standard configuration, you should apply the #Transactional annotation only to methods with public visibility. If you do annotate protected, private, or package-visible methods with the #Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings.
First of all: make your method public.
Second: you have to throw the exception. If you catch and not rethrow it, how do you expect the transactional processing to know that an error occured and then rollback?
You have two options: throw the Exception instead of catching it, or catch, do some further processing and then rethrow it.
So in your repository, just add a throws keyword and then rethrow the exception after the log statement:
public int executeQuery() throws Exception {
try {
keyHolder = new GenerateKeyHolder();
namedParamaterJDBCTemplate.update(getInsertQuery(), sqlParamaterSource, kekHolder, new String[] {
"pk_id"
})
return keyHolder.getKey().intValue();
} catch(Exception e) {
log.error("Exception while saving");
throw e;
}
}
Now, for your service:
Example 1 - use the throws keyword to propagate the checked exception:
#Override
#Transactional(propagation = propagation.REQUIRED, rollbackFor = Exception.class)
public int save(Request request) throws Exception {
int pk_id_partner = partnerRepo.save(request);
int pk_id_flow = flowRepo.save(request);
String publicationCode = generatePubCode(request);
int publicationCode = pubRepo.save(pk_id_partner, pk_id_flow, request);
return 0;
}
Example 2 - catch and rethrow it as an RuntimeException, which is unchecked.
#Override
#Transactional(propagation = propagation.REQUIRED)
public int save(Request request) {
try {
int pk_id_partner = partnerRepo.save(request);
int pk_id_flow = flowRepo.save(request);
String publicationCode = generatePubCode(request);
int publicationCode = pubRepo.save(pk_id_partner, pk_id_flow, request);
} catch(Exception ex) {
throw new RuntimeException(ex);
}
return 0;
}
Note that the second example doesn't need the rollbackFor argument to the #Transactional. By default, a transaction is rolled back if a unchecked exception occurs, so there's no need to explicitly use rollbackFor in cases of RuntimeExceptions.
Another verification that would have to be done if the solution does not work. It is to verify that the database tables allow the rollback. For this, the engine has to be in InnoDB and not in MyISAM and others.
In my case adding #EnableTransactionManagement annotation on Application class resolved the issue
In stead of #Transactional(propagation = propagation.REQUIRED provide #Transactional(propagation = propagation.REQUIRED_NEW
If you use the latter, it will use the parent transaction boundary, which is at class level.
And you don't need explicitly state rollbackFor=Exception.class. By default spring will roll back on exception
And do change private to public
Try this out
I have got a #Transactional marked class FooServiceImpl that needs to populate an object FooDetails with values obtained from another #Transactional class BarServiceImpl.
When trying to get values from the BarServiceImpl an Exception1 is thrown. I'd like to leave the FooDetails field whose setter threw the exception empty and catch the exception. All other fields must
remain populated even if some setter threw an exception.
Sadly, thats not the case because the transaction has been marked as rollback-only because of the Exception1 being thrown even though I explicitly marked the method as "noRollbackFor = Exception1.class" and caught the exception.
Why is that happening and how can i fix the code to populate all fields whose setters did not throw an Exception and leave empty those whose setters threw one.
Here is the code that leads to the behaviour:
#Service
#Transactional
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface TransactionalService {
// The name of the service
String value() default "";
}
public class FooDetails {
private String fieldA;
private String fieldB;
// getters and setters
...
}
#TransactionalService
public class BarServiceImpl implements BarService {
#Override
public String getFieldB() throws Exception1 {
...
try {
...
} catch (Exception2 e2) {
...
throw new Exception1();
} catch (Exception3 e3) {
...
throw new Exception1();
}
}
}
#TransactionalService
public class FooServiceImpl implements FooService {
...
...
#Autowired BarService barService;
#Override
#Transactional(noRollbackFor = {Exception1.class, Exception2.class, Exception3.class})
public FooDetails getFooDetails(Long fooId) {
Foo foo = fooDao.get(fooId);
if (foo == null) {
return null;
}
FooDetails fooDetails = new FooDetails();
// getAFooDetails() has no exceptions to throw => ok
fooDetails.setFieldA(getAFooDetails());
// barService.getFieldB() throws an Exception1 => results in
// "org.springframework.transaction.UnexpectedRollbackException:
// Transaction rolled back because it has been marked as rollback-only" => all foo fields left empty
try {
fooDetails.setFieldB(barService.getFieldB());
} catch (Exception1 e) {
LOG.info(e.getMessage());
}
}
}
You are using two logical transaction scopes: an outer transaction scope (in FooServiceImpl ) and an inner transaction scope (in BarServiceImpl). Both belong to the same single physical Transaction.
Since you say Exception1 is a checked exception, throwing it never causes any rollback (until explicitly declared) in BarServiceImpl.
Catching it in FooServiceImpl also does not lead to any rollback.
Usually, an UnexpectedRollbackException will be thrown, if an inner transaction scope (here: your BarServiceImpl.getFieldB) triggers a rollback.
When the calling method of the outer transaction scope (here: your FooServiceImpl.getFooDetails) returns normally, Spring's AbstractPlatformTransactionManager will try a commit. This commit fails because someone else already marked it as 'rollbackOnly' (which was done by the inner transaction scope). Then it will throw a UnexpectedRollbackException back to the caller of FooServiceImpl.getFooDetails.
So, your BarServiceImpl.getFieldB must have somehow thrown an unchecked exception (different from Exception1)
OR
You are using the one single physical transaction somewhere else in code you did not show here, and this piece of code did trigger a rollback via a unchecked exception.
What is the difference between method with
#Transactional(propagation = Propagation.SUPPORTS)
and a method without #Transactional?
For example:
public class TEst {
#Transactional
public void methodWithTransaction1(){
methodWithSupportsTransaction();
}
#Transactional
public void methodWithTransaction2(){
methodWithoutTransactional();
}
#Transactional(propagation = Propagation.SUPPORTS)
public void methodWithSupportsTransaction(){
}
public void methodWithoutTransactional(){
}
}
Except for the slight difference indicated in the javadoc regarding synchronization, a difference between both is that a transactional proxy intercepts the method call if the method is annotated with Transactional, and will mark the current transaction, if any, as rollbackOnly if a runtime exception is thrown from that method.
So, let's take an example:
public class A {
#Autowired B b;
#Transactional
public void foo() {
try {
b.bar();
}
catch (RuntimeException e) {
// ignore
}
...
}
}
public class B {
// #Transactional(propagation = Propagation.SUPPORTS)
public void bar() {
throw new RuntimeException();
}
}
Calling a.foo() will start a transaction if none exists yet (propagation REQUIRED). Then b.bar() will be called and will throw an exception. The exception is caught by a.foo(), which continues executing as if nothing happened. At the end of a.foo(), the transaction will be committed successfully.
Now let's uncomment the Transactional annotation on b.bar(). Calling a.foo() will start a transaction if none exists yet (propagation REQUIRED). Then b.bar() will be called and will throw an exception. This exception will be "intercepted" by the transactional proxy around B, which will mark the transaction to rollbackOnly. Then the exception will propagate to A. a.foo(). The exception is caught by a.foo(), which continues executing as if nothing happened. At the end of a.foo(), the transaction will be committed, but that commit will fail because the transaction has already been marked as rollbackOnly. The caller of a.foo() will get a TransactionSystemException.
Spring supports two types of transaction management programmatic and declarative.
Programmatic transaction management: In this manner transactions need to be handled by us. For example-
EntityTransaction tran = entityManager.getTransaction();
try {
tran.begin();
methodWithoutTransactional();
tran.commit();
} catch(Exception ex) {
tran.rollback();
throw ex;
}
Declarative transaction management: In this manner, we can separate out transaction management code from our business logic, simply by using annotation or xml based configuration. Which you've already done in example code-
#Transactional
public void methodWithTransaction1(){
methodWithSupportsTransaction();
}
For #Transactional annotation if we do not define the propagation type, PROPAGATION_REQUIRED will be applied by default. You can find the documentation here.
I use ssh framework to develop a web application.
There is an example of my transaction.
#Transactional
public StudentEntity addStudent(StudentEntity studentEntity) {
return studentDAO.save(studentEntity);
}
Now, I want to return null when exception is thrown and then transaction rollback.
In general it is not advised to return null.
If you anticipate any Exception from your logic you should inform the caller via throws clause so that they are prepared for such scenarios.
Regarding rollback you should consider below update to your #Transactional annotation
#Transactional(rollbackFor=Exception.class)
Do note that this will rollback transaction after throwing any exception.
To rollback transaction programatically take a look at TransactionAspectSupport class.
#Transactional
public StudentEntity addStudent(StudentEntity studentEntity) {
try {
return studentDAO.save(studentEntity);
}
catch(Exception ex) {
//set transaction for rollback.
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
You could do it declarative manner
#Transactional(rollbackFor={SomeSpecificException.class, SomeOtherSpecificException.class})
Methods invoked:
1. Struts Action
2. Service class method (annotated by #Transactional)
3. Xfire webservice call
Everything including struts (DelegatingActionProxy) and transactions is configured with Spring.
Persistence is done with JPA/Hibernate.
Sometimes the webservice will throw an unchecked exception. I catch this exception and throw a checked exception. I don't want the transaction to roll back since the web service exception changes the current state. I have annotated the method like this:
#Transactional(noRollbackFor={XFireRuntimeException.class, Exception.class})
public ActionForward callWS(Order order, ....) throws Exception
(...)
OrderResult orderResult = null;
try {
orderResult = webService.order(product, user)
} catch (XFireRuntimeException xfireRuntimeException) {
order.setFailed(true);
throw new WebServiceOrderFailed(order);
} finally {
persist(order);
}
}
I still get this exception:
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
When I try to reproduce this with junit, the transaction isn't marked for roll back and it's still possible to commit the transaction.
How do I make Spring not to roll back the transaction?
Managed to create a test case for this problem:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"file:web/WEB-INF/spring/applicationContext.xml",
"file:web/WEB-INF/spring/services.xml"})
#Transactional
public class DoNotRollBackTest {
#Autowired FakeService fakeService;
#Test
#Rollback(false)
public void testRunXFireException() {
fakeService.doSomeTransactionalStuff();
}
}
FakeService:
#Service
public class FakeService {
#Autowired private EcomService ecomService;
#Autowired private WebService webService;
#Transactional(noRollbackFor={XFireRuntimeException.class})
public void doSomeTransactionalStuff() {
Order order = ecomService.findOrderById(459);
try {
webService.letsThrowAnException();
} catch (XFireRuntimeException e) {
System.err.println("Caugh XFireRuntimeException:" + e.getMessage());
}
order.setBookingType(BookingType.CAR_BOOKING);
ecomService.persist(order);
}
}
WebService:
#Transactional(readOnly = true)
public class WebService {
public void letsThrowAnException() {
throw new XFireRuntimeException("test!");
}
}
This will recreate the rollback-exception.
Then I realized that the transaction is probably being marked as rollbackOnly in WebService.letsThrowAnException since WebService is also transactional. I moved to annotation:
#Transactional(noRollbackFor={XFireRuntimeException.class})
public void letsThrowAnException() {
Now the transaction isn't being rolled back and I can commit the changes to Order.
You must not throw an exception where Spring can see it. In this case, you must not throw WebServiceOrderFailed(). The solution is to split the code into two methods. The first method does the error handling and returns the exception, the outer method creates the transaction.
[EDIT] As for noRollbackFor: Try to replace Exception.class with WebServiceOrderFailed.class.