EJB3 relationships

Version 4

    This page gives some HowTos about handling EJB3 relationships, and it points to some common problems and error messages.
    It is written by a JBoss user, not a guru ;-).
    For questions and comments mail me at: wolfgang DOT knauf AT gmx DOT de

     

    Declaration of relationships

    Datatype

    It sounds easy to use lists (implementations of java.util.List) for the "to many" part of the relationship. This is normally no problem, but in combination with FetchType.EAGER it might result in deployment errors (see below).
    Those problems do not occur, if a java.util.Set is used. In most use cases, this is the better approach, because in a "to many" relation, there will be probably no duplicates. Only if duplicates are allowed, a java.util.List should be used.

    "mappedBy"

    To create a bidirectional relationship, you have to connect the two sides with a "mappedBy" attribute on  one of the two sides of the relation. The "mappedBy" specifies the property name of the other side. The property without "mappedBy" is the defining side of the relation.

    Example:

    @Entity()
    public class ParentBean implements Serializable
    {
      private Set<ChildBean> listChilds = new HashSet<ChildBean>();
     
      @OneToMany(mappedBy="parent")
      public Set<ChildBean> getChilds()
      {
        return this.listChilds;
      }
    }

    @Entity()
    public class ChildBean implements Serializable
    {
      @ManyToOne ()
      public ParentBean getParent()
      {
        return this.parent;
      }
    }

    If you want to specify e.g. a @JoinTable, you must do this on the defining side of the relation. On the "mappedBy" side, this annotation will have no effect (not even an error message).

    FetchType

    There are two fetch types available:

    • FetchType.EAGER: the relationship fields should be fetched immediately when loading the parent field.
    • FetchType.LAZY: the relationship fields should be fetched when necessary (e.g. when they are accessed).

    Both sides have drawbacks, so there is no easy decision for either of them.

    FetchType.EAGER:

    • Benefit: easier to code, because the field is always populated by the server.
    • Drawback: performance: if the parent relationship is loaded, all its relationship fields are populated, too.
    • Drawback: If an entity has more than one relationship field, this exception may occur on publish:
      javax.persistence.PersistenceException: [PersistenceUnit: ...] Unable to build EntityManagerFactory
           at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:677)
           at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:132)
           at org.jboss.jpa.deployment.PersistenceUnitDeployment.start(PersistenceUnitDeployment.java:311)
           ...
      Caused by: org.hibernate.HibernateException: cannot simultaneously fetch multiple bags
           at org.hibernate.loader.BasicLoader.postInstantiate(BasicLoader.java:89)
           at org.hibernate.loader.entity.EntityLoader.(EntityLoader.java:98)
           ...
          
      For an explanation and workarounds see here: http://jroller.com/eyallupu/entry/hibernate_exception_simultaneously_fetch_multiple.
      In short: try to use LAZY where possible, or replace java.util.List collections by java.util.Set.

    FetchType.LAZY:

    • Benefit: performance: on loading the entity, only the data of the entity itself is fetched. No relationship fields are populated.
    • Drawback: the relationship fields can only be lazily loaded when the entity bean is under entity manager control ("attached"). As soon as it becomes "detached",    accessing an unloaded relationship field will result in this exception:
      org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: my.package.ParentBean.childs, no session or session was closed
           org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
           org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372)
           ...
        The workaround for this is to force loading of the relationship fields while it is still attached. This must happen in the session bean where it is loaded.   So, if you have a method "findById" in your session bean, add the bold line:
        public ParentBean findById (Integer int_ParentId)
        {
          ParentBean parentBean = this.entityManager.find(ParentBean.class, int_ParentId);
         
          if (parentBean != null)
          {
            parentBean.getChilds().size();
          }

         
          return parentBean;
        }
        I included a NULL check, because "find" returns NULL if the ID is invalid.   Important: a call to parentBean.getChilds() does not fetch the childs. You have to access the content of the collection to fetch them. I did this by calling "size()".
        
         This prefetching has to be handled with care: check your use cases and verify that preloading makes sense. Otherwise, you might load unnecessary data,    and this slows your app down. 

    Default Values:
    The defaults for the "fetch" attribute differ beetween the different relationship types.

    • @OneToMany: LAZY
    • @ManyToOne: EAGER
    • @OneToOne: EAGER
    • @ManyToMany: LAZY

     

    EntityManager.find versus getReference
    "find" returns NULL if the entity is not found. "getReference" throws a javax.persistence.EntityNotFoundException, if nothing is found.

    There is another major difference beetween the two: "getReference" does not load relationship fields, even if they are set to "EAGER". This results in the  org.hibernate.LazyInitializationException if the entity gets detached and the relationship is accessed. On the other hand, it might improve performance if you have a bit of session bean code, where you don't need the relationship fields.

    CascadeType

    CascadeType.REMOVE
    This is the one which requires most thinking about. It depends on your use case, whether it makes sense to delete the childs, when the parent is deleted. For @ManyToMany-Relationships, a CascadeType.REMOVE is always wrong.

    A CascadeType.REMOVE does not mean "remove connection beetween child and parent, if parent is deleted", it means "delete child".

    Assume you have a one-to-many relation beetween parent and child without CascadeType.REMOVE, and you want to delete a parent with childs, you will see this (quite meaningless) exception in the server console:

    13:50:10,687 WARN  [JDBCExceptionReporter] SQL Error: 0, SQLState: null
    13:50:10,687 ERROR [JDBCExceptionReporter] failed batch
    13:50:10,687 ERROR [AbstractFlushingEventListener] Could not synchronize database state with session
    org.hibernate.exception.GenericJDBCException: Could not execute JDBC batch update
         at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:126)
         at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:114)
         at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
         at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275)
         ...
    Caused by: java.sql.BatchUpdateException: failed batch
         at org.hsqldb.jdbc.jdbcStatement.executeBatch(Unknown Source)
         at org.hsqldb.jdbc.jdbcPreparedStatement.executeBatch(Unknown Source)
         at org.jboss.resource.adapter.jdbc.CachedPreparedStatement.executeBatch(CachedPreparedStatement.java:474)
         ...
    13:50:10,703 WARN  [arjLoggerI18N] [com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator_2] TwoPhaseCoordinator.beforeCompletion - failed for com.arjuna.ats.internal.jta.resources.arjunacore.SynchronizationImple@1fb6021
    javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: Could not execute JDBC batch update
         at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:614)
         at org.hibernate.ejb.AbstractEntityManagerImpl$1.beforeCompletion(AbstractEntityManagerImpl.java:513)
         at com.arjuna.ats.internal.jta.resources.arjunacore.SynchronizationImple.beforeCompletion(SynchronizationImple.java:101)
         at com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.beforeCompletion(TwoPhaseCoordinator.java:263)
         at com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.end(TwoPhaseCoordinator.java:86)
         at com.arjuna.ats.arjuna.AtomicAction.commit(AtomicAction.java:177)
         ...

    "server.log" gave me a bit more of information:

    2008-12-28 13:50:10,687 DEBUG [org.hibernate.util.JDBCExceptionReporter] (http-127.0.0.1-8080-1) Could not execute JDBC batch update [delete from ParentBean where id=?]
    ...(same stacktrace as above)

    If it is intended to keep the child (no cascading of the deletion), then you first have to remove the child from the relationship (see chapter  "Managing the (bidirectional) relationship - Deleting an item" below).


    CascadeType.PERSIST and CascadeType.MERGE
    There is not much to say about them. I think you can set those flags whereever you want without drawbacks.
    Leaving those flags might be a problem, if your use cases allows modification of parent and childs in one step (so that saving the parent should also save the childs). If you dont' activate this flag, then only the parent is saved, but not the childs.


    CascadeType.REFRESH
    Same as above: I think you can set those flags whereever you want without drawbacks.

    CascadeType.ALL
    Combination of all the above, so set it only, if you are sure about  CascadeType.REMOVE.

    Managing the (bidirectional) relationship

    This chapters describes how to handle bidirectional relationships in code.
    The major clue is: always perform actions on both sides of a relation!

     

    My general advice is to place manipulation of relationships in the session bean layer. If you build methods like "addChildToParent(ParentBean parent, ChildBean child)" or "removeChildToParentMapping (ParentBean parent, ChildBean child)", then you have encapsulated the relationship handling in one place. And more important: if  you load the entitities from database in the beginning of this method, you will not have problems with detached entitities.

     

    Adding an item
    Here is a sample for a @OneToMany relationship (a parent has many childs): Two childs shall be added to the parent.

         ParentBean parent = ...;    
         ChildBean child1 = ...;
         parent.getChilds().add(child1);
         child1.setParent(parent);
        
         ChildBean child2 = ...;
         parent.getChilds().add(child2);
         child2.setParent(parent);
        
         this.entityManager.merge(parent);

    Important: add the new child to the child list of the parent, and set the parent in the child.
    If the line child1.setParent(parent); was left, the childs database column "parentid" would be NULL, thus the child would not be connected to the parent.

     

    The same approach is used for @ManyToMany relationships:

         LeftBean left = ...;    
         RightBean right = ...;
         left.getRights().add(right);
         right.getLefts().add(left);
        
         this.entityManager.merge(left);

     

    Removing an item
    If you want to remove an item from a relationship (without deleting either entity bean), you have to perform this code:

         ParentBean parent = ...;    
         ChildBean child = ...;
         parent.getChilds().remove(child);
         child.setParent(null);
        
         this.entityManager.merge(parent);

    This leaves the child in database with the "ParentID" column being NULL.
    If you leave the line child.setParent(null);, you will be surprised to find out the child and parent are still connected.

     

    For a @ManyToMany relationship, it might look like this:

         LeftBean leftBean = this.entityManager.find(LeftBean.class, int_LeftId);
         RightBean rightBean = this.entityManager.find(RightBean.class, int_RightId);
        
         leftBean.getRights().remove(rightBean);
         rightBean.getLefts().remove(leftBean);
        
         this.entityManager.persist(leftBean);

     

    Deleting an item

    If you actually want to delete an item, and you don't cascade removal, you may see two kinds of exceptions, depending on the kind of relationship you use.

    1) Deleting the parent side

    If you delete a parent with childs, you will see the exceptions shown in the above chapter "Declaration of relationships - CascadeType - CascadeType.REMOVE".
    The solution is to first remove the relationship before deleting the object. Here is a sample for a @ManyToMany relationship:

         public void deleteLeft(Integer int_LeftId)
         {
              LeftBean leftBean = this.entityManager.find(LeftBean.class, int_LeftId);
             
              for (RightBean rightBean : leftBean.getRights())
              {
                   rightBean.getLefts().remove(leftBean);
              }
              this.entityManager.remove(leftBean);
         }

    After fetching the LeftBean, I loop over the relation to the RightBean. For each relationship element RightBean,  I remove the LeftBean from the collection. Afterwards, I can persist it.

     

    2) Deleting the child side

    This exception seems to happen only on the "ToOne" side of a "@ManyToOne" relationship.

    javax.ejb.EJBTransactionRolledbackException: Transaction rolled back
         org.jboss.ejb3.tx.Ejb3TxPolicy.handleEndTransactionException(Ejb3TxPolicy.java:54)
         org.jboss.aspects.tx.TxPolicy.endTransaction(TxPolicy.java:175)
         org.jboss.aspects.tx.TxPolicy.invokeInOurTx(TxPolicy.java:87)
         org.jboss.aspects.tx.TxInterceptor$Required.invoke(TxInterceptor.java:190)
         org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102)
         org.jboss.aspects.tx.TxPropagationInterceptor.invoke(TxPropagationInterceptor.java:76)
         org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102)
         ...
         code to your session bean
         ...
    Caused by: javax.transaction.RollbackException: [com.arjuna.ats.internal.jta.transaction.arjunacore.commitwhenaborted] [com.arjuna.ats.internal.jta.transaction.arjunacore.commitwhenaborted] Could not commit transaction.
         com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple.commitAndDisassociate(TransactionImple.java:1426)
         com.arjuna.ats.internal.jta.transaction.arjunacore.BaseTransaction.commit(BaseTransaction.java:135)
         com.arjuna.ats.jbossatx.BaseTransactionManagerDelegate.commit(BaseTransactionManagerDelegate.java:75)
         org.jboss.aspects.tx.TxPolicy.endTransaction(TxPolicy.java:170)
         org.jboss.aspects.tx.TxPolicy.invokeInOurTx(TxPolicy.java:87)
         org.jboss.aspects.tx.TxInterceptor$Required.invoke(TxInterceptor.java:190)
         org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102)
         org.jboss.aspects.tx.TxPropagationInterceptor.invoke(TxPropagationInterceptor.java:76)
         org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102)
         ...
    Caused by: javax.persistence.EntityNotFoundException: deleted entity passed to persist: [org.jboss.wiki.relationships.eager.ChildEagerBean#]
         org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:598)
         org.hibernate.ejb.AbstractEntityManagerImpl$1.beforeCompletion(AbstractEntityManagerImpl.java:513)
         com.arjuna.ats.internal.jta.resources.arjunacore.SynchronizationImple.beforeCompletion(SynchronizationImple.java:101)
         com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.beforeCompletion(TwoPhaseCoordinator.java:263)
         com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.end(TwoPhaseCoordinator.java:86)
         com.arjuna.ats.arjuna.AtomicAction.commit(AtomicAction.java:177)
         com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple.commitAndDisassociate(TransactionImple.java:1414)
         com.arjuna.ats.internal.jta.transaction.arjunacore.BaseTransaction.commit(BaseTransaction.java:135)
         com.arjuna.ats.jbossatx.BaseTransactionManagerDelegate.commit(BaseTransactionManagerDelegate.java:75)
         org.jboss.aspects.tx.TxPolicy.endTransaction(TxPolicy.java:170)

    The solution is the same as above: remove the child from the parents collection first:

      public void deleteChild (Integer int_ChildId)
      {
        ChildBean child = this.entityManager.getReference(ChildBean.class, int_ChildId);
       
        child.getParent().getChilds().remove(child);
       
        this.entityManager.remove(child);
      }

     

    Entity Bean as JoinTable

    This chapter describes how to create a Entity Bean "ParentToChildBean" for a ManyToMany-mapping beetween two other entities named "ParentBean" and "ChildBean" (so that both of those just form an OneToMany to the mapping entity. If the JPA specification allowed us to declare IDs on relation fields, this would be easy, but unfortunately something like this declaration in "ParentToChildBean" is illegal:

     

      @ManyToOne
      @Id
      public Child
    Bean getChild()
      {
        ..
      }
       

    The fields of the Entity:

    So, we have to do use a trick: we declare the relation, and we duplicate the primary key fields of the "ParentBean" and "ChildBean" entities. This sample assumes that both have an Integer Id.

     

      private Integer iParentId;
      private Integer iChildId;
      private Parent parent;
      private Child child;

      @Id
      public Integer getParentId()
      {
        ...
      }

      @Id
      public Integer getChildId()
      {
        ...
      }

     

      @ManyToOne
      public Parent
    Bean getParent()
      {
        ...
      }

      @ManyToOne
      public Child
    Bean getChild()
      {
        ...
      }

      //TODO: setters

     

     

    Cleaning up the database structure:

    This results in one problem: the Id fields result in database columns, and the relationship fields create foreign key columns, too. So, we have to tell the server

    1) to use the same column names for Id fields and relationships and

    2) not to add the relationship fields to insert/update statements.

     

    This is a sample for the "parentId", same applies to the "childId".

     

      @Id
      @Column(name="PARENTID")
      public Integer getParentId()
      {

      }

      @ManyToOne
      @JoinColumn(name="PARENTID", insertable=false, updatable=false)
      public ParentBean getParent()
      {
        ...
      }


    The annotation "@JoinColumn" declares that the relationship columns should map to a column named "PARENTID", which is also the name of the "parentId" field. The attributes "insertable" and "updateable" must be set to "false" so that JBoss does not add them twice to insert/updates. If they were not set, this would result in this error:

      
      java.sql.SQLException: Column count does not match in statement [insert into ParentToChildBean (PARENTID, CHILDID, ..., other fields, ..., PARENTID, CHILDID) values (?, ?, ..., ?, ?)]
         at org.hsqldb.jdbc.Util.throwError(Unknown Source)
         at org.hsqldb.jdbc.jdbcPreparedStatement.(Unknown Source)
         at org.hsqldb.jdbc.jdbcConnection.prepareStatement(Unknown Source)
         ...


     

    Cleaning up code:

    There is one more thing to handle: the id fields may not differ from the relationship field values. So, I advice to

    1) declare getters and setters of the id fields as "private". The relationship shall only be accessed through the one-to-many properties. The private setters are required anyway, because JBoss uses them to set the values on loading the entity. We might have used field based property access to avoid them, but I prefer the property based field access.

    2) the setters of the one-to-many properties must update the id fields!

     

    Here is the full code:

     

    @Entity
    @IdClass(value=ParentToChildPK.class)
    public class ParentToChildEntity implements Serializable
    {
      private Integer iParentId;

      private Integer iChildId;
      private Parent parent;
      private Child child;

      @Id
      @Column(name="PARENTID")
      @SuppressWarnings("unused")
      private
    Integer getParentId()
      {
        return this.iParentId;
      }

      @SuppressWarnings("unused")
      private
    void setParentId(Integer iParentIdNew)
      {
        this.iParentId = iParentIdNew;
      }

      @Id
     
    @Column(name="CHILDID")
      @SuppressWarnings("unused")
      private
    Integer getChildId()
      {
       
    return this.iChildId;
      }

      @SuppressWarnings("unused")
      private void setChildId(Integer iChildIdNew)
      {
        this.iChildId = i
    ChildIdNew;
      }


     

      @ManyToOne
     
    @JoinColumn(name="PARENTID", insertable=false, updatable=false)
      public ParentBean getParent()
      {
        return this.parent;
      }

      public void setParent(ParentBean parentNew)
      {
        this.parent = parentNew
    ;
        if (parentNew != null)
          this.iParentId = parentNew.getId();
        else
          this.iParentId = null;

      }


      @ManyToOne
     
    @JoinColumn(name="CHILDID", insertable=false, updatable=false)
      public ChildBean getChild()
      {
        return this.child;
      }

      public void setChild(ChildBean childNew)
      {
        this.child = childNew
    ;
        if (childNew != null)
          this.iChildId = childNew.getId();
        else
          this.iChildId = null;

      }

    }

     

    ID Class:

    Now we are nearly done. The only thing left is an id class. This one contains an exact copy of the id fields! The names must not differ!

     

    public class ParentToChildPK implements Serializable
    {
      private Integer iParentId;

      private Integer iChildId;

      public Integer getParentId()
      {
        return this.iParentId;
      }

      public void setParentId(Integer iParentIdNew)
      {
        this.iParentId = iParentIdNew;
      }

      public Integer getChildId()
      {
       
    return this.iChildId;
      }

      public void setChildId(Integer iChildIdNew)
      {
        this.iChildId = i
    ChildIdNew;
      }
    }

    The Id class has to be declared on the entity:

     

     

    @Entity()
    @IdClass(
    ParentToChildPK.class)
    public class ParentToChildBean implements Serializable
    {
      ...

     

    We need the Id class because if it was missing,...

    • ...saving a ParentToChildBean would result in this error:
    14:32:44,281 WARN  [JDBCExceptionReporter] SQL Error: 0, SQLState: null
    14:32:44,281 ERROR [JDBCExceptionReporter] failed batch
    14:32:44,281 ERROR [AbstractFlushingEventListener] Could not synchronize database state with session
    org.hibernate.exception.GenericJDBCException: Could not execute JDBC batch update
         at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:126)
         at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:114)
         at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
         at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275)
         at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:266)
         at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:167)
         ...
    Caused by: java.sql.BatchUpdateException: failed batch
         at org.hsqldb.jdbc.jdbcStatement.executeBatch(Unknown Source)
         at org.hsqldb.jdbc.jdbcPreparedStatement.executeBatch(Unknown Source)
         at org.jboss.resource.adapter.jdbc.CachedPreparedStatement.executeBatch(CachedPreparedStatement.java:476)
         at org.jboss.resource.adapter.jdbc.WrappedStatement.executeBatch(WrappedStatement.java:774)
         at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
         at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
         ... 65 more

    If sql logging is activated, the real reason is clear:


      insert into ParentToChildBean (PARENTID, other fields) values (?, ...)

     

    The field "CHILDID" is missing.

     

    • Loading of a "ParentToChildBean" would not be possible, because "EntityManager.getReference" needs a primary key class for concatenated primary keys.

     

    Alternate way: EmbeddedId

    The previous sample had one drawback: the id fields had to be duplicated to entity and primary key class.

     

    Embedded ID class:

    To avoid this, we create an EmbeddedId class. This one is nearly the same as the primary key class:

     

    import javax.persistence.Embeddable;

    @Embeddable
    public class ParentToChildPK implements Serializable
    {
      private Integer iParentId;

      private Integer iChildId;

      @Column(name="PARENTID")
      public Integer getParentId()
      {
        return this.iParentId;
      }

      public void setParentId(Integer iParentIdNew)
      {
        this.iParentId = iParentIdNew;
      }

      @Column(name="CHILDID")
      public Integer getChildId()
      {
       
    return this.iChildId;
      }

      public void setChildId(Integer iChildIdNew)
      {
        this.iChildId = i
    ChildIdNew;
      }
    }

     

    There are two differences to the primary key class:

    • It has the annotation "javax.persistence.Embeddable"
    • The column names are defined on this class (but could be overridden if the embedded id was used for different entities)

     

    Entity "ParentToChildBean":

    The entity code is a bit simpler now:

     

    import javax.persistence.EmbeddedId;
    ...

    @Entity
    public class ParentToChildBean implements Serializable
    {

      private ParentToChildPK pk;
      private Parent parent;
      private Child child;

      @EmbeddedId
      @SuppressWarnings("unused")
      private
    ParentToChildPK getPk()
      {
        return this.pk;
      }
     
      @SuppressWarnings("unused")
      private void setPk(
    ParentToChildPK pk)
      {
        this.pk = pk;
      }


     

      @ManyToOne
     
    @JoinColumn(name="PARENTID", insertable=false, updatable=false)
      public ParentBean getParent()
      {
        return this.parent;
      }

      public void setParent(ParentBean parentNew)
      {
        this.parent = parentNew
    ;

        if (this.pk == null)
        {
          this.pk = new
    ParentToChildPK ();
        }

        if (
    parentNew != null)
        {
          this.pk.setParentId(
    parentNew.getId());
        }
        else
        {
          this.pk.setParentId(null);
        }
      }


      @ManyToOne
     
    @JoinColumn(name="CHILDID", insertable=false, updatable=false)
      public ChildBean getChild()
      {
        return this.child;
      }

      public void setChild(ChildBean childNew)
      {
        this.child = childNew
    ;
       

        if (this.pk == null)
        {
          this.pk = new
    ParentToChildPK ();
        }

        if (
    childNew != null)
        {
          this.pk.setChildId(
    childNew.getId());
        }
        else
        {
          this.pk.setChildId(null);
        }

      }
    }

     

    The embedded id (with annotation "@javax.persistence.EmbeddedId") is a member variable now and has getter and setter. Those are private for the same reasons as in the above sample.

    The relationship fields don't change. But the setter for "child" and "parent" are more complex now, because they have to write the new ID to the embedded id class, and this primary key class variable maybe has to be created first.

     

    But anyway, less code has to be written, and there are no duplicate fields (which would be fatal, if a field was renamed and only one variable was changed).