views:

1279

answers:

2

Hi all,

I searched there, and didn't find any similar topic, so I am posting a new question.

I am working with Hibernate on an existing Database. The table structure and data we are not allowed to change. The application is reading data from the database and migrating to another datastore based on some logic.

Now the problem is about a composite PK mapping. e.g.

Table A has a composite PK.

Table A
--------
a1 (pk)
a2 (pk)
a3 (pk)
a4 (pk)
foo
bar
========

Table B has a composite PK too, and one part of this composite PK is A's PK, here is working as FK as well.

Table B
--------
a1 (fk,pk)
a2 (fk,pk)
a3 (fk,pk)
a4 (fk,pk)
b1 (pk)
b2 (pk)
b3 (pk)
foo
bar
========

I tried several ways, and none of them works. Can anyone tell a working Hibernate mapping solution? better in annotation style.

Thanks a lot.

Kent

+1  A: 

Set up A's entity object as a @ManyToOne in B's pk Class.

So if you have

Class A
Class APK - A's Primary Key

Class B
Class BPK - B's primary Key.

BPK will contain A as an attribute

@Embeddable
public class BPK implements serializable {
  ....
  private A a;

  @ManyToOne(fetch=FetchType.EAGER)
  @JoinColumns ({
    @JoinColumn(name="...", referencedColumnName = "..."),
    @JoinColumn(name="...", referencedColumnName = "..."),
    ...
  })
  public getA() {
    return this.a;
  }
}

From the documentation

@Embeddable inherit the access type of its owning entity unless the Hibernate specific annotation @AccessType is used. Composite foreign keys (if not using the default sensitive values) are defined on associations using the @JoinColumns element, which is basically an array of @JoinColumn. It is considered a good practice to express referencedColumnNames explicitly. Otherwise, Hibernate will suppose that you use the same order of columns as in the primary key declaration.

Vinodh Ramasubramanian
@Vinodh Ramasubramania Hi, it is not a good idea to use an Entity as primary key. Hibernate Team does recommend. You can not restrict a query result in HQL or Criteria across a Entity used as primary key. Therefore, There are a lot of ways you can avoid to use it.
Arthur Ronald F D Garcia
But I am not using a Entity as PK. I am using it (A) to establish a ManyToOne relationship with the join conditions in the PK of B. In your solution I do not see how the Composite PK of B (BPK) is defined with the Composite PK of A (APK) as a subset to it.
Vinodh Ramasubramanian
The oneToMany in your BPK class helps me. I didn't think in that direction. I just kept thinking how to reference another APK class in BPK. And don't know how to set the annotation. For a convenient a.getBset(), I modeled another One2Many with joincolumns in A class. e.g. private Set<TypeB> bSet;. in TypeB class, also a manyToOne, pointing to TypeA. of course insertable, updatable = false are needed.Thank you guys.
Kent
A: 

Hi,

If your compound primary key have only surrogate keys, use @EmbeddableId

@Embeddable
public class CompoundIdA implements Serializable {

    private Integer field0;
    private Integer field1;
    private Integer field2;
    private Integer field3;

    @Column(name="FIELD_0")
    public Integer getField0() {
        return this.field0;
    }

    @Column(name="FIELD_1")
    public Integer getField1() {
        return this.field1;
    }

    @Column(name="FIELD_2")
    public Integer getField2() {
        return this.field2;
    }

    @Column(name="FIELD_3")
    public Integer getField3() {
        return this.field3;
    }

    public boolean equals(Object o) {
        if(o == null)
            return false;

        if(!(o instanceof CompoundIdA))
            return false;

        final CompoundIdA other = (CompoundIdA) o;
        if(!(getField0().equals(other.getField0()))
            return false;

        if(!(getField1().equals(other.getField1()))
            return false;

        if(!(getField2().equals(other.getField2()))
            return false;

        if(!(getField2().equals(other.getField2()))
            return false;

        return true;
    }

    // hashcode impl

}

In ClassA, we have

@Entity
public class ClassA {

    private CompoundIdA compoundIdA;

    @EmbeddedId
    public CompoundIdA getCompoundIdA() {
        return this.CompoundIdA;
    }

}

If your compound primary key have both natural and surrogate keys, use again @EmbeddableId

// Let's suppose field0 and field1 are both natural keys
@Entity
public class ClassA {

    private CompoundIdA compoundIdA;

    private Integer field0;
    private Integer field1;

    @EmbeddedId
    public CompoundIdA getCompoundIdA() {
        return this.CompoundIdA;
    }

    @Column(name="FIELD_0", insertable=false, updateable=false)
    public Integer getField0() {
        return this.field0;
    }

    @Column(name="FIELD_1", insertable=false, updateable=false)
    public Integer getField1() {
        return this.field1;
    }

}

Notice you have to set up insertable=false and updateable=false because more than one property share the same column. Otherwise, Hibernate will complain some errors.

If your compound primary key have only natural keys, use @IdClass

@Entity
@IdClass(CompoundIdA.class)
public class ClassA {

    private Integer field0;
    private Integer field1;
    private Integer field2;
    private Integer field3;

    @Id
    @Column(name="FIELD_0")
    public Integer getField0() {
        return this.field0;
    }

    @Id
    @Column(name="FIELD_1")
    public Integer getField1() {
        return this.field1;
    }

    @Id
    @Column(name="FIELD_2")
    public Integer getField2() {
        return this.field2;
    }

    @Id
    @Column(name="FIELD_3")
    public Integer getField3() {
        return this.field3;
    }

}

In ClassB, you can use the same approach as shown above, but if you want to define a @ManyToOne property, you have to set up insertable=false and updateable=false as follows

@Entity
public class ClassB {

    private ClassA classA;

    @ManyToOne
    @JoinColumns ({
        @JoinColumn(name="FIELD_0", referencedColumnName="FIELD_0", insertable=false, updateable=false),
        @JoinColumn(name="FIELD_1", referencedColumnName="FIELD_1", insertable=false, updateable=false),
        @JoinColumn(name="FIELD_2", referencedColumnName="FIELD_2", insertable=false, updateable=false),
        @JoinColumn(name="FIELD_3", referencedColumnName="FIELD_3", insertable=false, updateable=false)
    })
    public ClassA getClassA() {
        return this.classA;
    }

}

regards,

Arthur Ronald F D Garcia
I really do not see much difference between your answer and mine. Except you have used alternate annotations for the same things I have outlined.
Vinodh Ramasubramanian