Spring #Transactional annotation behaviour - java

I am building a workflow system, where a service layer - WorkflowServiceImpl, process a document and send notifications to users.
There is another service DocumentServiceImpl, which has a method post() method, which internally calls WorkflowServiceImpl.process() method.
#Service
public class WorkflowServiceImpl implements WorkflowService{
#Transactional(propagation=Propagation.REQUIRES_NEW, noRollbackFor=WorkflowException.class)
public void process(WorkflowDocument document) throws WorkflowException {
Boolean result = process(document);
if(!result){
throw new WorkflowException();
}
}
private Boolean process(WorkflowDocument document){
//some processing on document
updateDocument();
sendNotifications();
}
private void updateDocument(WorkflowDocument document){
//some update operation
}
private void sendNotifications(WorkflowDocument document){
//send notifications - insertion operation
}
}
#Service
public class DocumentServiceImpl implements DocumentService{
#Autowired private WorkflowService workflowService;
#Transactional
public void post(){
//some operations
workflowService.process(document);
//some other operations
}
}
As you can see, I have marked
DocumentServiceImpl.post() as #Transactional
WorkflowServiceImpl.process() as #Transactional(propagation=Propagation.REQUIRES_NEW, noRollbackFor=WorkflowException.class)
I am trying to achieve this :
1. WorkflowServiceImpl.process() method should commit always(update document and send notifications) - whether a WorkflowException is thrown or not
2. DocumentServiceImpl.post() method should rollback, when WorkflowException is thrown
When I tried using the above transaction configurations
1. When WorkflowException is not thrown, the code worked as expected - committed both WorkflowServiceImpl.process() and DocumentServiceImpl.post() methods
2. When WorkflowException is thrown, the request processing is not completed (I can see the request processing symbol in the browser)
I can't find what is wrong with the code. I am using spring version 3.1.4

You need to have a rollbackFor in the #Transactional annotation for WorkflowException and propagation as REQUIRES_NEW
#Transactional(rollbackFor = {WorkflowException.class}, propagation = Propagation.REQUIRES_NEW)
public void post(){
//some operations
workflowService.process(document);
//some other operations
}
This will make a new transaction to be started with post method of DocumentServiceImpl

Related

Manual transactional rollback bounds to an individual iteration in a forEach loop in Spring boot

