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 dao layer:
#Transactional
public class DatabaseCollectionDao implements IDatabaseCollectionDao {
#PersistenceContext
private EntityManager entityManager;
#Override
public void create(Collection collection) {
entityManager.persist(collection);
}
}
It works correctly but:
When database isn't available I have SocketException.
When database contains a duplicate key I have SQLIntegrityConstraintViolationException
I am trying to try/catch it inside create method:
#Override
public void create(Collection collection) {
try{
entityManager.persist(collection);
} catch (SQLIntegrityConstraintViolationException e){
//do smth
}
}
But Intellij says that It is never thrown.
When I try to try/catch Exception i have UnexpectedRollbackException.
How to handle exceptions using JPA entityManager?
update: An attempt to remove #Transactional gave nothing
P.S. To be sure i tried to try/catch it in higher layers. I don't know what i can try more to solve it.
create customException handler extend ResponseEntityExceptionHandler . #ExceptionHandler(ConstraintViolationException::class) fun handleConstraintViolation(ex: ConstraintViolationException, request: WebRequest): ResponseEntity {} this kotlin snippet u can convert to java easily –
I developed a typical enterprise application that is responsible for provisioning customer to a 3rd party system. This system has a limitation, that only one thread can work on a certain customer. So we added a simple locking mechanism that consists of #Singleton which contains a Set of customerIds currently in progress. Whenever a new request comes for provisioning, it first checks this Set. If cusotomerId is present, it waits otherwise it adds it to the Set and goes into processing.
Recently it was decided, that this application will be deployed in cluster which means that this locking approach is no longer valid. We came up with a solution to use DB for locking. We created a table with single column that will contain customerIds (it also has a unique constraint). When a new provisioning request comes we start a transaction and try and lock the row with customerId with SELECT FOR UPDATE (if customerId does not exist yet, we insert it). After that we start provisioning customer and when finished, we commit transaction.
Concept works but I have problems with transactions. Currently we have a class CustomerLock with add() and remove() methods that take care of adding and removing customerIds from Set. I wanted to convert this class to a stateless EJB that has bean-managed transactions. add() method would start a transaction and lock the row while remove() method would commit transaction and thus unlocked the row. But it seems that start and end of transaction has to happen in the same method. Is there a way to use the approach I described or do I have to modify the logic so the transaction starts and ends in the same method?
CustomerLock class:
#Stateless
#TransactionManagement(TransactionManagementType.BEAN)
public class CustomerLock {
#Resource
private UserTransaction tx;
public void add(String customerId) throws Exception {
try {
tx.begin();
dsApi.lock()
} catch (Exception e) {
throw e;
}
}
public void remove(String customerId) throws Exception {
try {
tx.commit();
} catch (Exception e) {
throw e
}
}
}
CustomerProvisioner class excerpt:
public abstract class CustomerProvisioner {
...
public void execute(String customerId) {
try {
customerLock.add(customerId);
processing....
customerLock.remove(customerId);
} catch (Exception e) {
logger.error("Error", e);
}
}
...
}
StandardCustomerProvisioner class:
#Stateless
public class StandardCustomerProvisioner extends CustomerProvisioner {
...
public void provision(String customerId) {
// do some business logic
super.execute(customerId);
}
}
As #Gimby noted, you should not mix container-managed and bean-managed transactions. Since your StandardCustomerProvisioner has no annotation like "#TransactionManagement(TransactionManagementType.BEAN)" - it uses container-managed transactions, and REQUIRED by default.
You have 2 options to make it work:
1) To remove "#TransactionManagement(TransactionManagementType.BEAN)" with UserTransaction calls and run CMT
2) Add this annotation ("#TransactionManagement(TransactionManagementType.BEAN)") to StandardCustomerProvisioner and use transaction markup calls from this method, so all the invoked methods use the same transactional context. Markup calls from CustomerLock should be removed anyway.
Good day. The following code:
class A{
private B b;
#Transactional
public SomeResult doSomething(){
SomeResult res = null;
try {
// do something
} catch (Exception e) {
res = b.saveResult();
}
return res ;
}
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
class B{
public SomeResult saveResult(){
// save in db
}
}
As I understand, if there is an exception in the method doSomething the transaction isn't rolled back. And how to make that it rolled? and returned SomeResult
You shouldn't call Rollback programmatically. The best way, as recommended by the docs, is to use declarative approach. To do so, you need to annotate which exceptions will trigger a Rollback.
In your case, something like this
#Transactional(rollbackFor={MyException.class, AnotherException.class})
public SomeResult doSomething(){
...
}
Take a look at the #Transaction API and the docs about rolling back a transaction.
If, despite the docs recommendation, you want to make a programmatic rollback, then you need to call it from TransactionAspectSupport as already suggested. This is from the docs:
public void resolvePosition() {
try {
// some business logic...
} catch (NoProductInStockException ex) {
// trigger rollback programmatically
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
There may be a architecture mistake though. If your method fails and you need to throw an exception, you shouldn't expect it to return anything. Maybe you're giving too much responsibilities to this method and should create a separated one that only model data, and throws an exception if something goes wrong, rolling back the transaction. Anyway, read the docs.
get TransactionStatus using TransactionAspectSupport.currentTransactionStatus()
ect transaction manager to your bean try to invoke Rollback(DefaultTransactionStatus status) in transaction manager.
refer to spring documentation
You are strongly encouraged to use the declarative approach to
rollback if at all possible. Programmatic rollback is available should
you absolutely need it, but its usage flies in the face of achieving a
clean POJO-based architecture.
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.