简体   繁体   中英

How can I do a JPA @OneToMany unidirectional mapping with the parent entity's composite key?

Currently these are only query operations where if you "get" the parent, you want to eager fetch all the children. It is legacy a legacy database (DB2) where there are three fields in the parent table that are its composite PK. The child table has those three fields and one more as its composite PK.

No need for bidirectional. Just need to get the children along with parent.

@Entity @IdClass(ParentId.class)
class Parent {
    @Id int someId
    @Id int someCode
    @Id Date someDate

    @OneToMany(fetch = FetchType.EAGER, ???)
    ???
    List<Child> children
}

class ParentId implements Serializable {
    int someId
    int someCode
    Date someDate
}

@Entity @IdClass(ChildId.class)
class Child {
    @Id int someId
    @Id int someCode
    @Id Date someDate
    @Id String childValue
    ...
}

class ChildId implements Serializable {
    int someId
    int someCode
    Date someDate
    String childValue
}

ChildId class is pretty much like the parent one plus another field.

Do I need to switch to @Embeddable and @EmbeddableId etc.? Regardless, anyone have ideas how to make this work? I've seen examples that are more simplistic and don't seem to work for me.

I can change the Parent and Child classes but not the tables and their current composite PKs.

You can use either an @IdClass or @EmbeddedId . Either way, you are using "Derived identities". Here is a possible solution using @IdClass :

@Entity
@IdClass(ParentId.class)
public class Parent {
    @Id int someId;
    @Id int someCode;
    @Id Date someDate;

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "parent")
    List<Child> children;
}

public class ParentId implements Serializable {
    int someId;
    int someCode;
    Date someDate;
    ...
}

@Entity
@IdClass(ChildId.class)
public class Child {
    @Id
    @ManyToOne
    @JoinColumns({
        @JoinColumn(name="PARENT_ID", referencedColumnName="someId"),
        @JoinColumn(name="PARENT_CODE", referencedColumnName="someCode"),
        @JoinColumn(name="PARENT_DATE", referencedColumnName="someDate") 
    })
    Parent parent;
    @Id String childValue;
    ...
}

public class ChildId implements Serializable {
    ParentId parent; // matches name of attribute and type of Parent Id class
    String childValue;
}

Derived identities are discussed in the JPA 2.1 spec, section 2.4.1.

You can continue with @IdClass with the Unidirectional mapping . For that you can add @JoinColumns({...)} along with cascade attribute on the List<Child> children field on the Parent class, as below:

@Entity @IdClass(ParentId.class)
class Parent {
    @Id int someId;
    @Id int someCode;
    @Id Date someDate;

    @OneToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
    @JoinColumns({
        @JoinColumn(name="someId", referencedColumnName="someId"),
        @JoinColumn(name="someCode", referencedColumnName="someCode"),
        @JoinColumn(name="someDate", referencedColumnName="someDate") 
})    
    List<CKChild> children = new ArrayList<>();
}
class ParentId implements Serializable {
    int someId;
    int someCode;
    Date someDate;
}

@Entity @IdClass(ChildId.class)
class Child {
    @Id int someId;
    @Id int someCode;
    @Id Date someDate;
    @Id String childValue;
}

class ChildId implements Serializable {
    int someId;
    int someCode;
    Date someDate;
    String childValue;
}

But then, the better idea would be to go with Bidirection mapping with mappedBy as indicated in the Brian's answer.

It is efficient from DML statement that get executed. With Unidirectional you will see one extra update statement for each children entity in the List<Child> while saving the Parent . And with Bidirectional it doesn't result in this extra update statement.

UPDATE:

My test code to retrieve:

session = openSession();
tx = session.beginTransaction();
System.out.println(session.createQuery("select p from Parent p").list());
tx.commit();

and gives me following

Hibernate: select ckparent0_.someCode as someCode1_1_, ckparent0_.someDate as someDate2_1_, ckparent0_.someId as someId3_1_ from Parent ckparent0_
Hibernate: select children0_.someCode as someCode2_0_0_, children0_.someDate as someDate3_0_0_, children0_.someId as someId4_0_0_, children0_.childValue as childVal1_0_0_, children0_.childValue as childVal1_0_1_, children0_.someCode as someCode2_0_1_, children0_.someDate as someDate3_0_1_, children0_.someId as someId4_0_1_ from Child children0_ where children0_.someCode=? and children0_.someDate=? and children0_.someId=?
Hibernate: select children0_.someCode as someCode2_0_0_, children0_.someDate as someDate3_0_0_, children0_.someId as someId4_0_0_, children0_.childValue as childVal1_0_0_, children0_.childValue as childVal1_0_1_, children0_.someCode as someCode2_0_1_, children0_.someDate as someDate3_0_1_, children0_.someId as someId4_0_1_ from Child children0_ where children0_.someCode=? and children0_.someDate=? and children0_.someId=?
**Retrieved :** [org.hibernate.bugs.Parent@1e17ad20, org.hibernate.bugs.Parent@7111ca49]

With the following DDLs

Hibernate: create table Child (childValue varchar(255) not null, someCode integer not null, someDate timestamp not null, someId integer not null, primary key (childValue, someCode, someDate, someId))
Hibernate: create table Parent (someCode integer not null, someDate timestamp not null, someId integer not null, primary key (someCode, someDate, someId))
Hibernate: alter table Child add constraint FKl06s6kkl5xx2s82tlbsh160vo foreign key (someCode, someDate, someId) references Parent

So first of all, this is Groovy which the above code kinda implies but I didn't explicitly mention. This requires the annotations to use brackets '[]' instead of curly braces '{}' to specify array properies like @JoinColumn.

Beyond that it was simpler than I thought. For the unidirectional relationship with same fields, I didn't need to specify actual column names or referenced column names. Just the field names that were common.

I also switched to a Set and put the @JoinColumns in the order that the keys were specified in the database. These last two things probably don't matter though.

@Entity @IdClass(ParentId.class)
class Parent {
    @Id int someCode
    @Id Date someDate
    @Id int someId

    @OneToMany(fetch = FetchType.EAGER)
    @JoinColumns([@JoinColumn(name = "someCode"),
        @JoinColumn(name = "someDate"),
        @JoinColumn(name = "someId")])
    Set<Child> children
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM