ObjectDB ObjectDB

Should derived fields of an EmbeddedId be manually maintained by app code?

#1

If I have an Entity which has a primary key which is derived and I am implementing it using @EmbeddedId, am I responsible for maintaining the derived fields of the @EmbeddedId?
I could not find anything in the JPA 2.0 spec https://download.oracle.com/auth/otn-pub/jcp/persistence-2.0-fr-oth-JSpec/persistence-2_0-final-spec.pdf?e=1317047713&h=54831c176e81a244a4c309e13eba0b27 which tells me what to expect.

I have a container class EContainer that is reponsible for maintaining a "map" of items, of type ECompoundKeyMapItem, identified by their "key" within the EContainer class.
The items (ECompoundKeyMapItem) are responsible similarly for maintaining a "map" of subitems, of type "ECompoundKeyMapSubItem", identified by their keys.
The embeddedId for the item class, ECompoundKeyMapItemId, contains a field, "cont", that is derived from ECompoundKeyMapItem's container field via the @MapsId("cont") annotation.
Likewise the ECompoundKeyMapSubItem class has a PK which is an embeddedId "ECompoundKeyMapSubItemId" class, which contains a field "prnt" which is derived from ECompoundKeyMapSubItem's field parent.


In the following as the code stands I do NOT maintain the derived fields in the embeddedIds explicitly and the test fails where it attempts to find an item with an @EmbeddedId ...

  ECompoundKeyMapItem item = container.getMapItem(1, em);
  if(item == null) {
   throw new TestException("cannot find ECompoundKeyMapItem 1");
  }


However it does not fail on Eclipselink.


If I alter the code so that I do maintain the EmbeddedId explicitly it works;

class ECompoundKeyMapItem ....

public void setSingleton(EContainer singleton) {
  this.id.cont = singleton.id;
  this.container = singleton;
}
 


But if I use the principle that the @EmbeddedId must be maintained for all derived fields I get an error with the ECompoundKeyMapSubItem class

class ECompoundKeyMapSubItem ....
public void setParent(ECompoundKeyMapItem parent) {
  this.id.prnt = parent.id;
  this.parent = parent;
}

[ObjectDB 2.3.0_01] Unexpected exception (Error 990)
  Generated by Java HotSpot(TM) Client VM 1.6.0_27 (on Windows Server 2008 6.0).
Please report this error on http://www.objectdb.com/database/issue/new
com.objectdb.o.InternalException: null
com.objectdb.o.InternalException
at com.objectdb.o.VUT.e(VUT.java:220)
at com.objectdb.o.VUT.e(VUT.java:147)
at com.objectdb.o.UMR.s(UMR.java:418)
at com.objectdb.o.UMR.q(UMR.java:374)
at com.objectdb.o.UML.s(UML.java:486)
at com.objectdb.o.MMM.X(MMM.java:793)
at com.objectdb.o.OBM.bx(OBM.java:384)
at com.objectdb.o.OBM.bx(OBM.java:249)
at com.objectdb.jpa.EMImpl.persist(EMImpl.java:375)
at uk.co.his.test.embeddedCompoundKeys.ECompoundKeyMapItem.mapSubItemsPutNew(ECompoundKeyMapItem.java:125)
at uk.co.his.test.embeddedCompoundKeys.ECompoundKeyMapItem.addSubItem(ECompoundKeyMapItem.java:115)
at uk.co.his.test.embeddedCompoundKeys.EContainer.generateMapItems(EContainer.java:57)
at uk.co.his.test.MainCompoundKeys.runEmbeddedContainTest(MainCompoundKeys.java:92)
at uk.co.his.test.MainCompoundKeys.main(MainCompoundKeys.java:148)
Unexpetced error com.objectdb.o.InternalException

//Code without manual maintenance of derived fields in @EmbeddedId classes...

import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.FlushModeType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Query;
import javax.persistence.Version;

import uk.co.his.test.TestException;


@Entity

