Java JPA/Hibernate具有共享主键的单向一对一映射

Java JPA/Hibernate具有共享主键的单向一对一映射,java,hibernate,jpa,jakarta-ee,one-to-one,Java,Hibernate,Jpa,Jakarta Ee,One To One,我很难与JPA(提供者:Hibernate)建立单向的一对一关系。在我看来,这不应该太麻烦,但显然JPA/Hibernate不同意;-) 问题是,我必须映射一个不能更改的遗留模式,并且该模式在两个实体之间使用一个共享主键,而该主键同时是一个实体的外键 我创建了一个简单的测试用例: DB如下所示: CREATE TABLE PARENT (PARENT_ID Number primary key, Message varchar2(50)); CREATE TABLE CHILD (CHILD_

我很难与JPA(提供者:Hibernate)建立单向的一对一关系。在我看来,这不应该太麻烦,但显然JPA/Hibernate不同意;-)

问题是,我必须映射一个不能更改的遗留模式,并且该模式在两个实体之间使用一个共享主键,而该主键同时是一个实体的外键

我创建了一个简单的测试用例:

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;
    }
}
父项(=一对一的拥有方)如下所示:

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扩展

我对正确行为的期望是: 如果我尝试将父对象与子对象连接在一起:

  • 从序列中获取ID,在父级上设置它
  • 持久化父级
  • 在子对象上设置父对象的ID
  • 顽童
  • 如果我试图持久化一个“独立”子级(例如entityManager.persist(aChild)),我会期望出现运行时异常

    非常感谢您的帮助

    因为需要从child返回到parent,这是不可取的

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

    也就是说,我想说,解决这个问题的自然方法是让孩子有一个父母

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

    如果我试图持久化一个“独立”子级(例如entityManager.persist(aChild)),我会期望出现运行时异常


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

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

    @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的另一个示例。

    此问题的解决方案是在父实体上使用@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
        }
    }
    

    我还没有尝试过,但它可能会起作用,因为您在getter上添加了注释,这应该告诉Hibernate使用setter填充pojo。您可以尝试在setParentId()和setChild()方法中设置子ID(如果它还没有)。不,这不起作用:IdentifierGenerationException:在调用save()之前必须手动分配该类的ID。您使用哪个版本的Hibernate?@axtavt:我查看了maven poms。显然有多个hibernate版本,分别是3.2.6ga和3.3.0.SP1。我需要花一段时间才能弄清楚使用的版本,因为这个项目中的maven设置有点麻烦。我会给你发帖的。你必须删除父目录中@OneToOne中的cascade属性。有关单向一对一关系,请参见我的答案(您必须手动处理级联操作)。另外,单独添加child将导致异常,因为没有为其分配id。Partenon,谢谢您的回答。是否确定在从DB序列接收到父id后将填充子id的EmbeddedId?OP不希望在子id中引用父id。他想要单向的一对一的解决方案,我认为必须手动处理。我的想法在这里啊,哎呀。由于某种原因,希望它是单向的这一点未能注册。此示例显示了父级上的PrimaryKeyJoinColumn注释,但在web上的其他任何地方,我都看到它仅在子级中使用。对此仍然感到困惑。给出了一个非常明确的实现问题的方法:使用共享主键的单向一对一这是不正确的,因为请求问题是映射单向的。为什么孩子必须认识父母?这是没有必要的,我总是避免在类之间耦合。