JPA @OneToMany → родительский - ссылка на ребенка (внешний ключ)
У меня есть вопрос о ссылках ParentEntities от Child Entites 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 ...
}
И Child.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:
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 ...
}
И Child.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
Спасибо за ответы.
Ответы
Ответ 1
Должен ли я действительно делать это? как это?
Да, это одна стратегия.
В двунаправленных отношениях существует "владеющая" и "не владеющая" сторона отношений. Поскольку собственная сторона в вашем случае находится на Child
, вам необходимо установить там отношения, чтобы она сохранялась. Собственная сторона обычно определяется тем, где вы указываете @JoinColumn
, но не похоже, что вы используете эту аннотацию, поэтому, скорее всего, это вытекает из того факта, что вы использовали mappedBy
в аннотации Parent
.
Вы можете прочитать намного больше об этом здесь.
Ответ 2
Да, это так. JPA не заботится о согласованности графика вашего объекта. Особенно вы должны установить его на стороне владельца двунаправленных отношений (в вашем случае с родительским атрибутом Child).
В спецификации JPA 2.0 говорится следующее:
Обратите внимание, что это приложение несет ответственность за сохраняя согласованность отношений времени выполнения - например, для обеспечения того, чтобы "одна" и "много" сторон двунаправленного отношения согласуются друг с другом, когда приложение обновляет отношения во время выполнения.
Ответ 3
Мы столкнулись с проблемой, сохраняя простой граф объектов, как показано выше. Запуск во 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)
Ответ 4
Кажется, все так. В родительском Entity
вы можете иметь что-то вроде
@PrePersist
private void prePersist() {
children.forEach( c -> c.setParent(this));
}
чтобы избежать повторения кода для установки отношений child/parent в другом месте в коде.
Ответ 5
Если я получу вас правильно, в соответствии с EntityManager
, если вы хотите, чтобы он управлял заказом вставки транзакции, вам нужно "сказать ему", что он также должен удержать детей. И вы этого не делаете, поэтому "он" не знает, что нужно сохранить, но ваш родительский список дочерних элементов не пуст, поэтому "он" считает, что он имеет правильное значение, но сохраненное значение равно null.
Итак, вам стоит подумать о чем-то вроде:
... begin, etc
em.persist(child)
em.persist(parent)
сделайте то, что вы хотите, с родительским объектом здесь, затем зафиксируйте, и это должно работать и для подобных случаев.