java JPA/Hibernate 单向一对一映射,共享主键

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4756596/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 07:53:08  来源:igfitidea点击:

JPA / Hibernate unidirectional one-to-one mapping with shared primary key

javahibernatejpajakarta-eeone-to-one

提问by Korgen

I'm having a very hard time trying to get a unidirectional one-to-one relationship to work with JPA (Provider: Hibernate). In my opinion this should not be too much of a hassle but apparently JPA / Hibernate disagrees on that ;-)

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

The problem is that I have to map a legacy schema which I cannot change and that this schema uses a shared primary key between two entities which at the same time is the foreign key for one entity.

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

I created a simple TestCase:

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

DB looks as follows:

数据库如下所示:

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;

The parent(=owning side of one-to-one) looks as follows:

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

@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;
    }
}

The 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;
    }
}

I totally see the problem that JPA does not know how to assign the id for the child. However I also tried using Hibernates "foreign" key Generator with also no success because that one needs to have a back reference to the parent from child which is not desirable. This problem does not seem too uncommon to me, so what am I missing here? Is there a solution at all? I can also use hibernate extensions if pure JPA does not provide a solution.

我完全看到了 JPA 不知道如何为孩子分配 id 的问题。但是,我也尝试使用 Hibernates“外来”密钥生成器,但也没有成功,因为它需要从子代对父代进行反向引用,这是不可取的。这个问题对我来说似乎并不罕见,那么我在这里错过了什么?有解决办法吗?如果纯 JPA 不提供解决方案,我也可以使用休眠扩展。

My expectations for a correct behavior would be: If I try to persist the parent with a child attached:

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

  1. get ID from sequence, set it on the parent
  2. persist parent
  3. set parent's ID on child
  4. persist child
  1. 从序列中获取 ID,将其设置在父级上
  2. 坚持父母
  3. 在孩子上设置父母的 ID
  4. 坚持孩子

If I try to persist a "standalone" child (e.g. entityManager.persist(aChild)) I would expect a RuntimeException.

如果我尝试保留一个“独立”的孩子(例如 entityManager.persist(aChild)),我会期待一个 RuntimeException。

Any help is greatly appreciated!

任何帮助是极大的赞赏!

回答by Tom Tresansky

For the db schema you described, you can use @MapsIdannotation on the dependent class (your Child class) to achieve the mapping back to the parent, like so:

对于您描述的 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;
}

Adding the mapping from parent to child you use the @PrimaryKeyJoinColumn annotation as you had listed, making the complete bi-directional one-to-one mapping look like this:

添加从父级到子级的映射,您使用您列出的 @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;
}

I used field rather than method access (and removed anything extraneous to the relationships), but it would be the same annotations applied to your getters.

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

Also see the last bit of section 2.2.3.1 herefor another example of @MapsId.

另请参阅此处第 2.2.3.1 节的最后一点以获取@MapsId 的另一个示例。

回答by jpkrohling

because that one needs to have a back reference to the parent from child which is not desirable

因为那个人需要从孩子那里反向引用父母,这是不可取的

Well, if a Child can only exist if there's a Parent, then there isa relationship between them. You may just not want to express in OO, but it does exist in the relational model.

好吧,如果如果有一个父项的子只能存在,再有就是它们之间的关系。您可能只是不想在 OO 中表达,但它确实存在于关系模型中。

That said, I would say that the natural solution for this is to have a Parent in the Child.

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

But if you really don't want to do that, I would suggest taking a look at mapping the ID as a PK class, and share them with both classes, using an @EmbeddedId. I'm pretty sure it would solve your problem, with one exception:

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

If I try to persist a "standalone" child (e.g. entityManager.persist(aChild)) I would expect a RuntimeException.

如果我尝试保留一个“独立”的孩子(例如 entityManager.persist(aChild)),我会期待一个 RuntimeException。

If you decide to use the @EmbeddedId approach in a PK class, I think you'll need to handle the above case as a "business rule".

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

回答by Vivek Singh

The solution of this problem is using @PostPersist annotation on the parent Entity. You have to create a method in the parent entity class and annotate it with @PostPersist, so this method will be invoked after parent entity is persist, and in this method just set the id of the child entity class. See the example below.

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