In JPA, when doing bulk operations such as this
update LogEntry e set e.customer = null where e.customer.id = :cid
It is recommended to use a separate entity manager to avoid breaking synchronization, according to this: UPDATE SET Queries in JPA/JPQL
For example, the EntityManager may not be aware that a cached entity object in its persistence context has been modified by an UPDATE query. Therefore, it is a good practice to use a separate EntityManager for UPDATE queries.
How do I create a separate entity manager in a JTA environment such as Wildfly using hibernate? Do I need to create a separate persistence unit for bulk operations?
EDIT: Given I dont need a separate PU for bulk operations, is this a sufficient way of solving it using a new transaction?
@Transactional
public class JpaCustomerRepository implements CustomerRepository {
@Inject
private EntityManager em;
...
@Override
public Customer remove(long id) {
CustomerEntity entity = em.find(CustomerEntity.class, id);
if (entity != null) {
updateLogEntriesToNull(entity);
em.remove(entity);
return entity;
} else {
return null;
}
}
@Transactional(value=TxType.REQUIRES_NEW)
public void updateLogEntriesToNull(CustomerEntity entity) {
em.createNamedQuery(LogEntry.updateCustomerToNull)
.setParameter("cid", entity.getId())
.executeUpdate();
}
...
}
Where LogEntry.updateCustomerToNull is the bulk query.
Answer: This does not work because the interceptor is not invoked when called from inside the same class.
EDIT2: Following the suggestions from Andrei, this should work:
@Transactional
public class JpaCustomerRepository implements CustomerRepository {
public static class BulkUpdater {
@Inject
private EntityManager em;
@Transactional(value=TxType.REQUIRES_NEW)
public void updateLogEntriesToNull(CustomerEntity entity) {
em.createNamedQuery(LogEntry.updateCustomerToNull)
.setParameter("cid", entity.getId())
.executeUpdate();
}
}
@Inject
private EntityManager em;
@Inject
private BulkUpdater bulkUpdater;
...
@Override
public Customer remove(long id) {
CustomerEntity entity = em.find(CustomerEntity.class, id);
if (entity != null) {
bulkUpdater.updateLogEntriesToNull(entity);
em.remove(entity);
return entity;
} else {
return null;
}
}
...
}
Testing confirms that the interceptor gets called twice.
The recommendation is valid only if you also do other stuff with the EntityManager
(when there is a risk of manipulating/reading the same entities as the BULK UPDATE). The easiest solution: make sure that this BULK UPDATE is executed in a separate service, within a new transaction. No need to create a separate PU (persistence unit) for bulk operations.