@OneToMany и составные первичные ключи?
Я использую Hibernate с аннотациями (в spring), и у меня есть объект, у которого есть упорядоченная взаимосвязь "один-к-одному", которая представляет собой дочерний объект, который имеет составной первичный ключ, одним из компонентов которого является внешний ключ возвращается к id родительского объекта.
Структура выглядит примерно так:
+=============+ +================+
| ParentObj | | ObjectChild |
+-------------+ 1 0..* +----------------+
| id (pk) |-----------------| parentId |
| ... | | name |
+=============+ | pos |
| ... |
+================+
Я пробовал различные комбинации аннотаций, ни одна из которых не работает. Это самое близкое, что я смог придумать:
@Entity
public class ParentObject {
@Column(nullable=false, updatable=false)
@Id @GeneratedValue(generator="...")
private String id;
@OneToMany(mappedBy="parent", fetch=FetchType.EAGER, cascade={CascadeType.ALL})
@IndexColumn(name = "pos", base=0)
private List<ObjectChild> attrs;
...
}
@Entity
public class ChildObject {
@Embeddable
public static class Pk implements Serializable {
@Column(nullable=false, updatable=false)
private String parentId;
@Column(nullable=false, updatable=false)
private String name;
@Column(nullable=false, updatable=false)
private int pos;
@Override
public String toString() {
return new Formatter().format("%s.%s[%d]", parentId, name, pos).toString();
}
...
}
@EmbeddedId
private Pk pk;
@ManyToOne
@JoinColumn(name="parentId")
private ParentObject parent;
...
}
Я пришел к этому после долгих приступов экспериментов, в которых большинство моих других попыток дало сущности, которые спящий режим не мог даже загружаться по разным причинам.
ОБНОВЛЕНИЕ: Спасибо всем за комментарии; Я добился определенного прогресса. Я сделал несколько настроек, и я думаю, что это ближе (я обновил код выше). Теперь, однако, проблема заключается в вставке. Родительский объект, кажется, сохраняет штраф, но дочерние объекты не сохраняются, и то, что я смог определить, заключается в том, что спящий режим не заполняет родительскую часть первичного ключа (составного) дочерних объектов, m получить не уникальную ошибку:
org.hibernate.NonUniqueObjectException:
a different object with the same identifier value was already associated
with the session: [org.kpruden.ObjectChild#null.attr1[0]]
Я заполняю атрибуты name
и pos
в своем собственном коде, но, конечно, я не знаю родительский идентификатор, потому что он еще не был сохранен. Любые идеи о том, как убедить спящий режим заполнить это?
Спасибо!
Ответы
Ответ 1
После долгих экспериментов и разочарований я в конце концов решил, что не могу сделать именно то, что хочу.
В конечном счете, я пошел вперед и дал ребенку объект свой собственный синтетический ключ и позволить Hibernate управлять им. Это не идеально, поскольку ключ почти такой же большой, как и остальные данные, но он работает.
Ответ 2
В книге Мэннинга Java Persistence с Hibernate есть пример, описывающий, как это сделать, в Разделе 7.2. К счастью, даже если вы не владеете книгу, вы можете увидеть пример исходного кода этого, загрузив версию JPA в Caveat Emptor образец проекта (прямая ссылка здесь) и изучения классов Category
и CategorizedItem
в auction.model
пакете,
Я также суммирую ключевые аннотации ниже. Дайте мне знать, если это все еще нет.
ParentObject:
@Entity
public class ParentObject {
@Id @GeneratedValue
@Column(name = "parentId", nullable=false, updatable=false)
private Long id;
@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@IndexColumn(name = "pos", base=0)
private List<ChildObject> attrs;
public Long getId () { return id; }
public List<ChildObject> getAttrs () { return attrs; }
}
ChildObject:
@Entity
public class ChildObject {
@Embeddable
public static class Pk implements Serializable {
@Column(name = "parentId", nullable=false, updatable=false)
private Long objectId;
@Column(nullable=false, updatable=false)
private String name;
@Column(nullable=false, updatable=false)
private int pos;
...
}
@EmbeddedId
private Pk id;
@ManyToOne
@JoinColumn(name="parentId", insertable = false, updatable = false)
@org.hibernate.annotations.ForeignKey(name = "FK_CHILD_OBJECT_PARENTID")
private ParentObject parent;
public Pk getId () { return id; }
public ParentObject getParent () { return parent; }
}
Ответ 3
Вы должны включить ссылку ParentObject
только в ChildObject.Pk
, а не отдельно родительский и родительский:
(getters, seters, атрибуты Hibernate, не связанные с отсутствием ключевых слов доступа к элементам и элементам доступа)
class ChildObject {
@Embeddable
static class Pk {
@ManyToOne...
@JoinColumn(name="parentId")
ParentObject parent;
@Column...
String name...
...
}
@EmbeddedId
Pk id;
}
В ParentObject
вы просто положите @OneToMany(mappedBy="id.parent")
, и он работает.
Ответ 4
Во-первых, в ParentObject
, "исправить" атрибут mappedBy
, который должен быть установлен на "parent"
. Также (но это, возможно, опечатка) добавьте аннотацию @Id
:
@Entity
public class ParentObject {
@Id
@GeneratedValue
private String id;
@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@IndexColumn(name = "pos", base=0)
private List<ObjectChild> attrs;
// getters/setters
}
Затем в ObjectChild
добавьте атрибут name
к objectId
в составном ключе:
@Entity
public class ObjectChild {
@Embeddable
public static class Pk implements Serializable {
@Column(name = "parentId", nullable = false, updatable = false)
private String objectId;
@Column(nullable = false, updatable = false)
private String name;
@Column(nullable = false, updatable = false)
private int pos;
}
@EmbeddedId
private Pk pk;
@ManyToOne
@JoinColumn(name = "parentId", insertable = false, updatable = false)
private ParentObject parent;
// getters/setters
}
И также добавить insertable = false, updatable = false
в @JoinColumn
, потому что мы повторяем столбец parentId
при отображении этого объекта.
С этими изменениями сохранение и чтение объектов работает отлично для меня (проверено с помощью Derby).
Ответ 5
Нашел этот вопрос, ища ответ на него, но ответы он не решил мою проблему, потому что я искал @OneToMany
, что не так хорошо подходит для структуры таблицы, в которой я шел. @ElementCollection
- это подходящее место в моем случае. Одна из причин этого, я считаю, это то, что он рассматривает всю строку отношений как уникальную, а не только идентификаторы строк.
@Entity
public class ParentObject {
@Column(nullable=false, updatable=false)
@Id @GeneratedValue(generator="...")
private String id;
@ElementCollection
@CollectionTable( name = "chidren", joinColumns = @JoinColumn( name = "parent_id" ) )
private List<ObjectChild> attrs;
...
}
@Embeddable
public static class ObjectChild implements Serializable {
@Column(nullable=false, updatable=false)
private String parentId;
@Column(nullable=false, updatable=false)
private String name;
@Column(nullable=false, updatable=false)
private int pos;
@Override
public String toString() {
return new Formatter().format("%s.%s[%d]", parentId, name, pos).toString();
}
... getters and setters REQUIRED (at least they were for me)
}
Ответ 6
Кажется, что вы получили довольно близко, и я пытаюсь сделать то же самое в своей текущей системе. Я начал с суррогатного ключа, но хотел бы удалить его в пользу составного первичного ключа, состоящего из родительского ПК и индекса в списке.
Мне удалось получить отношение "один к одному", которое разделяет ПК с главной таблицей с помощью "чужого" генератора:
@Entity
@GenericGenerator(
name = "Parent",
strategy = "foreign",
parameters = { @Parameter(name = "property", value = "parent") }
)
public class ChildObject implements Serializable {
@Id
@GeneratedValue(generator = "Parent")
@Column(name = "parent_id")
private int parentId;
@OneToOne(mappedBy = "childObject")
private ParentObject parentObject;
...
}
Интересно, можете ли вы добавить @GenericGenerator и @GeneratedValue для решения проблемы Hibernate, не назначающей родительский недавно приобретенный ПК во время вставки.
Ответ 7
После сохранения родительского объекта вы должны явно установить parentId в объектах Child, чтобы вставки в объектах Child работали.
Ответ 8
Проведя три дня на этом, я думаю, что нашел решение, но, честно говоря, мне это не нравится, и это определенно может быть улучшено. Однако он работает и решает нашу проблему.
Вот ваш конструктор сущности, но вы также можете сделать это в методе setter.
Кроме того, я использовал объект Collection, но он должен быть таким же или похожим со списком:
...
public ParentObject(Collection<ObjectChild> children) {
Collection<ObjectChild> occ = new ArrayList<ObjectChild>();
for(ObjectChild obj:children){
obj.setParent(this);
occ.add(obj);
}
this.attrs = occ;
}
...
В принципе, как и кто-то другой, мы должны сначала вручную установить все дочерние родительские идентификаторы перед сохранением родителя (вместе со всеми дочерними элементами)