Однонаправленное сопоставление JPA / Hibernate один к одному с общим первичным ключом - PullRequest
12 голосов
/ 21 января 2011

Мне очень тяжело пытаться заставить однонаправленные отношения один-к-одному работать с JPA (Поставщик: Hibernate). По моему мнению, это не должно быть слишком хлопотно, но, по-видимому, JPA / Hibernate не согласны с этим; -)

Проблема в том, что мне нужно отобразить унаследованную схему, которую я не могу изменить, и что эта схема использует общий первичный ключ между двумя сущностями, который одновременно является внешним ключом для одной сущности.

Я создал простой TestCase:

БД выглядит следующим образом:

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 не знает, как назначить идентификатор для ребенка. Однако я также попытался использовать Hibernates Generator «чужого» ключа, но также безуспешно, потому что нужно иметь обратную ссылку на родителя от child, что нежелательно. Эта проблема не кажется мне слишком необычной, так чего мне здесь не хватает? Есть ли решение вообще? Я также могу использовать hibernate-расширения, если чистый JPA не дает решения.

Мои ожидания от правильного поведения были бы: Если я попытаюсь настаивать на родителе с прикрепленным ребенком:

  1. получить идентификатор из последовательности, установить его на родительском
  2. настойчивый родитель
  3. установить идентификатор родителя для ребенка
  4. настойчивый ребенок

Если я попытаюсь сохранить «автономный» дочерний элемент (например, entityManager.persist (aChild)), я ожидаю исключение RuntimeException.

Любая помощь очень ценится!

Ответы [ 3 ]

4 голосов
/ 07 сентября 2012

Для схемы db, которую вы описали, вы можете использовать аннотацию @ 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;
}

Я использовал поле, а не метод доступа (и удалил все, что постороннее для отношений), но это были бы те же аннотации, которые применялись к вашим получателям.

Также см. Последний бит раздела 2.2.3.1 здесь для другого примера @ MapsId.

3 голосов
/ 21 января 2011

потому что нужно иметь обратную ссылку на родителя от потомка, что нежелательно

Что ж, если Дитя может существовать только при наличии Родителя, тогда - это связь между ними. Вы можете просто не хотеть выражать в ОО, но он существует в реляционной модели.

Тем не менее, я бы сказал, что естественным решением для этого является наличие Родителя в Ребенке.

Но если вы действительно не хотите этого делать, я бы посоветовал взглянуть на сопоставление идентификатора как класса PK и поделиться им с обоими классами, используя @EmbeddedId. Я уверен, что это решит вашу проблему, за одним исключением:

Если я попытаюсь сохранить «автономный» дочерний объект (например, entityManager.persist (aChild)), я ожидаю исключение RuntimeException.

Если вы решите использовать подход @EmbeddedId в классе PK, я думаю, вам нужно будет рассматривать вышеупомянутый случай как «бизнес-правило».

0 голосов
/ 02 августа 2017

Решением этой проблемы является использование аннотации @PostPersist на родительском объекте.Вы должны создать метод в классе родительского объекта и аннотировать его с помощью @PostPersist, поэтому этот метод будет вызываться после сохранения родительского объекта, а в этом методе просто установите идентификатор класса дочернего объекта.Смотрите пример ниже.

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