Search code examples
eclipsejpaeclipse-rcpeclipselinkjpa-2.1

RCP Editor and JPA merge


I am updating an RCP based application to use JPA 2.1 (EclipseLink's implementation) from Hibernate 3.x. The JPA merge behavior is causing me problems. The issue I am seeing is when I assign the returned value to the model object in the RCP editor (inherited from FormEditor) it is a new entity and changes are not being persisted on subsequent saves.

I'm seeking input on how best to update the RCP Editor model with the new object returned from JPA.merge().

Note: I understand the difference between hibernate's saveOrUpdate and JPA's persist.

The two approaches that I see are:

  1. In my service layer do a deep copy from the new entity returned from JPA.merge() into the entity that was passed into the service.save() method and do not make the assignment in the editor.doSave() method.
  2. Associate the model in the RCP editor to the new entity returned from JPA.merge()

The object graph is fairly complex and deep so writing a deep copy is not trivial. However, I don't know how to approach #2 above.

I'm open to any ideas on how to approach #1 / #2 or if there are options #3 - #n that I am missing.

Thanks for taking time to read my question!

Here is my current Editor.doSave method

@Override
public void doSave(IProgressMonitor monitor) {
    this.overviewPage.getDataBindingContext().updateModels();
    this.getActivePageInstance().getManagedForm().commit(true);
    try {
        motor = Activator.getAcMotorEntity().save(motor);
    } catch (BusinessException e) {
        StatusManager.getManager().handle(
                new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Unable to save the motor to the database", e),
                StatusManager.LOG | StatusManager.SHOW);
    }
    if (motor.getId() == 1L) {
        this.setPartName("[DEFAULT VALUES]");
    } else {
        this.setPartName(this.motor.getJobNumber());
    }
    this.setDirty(false);
}

and my service.save() which calls JPA's merge

@Override
public IT save(IT entity) throws BusinessException {
    EntityManager em = factory.createEntityManager();
    IT savedEntity = null;

    getLogger().debug("Save {} with id of {} ", entity.getClass().getName(), ((IDatabaseEntity) entity).getId());
    try {
        em.getTransaction().begin();
        savedEntity = em.merge(entity); // re-attach
        em.getTransaction().commit();
        this.firePropertyChange(entity, savedEntity);
    } catch (OptimisticLockException ole) {
        getLogger().error("Load " + entity.getClass().getName() + " with id of " + ((IDatabaseEntity) entity).getId(), ole);
        throw JpaExceptionFactory.createRecordChanged(ole, entity == null ? 0 : ((IDatabaseEntity) entity).getId(),
                entity == null ? "null entity" : entity.getClass().getName());
    } catch (PersistenceException pe) {
        getLogger().error("Load " + entity.getClass().getName() + " with id of " + ((IDatabaseEntity) entity).getId(), pe);
        if (em.getTransaction().isActive()) {
            em.getTransaction().rollback();
        }
        throw JpaExceptionFactory.createPersistence(pe, entity == null ? 0 : ((IDatabaseEntity) entity).getId(),
                entity == null ? "null entity" : entity.getClass().getName());
    } catch (Throwable t) {
        getLogger().error("Load " + entity.getClass().getName() + " with id of " + ((IDatabaseEntity) entity).getId(), t);
        if (em.getTransaction().isActive()) {
            em.getTransaction().rollback();
        }
        throw JpaExceptionFactory.createUnknownDatabase(t, entity == null ? 0 : ((IDatabaseEntity) entity).getId(),
                entity == null ? "null entity" : entity.getClass().getName());
    } finally {
        if (em != null && em.isOpen()) {
            em.close();
        }
    }

Solution

  • The issue turned out to be that the databinding context that maps the editor fields to the model was only being built when the form was created. This binding continued to point at the original entity.

    The solution is to rebuild the databinding context after the call to motor = Activator.getAcMotorEntity().save(motor); This updated the binding to point to the entity returned from the JPA.merge().