Search code examples
jpawildflyjtabulkupdate

How do I create a separate entity manager for bulk operations in a JTA environment?


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.


Solution

  • 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.