I'm trying to update a few columns in a table and the updation happens inside a forEach.
I want to handle each iteration as an individual transaction and any rollback inside the forEach should only rollback on the transactions that occurred on the specific iteration (not all previous iterations).
Moreover, I don't want an exception to trigger the rollback. Rather, it has to be triggered programmatically. For that, I'm making use of this - TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
This is what I tried so far:
#Service
public class MyService {
#Transactional
public void processLabResults() {
arrayList.forEach(i -> {
proccessDiagnosis();
});
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
proccessDiagnosis() {
boolean isDispositionUpdated = updateDisposition(); // calls JPA Repository to update
if(isUpdated) {
boolean isSomethingElseUpdated = updatedSomethingElse(); // calls JPA Repository to update
if(!isSomethingElseUpdated) {
TransactionInterceptor.currentTransactionStatus().setRollbackOnly(); //Should rollback only the transactions that happened in the current iteration
}
}
}
}
If I executed the above, it rolls back all the previous transactions that are not part of the current iteration as well. If I remove # Transactional annotation from the processLabResults method, I'm getting No transaction aspect-managed TransactionStatus in scope error and no rollback happens.
Any help would be appreciated. Thanks!
This answer helps me resolve the issue. I moved the proccessDiagnosis() function to a different service file.
Service.java
#Service
public class MyService {
#AutoWired
AnotherService anotherService;
#Transactional
public void processLabResults() {
arrayList.forEach(i -> {
anotherService.proccessDiagnosis();
});
}
}
AnotherService.java
#Service
public class AnotherService {
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void proccessDiagnosis() {
boolean isDispositionUpdated = updateDisposition(); // calls JPA Repository to update
if(isUpdated) {
boolean isSomethingElseUpdated = updatedSomethingElse(); // calls JPA Repository to update
if(!isSomethingElseUpdated) {
TransactionInterceptor.currentTransactionStatus().setRollbackOnly(); //Should rollback only the transactions that happened in the current iteration
}
}
}
}

How to perform another DB query in the same transaction in case of unique constraint violation in Java Spring #Transactional method?

I am struggling with handling exceptions during transactional methods with Spring #Transactional annotations. Currently, when my Dao insert method throws a unique contraint violation, I want to perform a read instead to get the existing item. However, this fails because the UniqueIdViolation causes the transaction to fail. How can I refactor this to work as intended whilst not doing a dodgy boolean return on the insert?
I have a DAO with an insert method, e.g.:
public class ItemDao {
public void insertItem(Item item) {
// insert into db via jooq
}
public Item fetch(String id) {
// fetch from db
}
...
}
If it violates a unique constraint I have an aspect that handles the DataAccessException and rethrows it as a UniqueIdException.
I have a service method that takes in some information and creates an Item, before returning that item.
If the dao throws a UniqueIdException, I want the service to catch this error and then call itemDao.find() to get the existing Item and return it.
public class MyServiceImpl implements MyService {
#Transactional(isolation = SERIALIZABLE)
public Item createItem(...) {
Item item = new Item(...);
try {
itemDao.insert(item);
return item;
} catch (UniqueIdException ex) {
return itemDao.find(item.getId());
}
}
}
The issue here is when itemDao.insert(item) throws an exception this causes the transaction to fail and therefore itemDao.find() doesn't run.
#Transactional is setup to rollback, by default, only when an unchecked exception is thrown.
Since DataAccessException is unchecked exception your transaction will be rolled backed.
A better strategy will be to first call fetch() if returned null then call insertItem

Confusing hibernate behaviour using spring data

I just hit a really strange case which I can't explain to myself. I have have the following scenario:
Hibernate version: 5.4.9
Spring data version: 2.2.3
So the following method is wrapped in a transaction and it only saves the entity
#Transactional
public Bookmark create(Entity entity) {
return repository.save(entity);
}
Here I registered a PostInsertEventListener. Based on some logic it uses the same repository to query the underlying table. I removed the logic in order to make the example more readable.
#Component
public class EntityListener implements PostInsertEventListener {
#Autowired
private EntityRepository repository;
#Autowired
private EntityManagerFactory entityManagerFactory;
#PostConstruct
private void init() {
final EventListenerRegistry registry = ((SessionFactoryImplementor) entityManagerFactory.unwrap(SessionFactory.class)).getServiceRegistry()
.getService(EventListenerRegistry.class);
registry.appendListeners(EventType.POST_INSERT, this);
}
#Override
public void onPostInsert(PostInsertEvent event) {
if (event.getEntity() instanceof Entity) {
repository.findByFieldOneAndFieldTwoIsNotNull(event.getEntity().fieldOne());
}
}
#Override
public boolean requiresPostCommitHanding(EntityPersister persister) {
return false;
}
}
So when I invoke the create(Entity entity) method the onPostInsert(PostInsertEvent event) is triggered(as expected) but when this line is invoked repository.findByFieldOneAndFieldTwoIsNotNull(event.getEntity().fieldOne());
then another insert is executed and the onPostInsert(PostInsertEvent event) is triggered again. And of course at some point this leads to StackOverflowException.
Can someone come up with an idea why another insert is executed when I'm reading data using findBy query?
So i have a progress on that issue. When I execute repository.findByFieldOneAndFieldTwoIsNotNull(event.getEntity().fieldOne()); in a new separate transaction then everything is fine. So it seems that executing queries in the entity listener in the same transaction that the insert was executed on is leading to an infinite recursion which leads to a StackOverflowException. But I can't figure it out why is this happening.

Spring boot #Async annotation throw LazyInitializationException

I have an emailsenderservice to manage email notification asynchronously.
There are 2 async methods, one method works, but another one throws LazyInitializationException:
#Service
public class EmailSenderService {
// working
#Async
public void sendNewBidRequestEmail(BidRequest bidRequest) {
this.sendNewBidRequestEmailToSupplier(bidRequest);
}
#Transactional
public void sendNewBidRequestEmailToSupplier(BidRequest bidRequest) {
sendNewBidRequestEmailToSupplier(bidRequest, bidRequest.getHotels());
}
#Transactional
public void sendNewBidRequestEmailToSupplier(BidRequest bidRequest, List<Hotel> hotelList) {
for (Hotel hotel : hotelList) {
...
this.sender.send()
}
}
// not working, throw exception
#Async
public void sendCancelledBidRequestEmail(BidRequest bidRequest, String reason) {
this.sendCancelledBidRequestEmailToSupplier(bidRequest, bidRequest.getHotels(), reason);
}
#Transactional
public void sendCancelledBidRequestEmailToSupplier(BidRequest bidRequest, List<Hotel> hotelList, String reason) {
for (Hotel hotel : hotelList) { // throw exception here
...
this.sender.send();
}
}
I'm totally following this thread.
You can see both async methods have almost the same structure. Async method calls a transactional method. But the second one throws org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.corpobids.server.entity.BidRequest.hotels, could not initialize proxy - no Session.
I even imitate the first async method structure to modify the second one to:
#Async
public void sendCancelledBidRequestEmail(BidRequest bidRequest, String reason) {
this.sendCancelledBidRequestEmailToSupplier(bidRequest, reason);
}
#Transactional
public void sendCancelledBidRequestEmailToSupplier(BidRequest bidRequest, String reason) {
this.sendCancelledBidRequestEmailToSupplier(bidRequest, bidRequest.getHotels(), reason);
}
#Transactional
public void sendCancelledBidRequestEmailToSupplier(BidRequest bidRequest, List<Hotel> hotelList, String reason) {
for (Hotel hotel : hotelList) { // exception in this line
...
this.sender.send();
}
}
}
This time, it gives me java.lang.IllegalStateException: org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl#fce9b7b is closed.
I would like to know the missing point in my code, any help would be appreciated.
You have issues with JPA.
In JPA the relationships OneToMany has two behaviors LAZY and EAGER.
You can check a good explanation on: Difference between FetchType LAZY and EAGER in Java Persistence API?
I assume when you invoke your code in async mode the context of JPA is loose. So Hibernate can't fill the relationship executing a new query since the context is different. In order to fix your problem you have two options:
Configure the relationship with EAGER
Preload the relationship before invoke async method
Better techniques for lead with Lazy Loading:
https://www.thoughts-on-java.org/5-ways-to-initialize-lazy-relations-and-when-to-use-them/

How to prevent JPA from rolling back transaction?

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.

Categories

Resources