@NamedQueries({
  @NamedQuery(name = "numberOfMapItems", query = "SELECT COUNT(x) FROM ECompoundKeyMapItem x WHERE x.container = :container") })
public class EContainer  {

public static EContainer getInstance(EntityManager em) {
  EContainer c = em.find(EContainer.class, "1");
  if (c == null) {
   c = new EContainer();
   em.persist(c);
  }
  return c;
}


@Id
protected String id = "1";


@SuppressWarnings("unused")
// used by JPA

@Version
private int version;


@Basic
protected java.lang.Integer lastInstanceInMap;

protected EContainer() { // Empty constructor for JPA
}

public void generateMapItems(Integer numberToGen, boolean subItems, EntityManager em)
   throws TestException {
  if (lastInstanceInMap == null) {
   lastInstanceInMap = 0;
  }
  int endCnt = lastInstanceInMap + numberToGen;
  int startCnt = lastInstanceInMap;
  long timing = System.currentTimeMillis();
  while (endCnt > startCnt) {
   startCnt++;
   String key = "" + startCnt;
   ECompoundKeyMapItem item = mapItemsPutNew(key, new ECompoundKeyMapItem(startCnt), em);
   ECompoundKeyMapSubItem subItem = item.addSubItem(em, key);
   put(key, subItem);
   lastInstanceInMap = startCnt;
   if(startCnt % 1000 == 0) {
    System.out.println("Created 1000 items " + startCnt + " in " + (System.currentTimeMillis() - timing) + "ms");
    timing = System.currentTimeMillis();
   }
  }
}

private ECompoundKeyMapItem mapItemsPutNew(java.lang.String key,
   ECompoundKeyMapItem newObj, EntityManager em) throws TestException {
  newObj.setUUID();
  newObj.setSingleton(this);
  newObj.setCachedKey(key);
  em.persist(newObj);
  return newObj;
}

private void put(java.lang.String key, ECompoundKeyMapSubItem o) {
  EContainer oldReferer = o.getOtherContainerRelationship();
  if (oldReferer != null) {
   oldReferer.remove(o);
  }
  o.setOtherContainerRelationship(this);
  o.setOtherRelationshipKey(key);
}

private void remove(ECompoundKeyMapSubItem o) {
  o.setOtherContainerRelationship(null);
  o.setOtherRelationshipKey(null);
}

public ECompoundKeyMapItem getMapItem(Integer cachedKey, EntityManager em) {
  return em.find(ECompoundKeyMapItem.class, new ECompoundKeyMapItem.ECompoundKeyMapItemId(cachedKey.toString(), id));
}

public Integer getLastMapInstanceKey() {
  return lastInstanceInMap;
}

public int getNumberOfMapItems(EntityManager em) {
  Query q = em
    .createNamedQuery("numberOfMapItems");
  q.setParameter("container", this);
  q.setFlushMode(FlushModeType.AUTO);
  try {
   long l = (Long) q.getSingleResult();
   if (l > Integer.MAX_VALUE)
    return -1;
   int i = (int) l;
   return i;
  } catch (javax.persistence.NoResultException ex) {
   return -1;
  }
}
}

import java.io.Serializable;

import javax.persistence.Basic;
import javax.persistence.Embeddable;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.FetchType;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
import javax.persistence.Version;


@Entity
public class ECompoundKeyMapItem  {


@Embeddable
public static class ECompoundKeyMapItemId implements Serializable {
  private static final long serialVersionUID = 0L;
  private String cachedKey;
  private String cont;
  public ECompoundKeyMapItemId() {}
  public ECompoundKeyMapItemId(String id, String parent) {
   this.cachedKey = id;
   this.cont = parent;
  }
 
