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
Related
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
}
}
}
}
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.
I am inserting the data in one method(has #Transactional(propagation = Propagation.Required)) but in the other method(has #Transactional(propagation = Propagation.Required)) if I try to get the same data it is giving null.
Both methods are wrote in service layer with #Transactional (rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
How to get the data which is inserted in the same transaction.
Something like :-
#Service
public class Service{
#Transactional
public void method(){
mapper.insert(); //insert to DB(Using Mapper interface)
ServiceLayer.method2()
}
}
#Service
public void ServiceLayer{
#Transactional
public static void method2(){
result = mapper.select() //Select inserted data - returning null
}
}
In order to persist the changes made to the current session you can invoke entityManager.flush();
It may be worked, but it's not a solution.
In your case, your Transaction from Service.method() created a transaction that is not committed yet. That's why you can't fetch it.
I found the answer...after removing #transactional from ServiceLayer.method2() it's worked fine.
What I want is to implement the Repository pattern in a JPA/Hibernate application. I have a generic interface that describes the basic contract of my repositories:
public interface EntityRepository<Entity extends Object, EntityId> {
Entity add(Entity entity);
Entity byId(EntityId id);
void remove(Entity entity);
void removeById(EntityId id);
void save();
List<Entity> toList();
}
And here is an implementation of such an interface:
public class EntityRepositoryHibernate<Entity extends Object, EntityId>
implements Serializable,
EntityRepository<Entity, EntityId> {
private static final long serialVersionUID = 1L;
#Inject
protected EntityManager entityManager;
protected Class<Entity> entityClass;
public EntityRepositoryHibernate(Class<Entity> entityClass) {
this.entityClass = entityClass;
}
public EntityManager getEntityManager() {
return entityManager;
}
#Override
public Entity add(Entity entity) {
entityManager.persist(entity);
return entity;
}
#SuppressWarnings("unchecked")
#Override
public Entity byId(EntityId id) {
DetachedCriteria criteria = criteriaDAO.createDetachedCriteria(entityClass);
criteria.add(Restrictions.eq("id", id));
return (Entity)criteriaDAO.executeCriteriaUniqueResult(criteria);
}
#Override
public void remove(Entity entity) {
if(entity==null)
return;
entityManager.remove(entity);
}
#Override
public void removeById(EntityId id) {
remove(byId(id));
}
#Override
public List<Entity> toList() {
throw new UnsupportedOperationException("toList() not implemented in "+entityClass.getName());
}
#Override
public void save() {
entityManager.flush();
}
}
All methods are working fine, except save(), so this is the focus here.
As far as I understand, Hibernate is able to track all changes in any instance returned by a query (the byId() method). So, the idea of the save() method is to save any instances that where retrieved and changed, that's why the method does not receives any parameters, it is supposed to save everything that has to be saved (which means, any persistent instance that was retrived and somehow updated while the repository lives.
In a possible scenario, I could call byId() 10 times to retrieve 10 different instances and change only 4 of them. The idea is that by calling save() once, those 4 instances would be saved in the data server.
Problem is when I call flush() I receive an exception stating that there is no transaction active. Since I'm using a JTA persistence unit, it's illegal to open the transation programatically by calling entityManager.getTransaction().
Considering that, what to do to fix the code?
First of all, it seems that your are missunderstanding the purpose of EntityManager.flush method. It doesn't commit any changes managed by persistence context, just sends SQL instructuions to the database. I mean, for the same JTA transaction, when you retrieve and modify some entity instances the changes/SQL instructions are cached waiting to be sent to the database. If the underlying transaction is commited this changes are flushed to the database along with the commit instruction. If you invoke flush before transaction is commited, only flush the changes until the invokation point (well, some SQL instruction could have been flushed previously by reasons out of this matter) but not the commit instruction is sent.
How to fixed?
I suggest you to don't mix Repository Pattern with Transaction manipulation.
Looks like you are using Container Managed Transactions (javaee tutorial) so just erase the save method and let container to manage the transactions. This will change your focus, you now have to care about rolling back transactions (throwing exception or invoking setRollbackOnly) but you don't need to explicit commmit.
In my code, I did as follows:
queried for a course entity
populate it with the given course data.
courseDao.update(entity) which internally calls persist(entity) method.
Surprisingly, the data is got updated successfully.
I am confused with this behaviour of persist method.
Please help me out.
code is as below:
//My Service......
#Service("myService")
#Transactional
public class MyServiceImpl implements MyService {
#Transactional(rollbackFor = { Throwable.class })
public void updateCourse(final Course course) throws MyServiceException {
------
------
CourseEntity courseEntity = courseDao.findById(course.getId());
populateCourseEntity(courseEntity, course);
courseDao.update(courseEntity);
}
}
//CourseDao.....
public class CourseDaoImpl implements CourseDao {
--------
public void update(final T entity) throws MyDaoException {
if (entity != null) {
this.entityManager.persist(entity);
}
else {
String errMsg = "Object to be updated cannot be null.";
throw new MyDaoException(errMsg);
}
}
}
When an entity is currently managed (attached to a session), all updates to it are directly reflected to the underlying storage even without calling persist().
In your case, you load your entity, so it's in the session. Then even if you don't call persist() it will be updated in the database on transaction commit.
The persist() description from the javadoc:
Make an entity instance managed and persistent.
This means that the method doesn't do anything in your case, since your entity is both persistent and managed.
P.S. Where I say "session", understand "entity manager"
JPA tries very hard to be a helpful API, such that anything you get from it (or save to it) will subsequently be tracked by JPA. This means than any further changes will be automatically handled for you by JPA without any additional work on your part.