JPA / 休眠单向一对一映射,具有共享主键

2022-09-04 04:12:21

我很难尝试获得单向的一对一关系来使用JPA(提供程序:Hibernate)。在我看来,这应该不会太麻烦,但显然JPA / Hibernate不同意这一点;-)

问题是我必须映射一个我无法更改的旧架构,并且该架构使用两个实体之间的共享主键,该主键同时是一个实体的外键。

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

数据库如下所示:

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)),我会期待一个RunstException。

任何帮助都非常感谢!


答案 1

对于您描述的数据库架构,您可以在依赖类(子类)上使用@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;
}

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

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


答案 2

因为那个需要从孩子那里回溯父母,这是不可取的

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

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

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

如果我尝试持久化一个“独立”的子级(例如entityManager.persist(aChild)),我会期待一个RunstException。

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


推荐