  @Override
  public boolean equals(Object o) {
   if(!(o instanceof ECompoundKeyMapItemId)) return false;
   ECompoundKeyMapItemId other = (ECompoundKeyMapItemId) o;
   return cont.equals(other.cont) && cachedKey.equals(other.cachedKey);
  }
  @Override
  public int hashCode() {
   //Follow bloch Item 8.
   int result = 17;
   result = 37*result + cachedKey.hashCode();
   result = 37*result + cont.hashCode();
   return result;
  }
  public String getCachedKey() {
   return cachedKey;
  }
  public String getSingleton() {
   return cont;
  }
}


@EmbeddedId public ECompoundKeyMapItemId id;


@ManyToOne(fetch=FetchType.LAZY)

@MapsId("cont")
private EContainer container;

public EContainer getSingleton() {
  return container;
}


@Version
private int version;


@Basic
private Integer property;


@SuppressWarnings("unused") //JPA
private ECompoundKeyMapItem() {
}

public ECompoundKeyMapItem(int startCnt) {
  id = new ECompoundKeyMapItemId();
  property = startCnt;
}

public void setUUID() {
  //Do nothing we do not have a separate ID
}

public Integer getJPAVersion() {
  return version;
}

public void setSingleton(EContainer singleton) {
  this.container = singleton;
}

public void setCachedKey(String key) {
  this.id.cachedKey = key;
}

public String getCachedKey() {
  return this.id.cachedKey;
}

public void setProperty(Integer newValue) {
  property = newValue;
}

public Integer getProperty() {
  return property;
}

public String getInfo() {
  return "ECompoundKeyMapItem: id=" + getId() + ", version=" + version + ", key=" + id.cachedKey + ", property=" + property;
}

public String getId() {
  return container.id + "/" + id.cachedKey;
}

  public ECompoundKeyMapSubItem addSubItem(EntityManager em, String key) {
   ECompoundKeyMapSubItem newObj = mapSubItemsPutNew(key, em);
     return newObj;
  
  }

private ECompoundKeyMapSubItem mapSubItemsPutNew(String key, EntityManager em) {
   ECompoundKeyMapSubItem newObj = new ECompoundKeyMapSubItem(key);
      newObj.setParent(this);
      newObj.setSubKey(key);
      //em.persist(this);
      em.persist(newObj);
      //em.flush();
      return newObj;
}
}

import java.io.Serializable;

import javax.persistence.Basic;
import javax.persistence.Embeddable;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
import javax.persistence.Version;

import uk.co.his.test.embeddedCompoundKeys.ECompoundKeyMapItem.ECompoundKeyMapItemId;


@Entity
public class ECompoundKeyMapSubItem  {


@Embeddable
public static class ECompoundKeyMapSubItemId implements Serializable {
  private static final long serialVersionUID = 0L;
  private String subKey;
  private ECompoundKeyMapItemId prnt;
  public ECompoundKeyMapSubItemId() {}
  public ECompoundKeyMapSubItemId(String id, ECompoundKeyMapItemId parent) {
   this.subKey = id;
   this.prnt = parent;
  }
 
  @Override
  public boolean equals(Object o) {
   if(!(o instanceof ECompoundKeyMapSubItemId)) return false;
   ECompoundKeyMapSubItemId other = (ECompoundKeyMapSubItemId) o;
   return prnt.equals(other.prnt) && subKey.equals(other.subKey);
  }
  @Override
  public int hashCode() {
   //Follow bloch Item 8.
   int result = 17;
   result = 37*result + subKey.hashCode();
   result = 37*result + prnt.hashCode();
   return result;
  }
  public String getSubKey() {
   return subKey;
  }
  public ECompoundKeyMapItemId getParent() {
   return prnt;
  }
 
}


@EmbeddedId
private ECompoundKeyMapSubItemId id;


@ManyToOne(fetch=FetchType.LAZY)

@MapsId("prnt")
private ECompoundKeyMapItem parent;


@Version
private int version;


@Basic
private String property;


// Attributes which mark another non containment relationship

@ManyToOne(fetch=FetchType.LAZY)
private EContainer otherContainerRelationship;


@Basic
private String otherRelationshipKey;

 

@SuppressWarnings("unused")//JPAs
private ECompoundKeyMapSubItem() {}

public ECompoundKeyMapSubItem(String property) {
  id = new ECompoundKeyMapSubItemId();
  this.property = property;
}

public void setUUID() {
  //Do nothing we do not have a separate ID
}

public Integer getJPAVersion() {
  return version;
}

public ECompoundKeyMapItem getParent() {
  return parent;
}

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

public String getSubKey() {
  return id.subKey;
}

public void setSubKey(String subKey) {
  this.id.subKey = subKey;
}

public void setProperty(String newValue) {
  property = newValue;
}

public String getProperty() {
  return property;
}

public EContainer getOtherContainerRelationship() {
  return otherContainerRelationship;
}

public void setOtherContainerRelationship(EContainer otherContainerRelationship) {
  this.otherContainerRelationship = otherContainerRelationship;
}

public String getOtherRelationshipKey() {
  return otherRelationshipKey;
}

public void setOtherRelationshipKey(String otherRelationshipKey) {
  this.otherRelationshipKey = otherRelationshipKey;
}

public String getInfo() {
  return "ECompoundKeyMapItem: id=" + parent.getId() + "/" + id.subKey + ", version=" + version + ", key=" + id.subKey + ", property=" + property;
}
}

