Is it possible to remove parent/child entities without refresh?

#1

We have a big entity object tree in our project. If I use the entityManager.remove(childEntity) method to remove a child entity a entityManager.refresh(parentEntity) is necessary in order to have an updated parent/child object tree in memory. But entityManager.refresh is too slow for our project. Do you know a way to remove a child entity from a parent entity without a entityManager.refresh call after the entityManager.remove operation?

I have searched for a solution with the following unit test (See testRemoveVariant2()). But my approach does not work correctly ([ObjectDB 2.5.7_02] javax.persistence.RollbackException Failed to commit transaction: (error 613)). Do you have an idea?

package example;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.FetchType;
import javax.persistence.FlushModeType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.MapKey;
import javax.persistence.OneToMany;
import javax.persistence.Persistence;

import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

public class ObjectDbRemoveTest {
@Entity
public static class ParentEntity {
  @Id
  @GeneratedValue
  private long id;

  @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true)
  @MapKey(name = "childName")
  private Map<String, ChildEntity> children = new HashMap<String, ChildEntity>();

  public Map<String, ChildEntity> getChildren() {
   return children;
  }

  public void addChild(ChildEntity child) {
   child.setParent(this);
   children.put(child.getChildName(), child);
  }

  public long getId() {
   return id;
  }

  public void setId(long id) {
   this.id = id;
  }
}

@Entity
public static class ChildEntity {

  @Id
  @GeneratedValue
  private long id;

  @ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH })
  private ParentEntity parent;

  @Column
  private String childName;

  public ChildEntity() {
  }

  public ChildEntity withChildName(String value) {
   childName = value;
   return this;
  }

  public ParentEntity getParent() {
   return parent;
  }

  public void setParent(ParentEntity parent) {
   this.parent = parent;
  }

  public String getChildName() {
   return childName;
  }

  public void setChildName(String childName) {
   this.childName = childName;
  }

  public long getId() {
   return id;
  }

  public void setId(long id) {
   this.id = id;
  }
}

private static EntityManager createEntityManager(String dbFileName, boolean delete) {

  EntityManagerFactory emf = null;
  if (delete) {
   new File(dbFileName).delete();
   new File(dbFileName + "$").delete();
  }
  emf = Persistence.createEntityManagerFactory("objectdb:" + dbFileName);

  EntityManager entityManager = emf.createEntityManager();
  entityManager.setFlushMode(FlushModeType.AUTO);

  return entityManager;
}

private static void closeEntityManager(EntityManager entityManager) {
  if (entityManager != null && entityManager.isOpen()) {
   entityManager.close();
   entityManager.getEntityManagerFactory().close();
  }
}

private static EntityManager entityManager;

@BeforeClass
public static void setUp() throws Exception {
  entityManager = createEntityManager("d:\\Temp\\RemoveTest.odb", true);

  ParentEntity parentEntity = new ParentEntity();
  entityManager.getTransaction().begin();
  entityManager.persist(parentEntity);
  parentEntity.addChild(new ChildEntity().withChildName("childEntity1"));
  parentEntity.addChild(new ChildEntity().withChildName("childEntity2"));
  parentEntity.addChild(new ChildEntity().withChildName("childEntity3"));
  parentEntity.addChild(new ChildEntity().withChildName("childEntity4"));
  entityManager.getTransaction().commit();
}

@AfterClass
public static void tearDown() throws Exception {
  closeEntityManager(entityManager);
}

@Test
public void testRemoveVariant1() {
  ParentEntity parentEntity = entityManager.find(ParentEntity.class, Long.valueOf(1));

  entityManager.getTransaction().begin();
  entityManager.remove(parentEntity.getChildren().get("childEntity1"));
  entityManager.getTransaction().commit();

  Assert.assertEquals(4, parentEntity.getChildren().size());
  entityManager.refresh(parentEntity); // Reload new state from database
  Assert.assertEquals(3, parentEntity.getChildren().size());
}

@Test
public void testRemoveVariant2() {
  ParentEntity parentEntity = entityManager.find(ParentEntity.class, Long.valueOf(1));
  entityManager.refresh(parentEntity);

  entityManager.getTransaction().begin();
  ChildEntity childEntity2 = parentEntity.getChildren().get("childEntity2");
  entityManager.remove(childEntity2);
  parentEntity.getChildren().remove("childEntity2");
  entityManager.getTransaction().commit();

  Assert.assertEquals(2, parentEntity.getChildren().size());
  entityManager.refresh(parentEntity);
  Assert.assertEquals(2, parentEntity.getChildren().size());
}
}
#2

In order to understand the problem, could you please explain in which way data in memory is not up to date after remove and how exactly refresh solves it?

 

ObjectDB Support
ObjectDB - Fast Object Database for Java (JPA/JDO)
#3

If you remove a child entity of the parent entity with entityManager.remove the parent entity still has the wrong number of child elements in memory. After the call entityManger.refresh(parentEntity) the removed childEntity ist not any more available in the parent entity. You can see that in the method testRemoveVariant1(). Before the entityManager.refresh call the parent entity has the wrong number of child entities.

In the following code fragment you can see a modified version of the testRemoveVariant1(). Before the entityManger.refresh call the number of child entities is 4 and not 3. This is the reason why I need the refresh call.

@Test
public void testRemoveVariant1() {
  ParentEntity parentEntity = entityManager.find(ParentEntity.class, Long.valueOf(1));

  entityManager.getTransaction().begin();
  entityManager.remove(parentEntity.getChildren().get("childEntity1"));
  entityManager.getTransaction().commit();

  Assert.assertEquals(4, parentEntity.getChildren().size());
  entityManager.refresh(parentEntity); // Reload new state from database
  Assert.assertEquals(3, parentEntity.getChildren().size());
}
#4

According to the JPA instructions the application should update both sides of the relationship, rather than calling refresh after updating one side (although in practice refresh is also an option), e.g.

@Test
public void testRemoveVariant1() {
  ParentEntity parentEntity = entityManager.find(ParentEntity.class, Long.valueOf(1));

  entityManager.getTransaction().begin();
  ChildEntity child = parentEntity.getChildren().get("childEntity1");
  entityManager.remove(child);
  entityManager.getTransaction().commit();

  Assert.assertEquals(4, parentEntity.getChildren().size());
  parentEntity.getChildren().remove(child);
  Assert.assertEquals(3, parentEntity.getChildren().size());
}

Is it possible in your application?

ObjectDB Support
ObjectDB - Fast Object Database for Java (JPA/JDO)
#5

Thank you for your proposal. Unfortunately, this is not possible for our application because the entityManager.getTransaction().commit() is the last statement in our application. If the call parentEntity.getChildren().remove("childEntity1") is before entityManager.getTransaction().commit() I got an objectdb exception (error 613).

I have updated the unit test in the first item of this forum thread because in the unit test was a mistake. Please run the unit test again and you can see what I mean.

#6

Your revised test case demonstrates an ObjectDB bug in handling inverse (mapped by) collections and maps in reflection mode (note that the test passes successfully if the classes are enhanced, and it is always recommended to use enhanced classes, which are more efficient).

Build 2.6.0_01 fixes the bug. Thank you for this report.

ObjectDB Support
ObjectDB - Fast Object Database for Java (JPA/JDO)

Post Reply

To post a reply and/or subscribe to update notifications - please login