JPA @OneToMany -> Родитель - дочерняя ссылка (внешний ключ) - PullRequest
42 голосов
/ 02 марта 2012

У меня есть вопрос по поводу ссылки на ParentEntities из дочерних объектов ir Если у меня есть что-то вроде этого:

Parent.java:

@Entity(name ="Parent")
public class Parent {
    @Id
    @Generate.....
    @Column
    private int id;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "parent")
    private Set<Child> children;

    simple ... getter and setter ...
}

И Дитя.java:

@Entity(name ="Child")
public class Child{
    @Id
    @Generate....
    @Column
    private int id;

    @ManyToOne
    private Parent parent;

    ... simple getter an setter
}

Будут созданы следующие таблицы:

Parent:
     int id

Child:
     int id
     int parent_id (foreign key: parent.id)

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

 @Transactional
 public void test() {
    Parent parent = new Parent();

    Child child = new Child();
    Set<Child> children = new HashSet<Child>();
    children.add(child);

    parent.setChildren(children);
    entityManager.persist(parent);
  }

что приводит к этому в базе данных:

Parent:
     id
     100

Child
     id     paren_id
     101    100

Но это не тот случай, вы должны просто установить Parent на Child (что, я думаю, каркас мог бы сделать сам).

Итак, что на самом деле в базе данных это:

Parent:
     id
     100

Child
     id     paren_id
     101    (null)

потому что я не назначил Родителя Ребенку. Итак, мой вопрос:

Должен ли я действительно делать что-нибудь? как это?

Parent.java:

...
setChildren(Set<Child> children) {
   for (Child child : children) {
     child.setParent.(this);
   }

   this.children = children;
}
...

Edit:

Согласно быстрым ответам, я смог решить эту проблему с помощью @JoinColumn на объекте-владельце ссылок. Если мы возьмем пример сверху, я сделал что-то. как это:

Parent.java:

  @Entity(name ="Parent")
    public class Parent {
        @Id
        @Generate.....
        @Column
        private int id;

        @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
        @JoinColumn(name= "paren_id")
        private Set<Child> children;

        simple ... getter and setter ...
    }

И Дитя.java:

@Entity(name ="Child")
public class Child{
    @Id
    @Generate....
    @Column
    private int id;

    ... simple getter an setter
}

Теперь, если мы сделаем это:

 @Transactional
 public void test() {
    Parent parent = new Parent();

    Child child = new Child();
    Set<Child> children = new HashSet<Child>();
    children.add(child);

    parent.setChildren(children);
    entityManager.persist(parent);
  }

Ссылка правильно установлена ​​родителем:

Parent:
     id
     100

Child
     id     paren_id
     101    100

Спасибо за ответы.

Ответы [ 5 ]

14 голосов
/ 02 марта 2012

Должен ли я действительно делать что-нибудь? как это?

Это одна стратегия, да.

В двунаправленных отношениях существует "владеющая" и "не владеющая" сторона отношений. Поскольку сторона-владелец в вашем случае находится на Child, вам нужно установить отношения там, чтобы она сохранялась. Сторона-владелец обычно определяется тем, где вы указываете @JoinColumn, но это не похоже на то, что вы используете эту аннотацию, поэтому, скорее всего, это связано с тем, что вы использовали mappedBy в аннотации Parent. 1011 *

Вы можете прочитать об этом подробнее здесь .

3 голосов
/ 02 марта 2012

Да, это так. JPA не заботится о согласованности вашего графа сущностей. Особенно вы должны установить его на стороне владельца двунаправленных отношений (в вашем случае на родительский атрибут Child).

В спецификации JPA 2.0 это сказано следующими словами:

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

2 голосов
/ 22 ноября 2017

Кажется, все еще так.В родительском Entity у вас может быть что-то вроде

@PrePersist
private void prePersist() {
   children.forEach( c -> c.setParent(this));
}

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

2 голосов
/ 15 августа 2014

Мы столкнулись с проблемой при сохранении простого графа объектов, подобного показанному выше.При запуске в H2 все работало бы, но когда мы работали с MySQL, «paren_id» в дочерней таблице (определенной в аннотации @JoinColumn) не заполнялся сгенерированным идентификатором родителя - даже если он был установлен как нестолбец с ограничением внешнего ключа в БД.

Мы получили бы исключение, подобное этому:

org.hibernate.exception.GenericJDBCException: Field 'paren_id' doesn't have a default value

Для всех, кто мог бы столкнуться с этим, мы в конечном итоге обнаружили, что у нас был другой атрибут @JoinColumn длязаставить его работать:

@JoinColumn(name="paren_id", nullable=false)
0 голосов
/ 08 апреля 2016

Если я получаю вас правильно, в соответствии с EntityManager, если вы хотите, чтобы он управлял порядком вставки транзакции, вы должны "сказать ему", что она должна сохраняться и для детей. И вы этого не делаете, поэтому «он» не знает, что сохранить, но список дочерних элементов вашего родителя не пуст, поэтому «он» считает, что он правильный, но сохраненное значение равно нулю.

Так что вы должны подумать о том, чтобы сделать что-то вроде:

... begin, etc
em.persist(child)
em.persist(parent)

делайте то, что вы хотите, с родительским объектом здесь, затем фиксируйте, и это должно работать и в подобных случаях.

...