Here is the test...
public static void runEmbeddedContainTest(String puName, int numItems) throws TestException {
  EntityManagerFactory emf = Persistence.createEntityManagerFactory(puName);
  EntityManager em = emf.createEntityManager();
  em.getTransaction().begin();
 
  EContainer container = EContainer.getInstance(em);
  long begin = System.currentTimeMillis();
  container.generateMapItems(numItems, false, em);
  long beginCommit = System.currentTimeMillis();
  em.getTransaction().commit();
  long end = System.currentTimeMillis();
  System.out.println("Commit " + numItems + " in " + (end - beginCommit) + " milliseconds");
  System.out.println("Created " + numItems + " in " + (end - begin) + " milliseconds");
  System.err.flush();
  System.out.flush();
 
  int numberOfMapItems = container.getNumberOfMapItems(em);
  if(numberOfMapItems != numItems) {
   throw new TestException("Created " + numberOfMapItems + " not " + numItems + " items");
  }
  ECompoundKeyMapItem item = container.getMapItem(1, em);
  if(item == null) {
   throw new TestException("cannot find ECompoundKeyMapItem 1");
  }
  if(item.getSingleton() != container) {
   throw new TestException("Container is not as expected: " + container);
  }
  if(!"1".equals(item.getCachedKey())){
   throw new TestException("Item key is not as expected: 1");
  }
  item = container.getMapItem(numItems, em);
  if(item == null) {
   throw new TestException("cannot find ECompoundKeyMapItem " + numItems);
  }
  if(item.getSingleton() != container) {
   throw new TestException("Container is not as expected: " + container);
  }
  if(!(numItems+"").equals(item.getCachedKey())){
   throw new TestException("Item key is not as expected: " + numItems);
  }
  System.out.println("Done Checks");
  System.err.flush();
  System.out.flush();
}

 

edit
delete
#2

It would be very helpful if you follow the posting instructions and simplify this test case:

  1. Remove any unnecessary code. Do you really need 3 entity classes and 2 embeddable classes to demonstrate the problem? What about all the getter and setter methods? the version field? etc. Just remove whatever possible.
  2. Remove external dependency (e.g. uk.co.his.test.TestException)
  3. Provide one main class with a runnable main method and use static inner classes for the persistable types.
  4. Explain the actual output of the test case vs the expected output and what has failed.

Thank you for posting the test case, but it could be much more valuable with 50 lines of codes instead of about 400.

ObjectDB Support
edit
delete
#3

Here is a simplified test case.

1) I do need 3 entity classes as this shows an Entity with an EmbeddedId where the containing Entity has a simple Primary Key, and a case which shows an Entity whose container has a Primary Key which is partly a Primary Key.

2) I have kept the Entity classes in separate files apart from the top container entity which has the main method and this make the whole thing simpler to read.

