JPA / Hibernate使用共享主键进行单向一对一映射

我正在努力获得与JPA(提供者:Hibernate)一起使用的单向一对一关系。 在我看来,这不应该是一个麻烦,但显然JPA / Hibernate不同意这一点;-)

问题是我必须映射一个我无法更改的遗留模式,并且此模式在两个实体之间使用共享主键,同时它是一个实体的外键。

我创建了一个简单的TestCase:

DB如下所示:

CREATE TABLE PARENT (PARENT_ID Number primary key, Message varchar2(50)); CREATE TABLE CHILD (CHILD_ID Number primary key, Message varchar2(50), CONSTRAINT FK_PARENT_ID FOREIGN KEY (CHILD_ID )REFERENCES PARENT (PARENT_ID)); CREATE SEQUENCE SEQ_PK_PARENT START WITH 1 INCREMENT BY 1 ORDER; 

父母(=一对一的拥有方)如下所示:

 @Entity @Table(name = "PARENT") public class Parent implements java.io.Serializable { private Long parentId; private String message; private Child child; @Id @Column(name = "PARENT_ID", unique = true, nullable = false, precision = 22, scale = 0) @SequenceGenerator(name="pk_sequence", sequenceName="SEQ_PK_PARENT") @GeneratedValue(generator="pk_sequence", strategy=GenerationType.SEQUENCE) public Long getParentId() { return this.parentId; } public void setParentId(Long parentId) { this.parentId = parentId; } @Column(name = "MESSAGE", length = 50) public String getMessage() { return this.message; } public void setMessage(String message) { this.message = message; } @OneToOne (cascade = CascadeType.ALL) @PrimaryKeyJoinColumn(name="PARENT_ID", referencedColumnName="CHILD_ID") public Child getTestOneToOneChild() { return this.child; } public void setTestOneToOneChild(Child child) { this.child = child; } } 

孩子:

 @Entity @Table(name = "TEST_ONE_TO_ONE_CHILD", schema = "EXTUSER") public class Child implements java.io.Serializable { private static final long serialVersionUID = 1L; private Long childId; private String message; public Child() { } public Child(String message) { this.message = message; } @Id @Column(name = "CHILD_ID") public Long getChildId() { return this.childId; } public void setChildId(Long childId) { this.childId = childId; } @Column(name = "MESSAGE", length = 50) public String getMessage() { return this.message; } public void setMessage(String message) { this.message = message; } } 

我完全看到JPA不知道如何为孩子分配id的问题。 然而,我也尝试使用Hibernates“外来”密钥生成器也没有成功,因为那个人需要有一个来自子的父级的后引用,这是不可取的。 这个问题对我来说似乎并不常见,所以我在这里缺少什么? 有解决方案吗? 如果纯JPA不提供解决方案,我也可以使用hibernate扩展。

我对正确行为的期望是:如果我试图坚持父母与附加的孩子:

  1. 从序列中获取ID,将其设置在父级上
  2. 坚持父母
  3. 在孩子身上设置父母的ID
  4. 坚持孩子

如果我试图坚持一个“独立”子(例如entityManager.persist(aChild)),我会期望一个RuntimeException。

任何帮助是极大的赞赏!

对于您描述的db模式,您可以在依赖类(您的Child类)上使用@MapsId注释来实现映射回父级,如下所示:

 @Entity class Parent { @Id @Column(name = "parent_id") @GeneratedValue Long parent_id; } @Entity class Child { @Id @Column(name = "child_id") Long child_id; @MapsId @OneToOne @JoinColumn(name = "child_id") Parent parent; } 

添加从父级到子级的映射,您可以使用列出的@PrimaryKeyJoinColumn批注,使完整的双向一对一映射如下所示:

 @Entity class Parent { @Id @Column(name = "parent_id") @GeneratedValue Long parent_id; @OneToOne @PrimaryKeyJoinColumn(name="parent_id", referencedColumnName="child_id") public Child; } @Entity class Child { @Id @Column(name = "child_id") Long child_id; @MapsId @OneToOne @JoinColumn(name = "child_id") Parent parent; } 

我使用了字段而不是方法访问​​(并删除了与关系无关的任何内容),但这将是应用于getter的相同注释。

另请参阅2.2.3.1的最后一节,了解@MapsId的另一个示例。

因为那个人需要有一个来自孩子的父母的背引用,这是不可取的

好吧,如果一个孩子只有在有父母的情况下才能存在,那么他们之间就存在关系。 您可能不想在OO中表达,但它确实存在于关系模型中。

也就是说,我会说,这样做的自然解决方案是在孩子身上有一个父母。

但是如果你真的不想这样做,我建议你看一下将ID映射为PK类,并使用@EmbeddedId与两个类共享它们。 我很确定它能解决你的问题,但有一个例外:

如果我试图坚持一个“独立”子(例如entityManager.persist(aChild)),我会期望一个RuntimeException。

如果您决定在PK类中使用@EmbeddedId方法,我认为您需要将上述情况作为“业务规则”处理。

此问题的解决方案是在父实体上使用@PostPersist批注。 您必须在父实体类中创建一个方法并使用@PostPersist对其进行注释,因此在父实体持久化之后将调用此方法,并且在此方法中只需设置子实体类的id。 请参阅下面的示例。

 @Entity @Table(name = "PARENT") public class Parent implements java.io.Serializable { private Long parentId; private String message; private Child child; @Id @Column(name = "PARENT_ID", unique = true, nullable = false, precision = 22, scale = 0) @SequenceGenerator(name="pk_sequence", sequenceName="SEQ_PK_PARENT") @GeneratedValue(generator="pk_sequence", strategy=GenerationType.SEQUENCE) public Long getParentId() { return this.parentId; } public void setParentId(Long parentId) { this.parentId = parentId; } @OneToOne (cascade = CascadeType.ALL) @PrimaryKeyJoinColumn public Child getTestOneToOneChild() { return this.child; } public void setTestOneToOneChild(Child child) { this.child = child; } @PostPersist public void initializeCandidateDetailID() { System.out.println("reached here");// for debugging purpose this.child.setChildId(parentId); // set child id here System.out.println("reached here"+Id); // for debugging purpose } }