【问题标题】:JPA / Hibernate unidirectional one-to-one mapping with shared primary key具有共享主键的 JPA / Hibernate 单向一对一映射
【发布时间】:2011-06-12 23:57:27
【问题描述】:

我很难尝试建立单向的一对一关系来使用 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 不提供解决方案,我也可以使用休眠扩展。

我对正确行为的期望是: 如果我尝试坚持带孩子的父母:

  1. 从序列中获取 ID,设置在父节点上
  2. 坚持父母
  3. 为孩子设置父母的 ID
  4. 坚持孩子

如果我尝试持久化一个“独立”子级(例如 entityManager.persist(aChild)),我预计会出现 RuntimeException。

非常感谢任何帮助!

【问题讨论】:

  • 我还没有尝试过,但它可能会起作用,因为您在 getter 上添加了注释,这应该告诉 Hibernate 使用 setter 填充您的 POJO。您可以尝试在 setParentId() 和 setChild() 方法中设置子 ID(如果还没有)。
  • 不,这不起作用:IdentifierGenerationException:必须在调用 save() 之前手动分配此类的 ID
  • 你使用哪个版本的 Hibernate?
  • @axtavt:我查看了 maven poms。显然那里有多个休眠版本,分别是 3.2.6ga 和 3.3.0.SP1。我需要一段时间才能弄清楚使用的版本,因为这个项目中的 maven 设置有点麻烦。我会及时通知你
  • 您必须删除父级中@OneToOne 中的级联属性。请参阅我对单向一对一关系的回答(您必须手动处理级联操作)。单独添加孩子也会导致异常,因为没有为其分配 id。stackoverflow.com/questions/7892571/…

标签: java hibernate jpa jakarta-ee one-to-one


【解决方案1】:

对于您描述的 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 节的最后一位 here 以获取 @MapsId 的另一个示例。

【讨论】:

  • OP 不想在孩子中引用父母。他想要单向的一对一解决方案,我认为必须手动处理。我的想法在这里stackoverflow.com/questions/7892571/…
  • 啊,哎呀。关于希望它是单向的那一点由于某种原因未能注册。
  • 这个例子显示了父级上的 PrimaryKeyJoinColumn 注释,但在网络上的其他任何地方我都看到它只在子级中使用。仍然对此感到困惑。
  • This tutorial 提供了一种非常清晰的方法来实现所要求的:与共享主键的单向一对一
  • 这是不正确的,因为请求问题是单向的。为什么孩子一定要认识父母?没必要,我总是避免在类之间耦合。
【解决方案2】:

因为需要从子级反向引用父级,这是不可取的

好吧,如果 Child 只能在有 Parent 的情况下存在,那么它们之间就存在 关系。您可能只是不想在 OO 中表达,但它确实存在于关系模型中。

也就是说,我想说解决这个问题的自然解决方案是在孩子中有一个父母。

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

如果我尝试持久化一个“独立”子级(例如 entityManager.persist(aChild)),我预计会出现 RuntimeException。

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

【讨论】:

  • 帕特农,感谢您的回答。您确定在从 DB 序列收到父 ID 后会填充子的 EmbeddedId?
【解决方案3】:

这个问题的解决方法是在父Entity上使用@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
    }
}

【讨论】:

    猜你喜欢
    • 2021-05-26
    • 1970-01-01
    • 2014-11-22
    • 2020-03-09
    • 2011-05-08
    • 1970-01-01
    • 2016-09-18
    • 2012-09-02
    • 2022-01-03
    相关资源
    最近更新 更多