Search code examples
javajpajpa-2.0openjpa

OpenJPA OneToMany and composite key in parent and child table


I have tables with composited primary key.

Server(key=ServerId)
ServerId|Name
1       |server1
2       |server2
ParentObj(key=ServerId+Code)
ServerId|Code |Title
1       |code1|value1
1       |code2|value2
2       |code1|Value2b
ChildObj(key=ServerId+Code+Name)
ServerId|Code |Name |Value
1       |code1|prop1|val1
1       |code1|prop2|val2
1       |code2|prop1|val1b
2       |code1|prop3|val3

This is Java beans I have.

@Entity @Table(name="ParentObj") @Access(AccessType.FIELD)
@IdClass(value=ParentObj.PK.class)
@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
@XmlRootElement
public class ParentObj {
   @Id private long serverId;
   @Id private String code;
   private String title;

   public long getServerId() { return serverId; }
   public String getCode() { return code; }
   public String getTitle() { return title; }

   public static class PK implements Serializable {
     private static final long serialVersionUID = 1L;       
     private long serverId;
     private String code;

     public long getServerId() { return serverId; }
     public void setServerId(long id) { serverId=id; }
     public String getCode() { return code; }
     public void setCode(String code) { this.code=code; }
  }
}

@Entity @Table(name="ChildObj") @Access(AccessType.FIELD)
@IdClass(value=ChildObj.PK.class)
@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
@XmlRootElement
public class ChildObj {
   @Id private long serverId;
   @Id private String code;
   @Id private String name;
   private String value;
   // public getter+setters for each field

   public static class PK implements Serializable {
     private static final long serialVersionUID = 1L;       
     private long serverId;
     private String code;
     private String name;

     public long getServerId() { return serverId; }
     public void setServerId(long id) { serverId=id; }
     public String getCode() { return code; }
     public void setCode(String code) { this.code=code; }
     public String getName() { return name; }
     public void setName(String name) { this.name=name; }
  }
}

I have been trying "everything" to create OneToMany mapping(ParentObj->ChildObj) but nothing seem to work. I don't need ManyToOne(ParentObj<-ChildObj) link but that's ok if one must be defined.

This is a legacy database so I cannot insert an auto_increment identity column or create extra join table between parent and childs.

This annotation is conceptually what I want but multiple join columns is not accepted by OpenJPA2.x library.

// from parent to zero or more childs
@OneToMany(fetch=FetchType.LAZY)
@JoinColumns({
    @JoinColumn(name="server_id", referencedColumnName="server_id"),
    @JoinColumn(name="code", referencedColumnName="code")
})
private List<ChildObj> properties;

Edit, answer OneToMany, ManyToOne and EmbeddedId annotations works. I have only tried reading existing rows but its fine for now. Later I try update+insert+delete tasks.

public class ParentObj {
   @EmbeddedId ParentObj.PK pk;
   @OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL, mappedBy="parent", orphanRemoval=true)
   private List<ChildObj> childs;

   public PK getPK() { return pk; }
   public void setPK(PK pk) { this.pk=pk; }
   public List<ChildObj> getChilds() { return childs; }
   ...

   @Embeddable @Access(AccessType.FIELD)
   public static class PK implements Serializable {    
       private static final long serialVersionUID = 1L;     
       @Column(nullable=false) private long serverId;
       @Column(nullable=false) private String code;
       ..getters+setters+hashCode+equals functions
   }
}

public class ChildObj {
   @EmbeddedId ChildObj.PK pk;
   @ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST, optional=false)
      @JoinColumns({
      @JoinColumn(name="serverid", referencedColumnName="serverid", nullable=false), 
      @JoinColumn(name="code", referencedColumnName="code", nullable=false)
   })   
   private ParentObj parent;

   public PK getPK() { return pk; }
   public void setPK(PK pk) { this.pk=pk; }
   public long getServerId() { return pk.getServerId(); }
   public String getCode() { return pk.getCode(); }
   public String getName() { return pk.getName(); }
   ...

   @Embeddable @Access(AccessType.FIELD)
   public static class PK implements Serializable {
      private static final long serialVersionUID = 1L;      
      @Column(nullable=false) private long serverId;
      @Column(nullable=false) private String code;
      @Column(nullable=false) private String name;
      ..getters+setters+hashCode+equals functions
   }
}

Solution

  • The easiest way to do this is to create an association from ChildObj to ParentObj similar to the following:

    @ManyToOne(fetch = FetchType.LAZY, optional = true)
    @JoinColumns({
        @JoinColumn(name = "serverId", referencedColumnName = "serverId"),
        @JoinColumn(name = "code", referencedColumnName = "code")})
    private ParentObj parentObj;
    

    and then define the @OneToMany association in ParentObj like this:

    @OneToMany(mappedBy = "parentObj", fetch=FetchType.LAZY)
    private List<ChildObj> children;
    

    I would also recommend that you define your composite keys as @Embeddable classes, used as @EmbeddedId references in the Entities. These embeddable PK classes should be separate classes (not inner classes), as you will use them separately to query the related Entities, and serialisation of inner classes can cause problems