3) If you run the test case 

    a) With DEMO_MODE set to DemoMode.WORKS_MAINTENANCE everything runs fine and I see the following output.

    Purged DB

    Success

    b) With DEMO_MODE set to DemoMode.NO_EMBEDDED_MAINTENANCE I get an exception;

Purged DB
[ObjectDB 2.3.0_01] Unexpected exception (Error 990)
  Generated by Java HotSpot(TM) Client VM 1.6.0_27 (on Windows Server 2008 6.0).
Please report this error on http://www.objectdb.com/database/issue/new
com.objectdb.o.InternalException: null
com.objectdb.o.InternalException
at com.objectdb.o.VUT.e(VUT.java:220)
at com.objectdb.o.VUT.e(VUT.java:147)
at com.objectdb.o.UMR.s(UMR.java:418)
at com.objectdb.o.UMR.q(UMR.java:374)
at com.objectdb.o.UML.s(UML.java:486)
at com.objectdb.o.MMM.X(MMM.java:793)
at com.objectdb.o.OBM.bx(OBM.java:384)
at com.objectdb.o.OBM.bx(OBM.java:249)
at com.objectdb.jpa.EMImpl.persist(EMImpl.java:375)
at uk.co.his.test.embeddedCompoundKeysSimplified.ECompoundKeyMapItem.addSubItem(ECompoundKeyMapItem.java:84)
at uk.co.his.test.embeddedCompoundKeysSimplified.EContainer.addMapItem(EContainer.java:36)
at uk.co.his.test.embeddedCompoundKeysSimplified.EContainer.main(EContainer.java:76)

 

    c) With DEMO_MODE set to DemoMode.ALL_EMBEDDED_MAINTENANCE I get an exception;

Purged DB
[ObjectDB 2.3.0_01] Unexpected exception (Error 990)
  Generated by Java HotSpot(TM) Client VM 1.6.0_27 (on Windows Server 2008 6.0).
Please report this error on http://www.objectdb.com/database/issue/new
com.objectdb.o.InternalException: null
com.objectdb.o.InternalException
at com.objectdb.o.VUT.e(VUT.java:220)
at com.objectdb.o.VUT.e(VUT.java:147)
at com.objectdb.o.UMR.s(UMR.java:418)
at com.objectdb.o.UMR.q(UMR.java:374)
at com.objectdb.o.UML.s(UML.java:486)
at com.objectdb.o.MMM.X(MMM.java:793)
at com.objectdb.o.OBM.bx(OBM.java:384)
at com.objectdb.o.OBM.bx(OBM.java:249)
at com.objectdb.jpa.EMImpl.persist(EMImpl.java:375)
at uk.co.his.test.embeddedCompoundKeysSimplified.ECompoundKeyMapItem.addSubItem(ECompoundKeyMapItem.java:84)
at uk.co.his.test.embeddedCompoundKeysSimplified.EContainer.addMapItem(EContainer.java:36)
at uk.co.his.test.embeddedCompoundKeysSimplified.EContainer.main(EContainer.java:76)

 

My question is "What is the correct way to manage an EmbeddedId's state, when that state is derived from an Object Reference via @MapsId"?

edit
delete
#4
edit
delete
#5

You have to maintain the Ids, i.e. the correct mode in your test program is DemoMode.ALL_EMBEDDED_MAINTENANCE.

The exception indicates a bug that following your report has been fixed (in build 2.3.0_03). Thank you for this report and for the test case. Unfortunately you may discover other problems when using such complex ID structures, since they are not commonly used by ObjectDB users. Please do not hesitate to report any strange behavior.

Two unrelated comments:

  1. Why do you use the purgeObjectDb method instead of the new ;drop url parameter?
  2. To paste code in the forum, first select the Java Code style and then paste (as explained in the lines below the editor). The order is important, otherwise the blocks may be split to blocks.
ObjectDB Support
edit
delete
#6

Thanks for you reply and the speedy fix.

1) Its just old code from a test harness

2) I shall use it in the future. 

edit
delete

Reply

To post on this website please sign in.