Spring boot #Async annotation throw LazyInitializationException - java

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/

Related

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 #Transactional annotation behaviour

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

How to ensure #Entity exists in #Async methods?

I want to create an entity, and within the transaction trigger an #Async method to perform some changes on the same entity. The changes should also be persisted async.
Problem: as I have to fire the async method within the transaction, I could use the autogenerated #Id from the entity. BUT the async method then would have to first fetch the entity by that Id, and most often this does not exist yet.
Only if I put some Thread.sleep() as first statement inside the async method, it can mostly be ensured that the entity has been persisted by the outer transaction.
But that solution is not very nice. Question: how can I ensure inside the async method that it should wait for the entity to exist in DB?
#Service
public class OuterService {
#Service
private SyncService service;
#Transactional
public void process() {
service.mySyncMethod();
//etc
}
}
#Service
public class SyncService {
#Transactional
public void mySnycMethod() {
Entity entity = new MyEntity();
//fill entity
dao.save(entity);
asyncService.performLongRunningTask(entity.getId());
}
}
#Service
public class AsycnService {
#Async
#Transactional
public voi performLongRunningTask(Long id) {
//problem: is mostly == null because this is executed before the outer transaction completes
//only works if I put like Thread.sleep(5000) in between. but how can I really ensure the entity exists before executing this async lookup?
MyEntity entity = dao.findOne(id);
//perform long running task
//change some fields in entity accordingly
dao.save(entity);
}
}
You could register a hook on transaction commit using the TransactionSynchronizationManager.registerSynchronization() and implementing the afterCommit() method.
#Transactional
public void mySnycMethod() {
Entity entity = new MyEntity();
// fill entity
dao.save(entity);
// performLongRunningTask will start after the transaction has been
// commited
TransactionSynchronizationManager
.registerSynchronization(new TransactionSynchronizationAdapter() {
#Override
public void afterCommit() {
asyncService.performLongRunningTask(entity.getId());
}
});
}
But note what the Javadocs say about using the TransactionSynchronizationManager in your application:
To be used by resource management code but not by typical application
code

HibernateTemplate not getting the object when called through TaskExecutor

I have a web service DocGenerationServiceImpl that inserts (for every format) a record in the table using DocRepository and object representing the record as DocFileDO. In the for-loop, I can get the id of the record that was created in the table. For each record, I will call the executor's execute method where DocGenTask will search for the record given the id. However, for example, there are 3 formats, the DocGenTask is able to get only the last record. The first 2 it cannot find. Although it's using hibernateTemplate. Can please advise?
#RestfulService
#Controller
#RequestMapping("/docs")
public class DocGenerationServiceImpl {
#Autowired
private TaskExecutor taskExecutor;
#Autowired
private DocRepository docRepository;
#RequestMapping(value = "/generate", method = RequestMethod.POST)
#ResponseBody
public String generatedDocFile(DOCParam param) {
for(String format : param.getFormatList()) {
DocFileDO docFileDO = new DocFileDO();
...
docRepository.saveDocFile(docFileDO);
log.debug("docFileDO id = " + docFileDO.getId());
DocGenTask task = new DocGenTask(docFileDO.getId());
task.setDocRepository(docRepository);
taskExecutor.execute(task);
}
}
}
#Repository
public class DocRepository {
#Autowired
private HibernateTemplate hibernateTemplate;
public DocFileDO saveDocFile(DocFileDO docFile) {
hibernateTemplate.save(docFile);
hibernateTemplate.flush();
return docFile;
}
public DocFileDO getDocFile(Long docFileId) {
return hibernateTemplate.get(DocFileDO.class, docFileId);
}
}
public class DocGenTask implements Runnable {
public void run() {
generate();
}
private void generate() {
DocFileDO docFileObj = docRepository.getDocFile(docFileId);
}
}
A couple of things
Don't use HibernateTemplate it should be considered deprecated as of Hibernate 3.0.1 (which was released somewhere in 2006). Use the SessionFactory directly and use the getCurrentSession() method to get a hibernate Session to operate on.
You don't have transactions setup (judging from the snippets), to work with a databse you need proper transaction setup.
Your controller is doing much, all of this should be inside a service.
The first refactor your repository
#Repository
public class DocRepository {
#Autowired
private SessionFactory sf;
public DocFileDO saveDocFile(DocFileDO docFile) {
Session session = sf.getCurrentSession();
session.save(docFile);
return docFile;
}
public DocFileDO getDocFile(Long docFileId) {
return sf.getCurrentSession().get(DocFileDO.class, docFileId);
}
}
Now your code will probably fail due to improper transaction setup. Add #Transactional to all the methods (or class) that need a transaction (like the saveDocFile method).
As mentioned you probably should move the code found in the controller to a service. The controller should be nothing more then a thin integration layer converting from the web to an internal representation of something and then kick off a service/business method somewhere. This service-/business-method is also your transactional unit-of-work it either all succeeds or all fails.

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