Есть сущность класса «А». У класса A могут быть дети того же типа «A». Также "A" должен содержать его родителя, если это ребенок.
Это возможно? Если да, то как мне сопоставить отношения в классе Entity? [«A» имеет столбец идентификаторов.]
Ответы:
Да, это возможно. Это особый случай стандартного двунаправленного @ManyToOne
/ @OneToMany
отношений. Он особенный, потому что объекты на каждом конце отношения одинаковы. Общий случай подробно описан в разделе 2.10.2 спецификации JPA 2.0. .
Вот наработанный пример. Во-первых, класс сущности A
:
@Entity
public class A implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
@ManyToOne
private A parent;
@OneToMany(mappedBy="parent")
private Collection<A> children;
// Getters, Setters, serialVersionUID, etc...
}
Вот примерный main()
метод, который сохраняет три таких объекта:
public static void main(String[] args) {
EntityManager em = ... // from EntityManagerFactory, injection, etc.
em.getTransaction().begin();
A parent = new A();
A son = new A();
A daughter = new A();
son.setParent(parent);
daughter.setParent(parent);
parent.setChildren(Arrays.asList(son, daughter));
em.persist(parent);
em.persist(son);
em.persist(daughter);
em.getTransaction().commit();
}
В этом случае все три экземпляра сущности должны быть сохранены до фиксации транзакции. Если мне не удается сохранить одну из сущностей на графе родительско-дочерних отношений, возникает исключение commit()
. В Eclipselink это RollbackException
подробное описание несоответствия.
Такое поведение настраивается с помощью cascade
атрибута A
«с @OneToMany
и @ManyToOne
аннотаций. Например, если я использую cascade=CascadeType.ALL
обе эти аннотации, я могу безопасно сохранить одну из сущностей и игнорировать другие. Скажем, я настаивал parent
на своей транзакции. Траверсы реализации JPA parent
«ы children
собственности , потому что он отмечен CascadeType.ALL
. Реализация JPA находит son
и daughter
там. Затем он сохраняет обоих детей от моего имени, хотя я явно не запрашивал это.
Еще одно замечание. Ответственность за обновление обеих сторон двунаправленной связи всегда лежит на программисте. Другими словами, всякий раз, когда я добавляю дочернего элемента к какому-либо родительскому элементу, я должен соответствующим образом обновить его родительское свойство. Обновление только одной стороны двунаправленной связи является ошибкой в JPA. Всегда обновляйте обе стороны отношений. Это недвусмысленно написано на странице 42 спецификации JPA 2.0:
Обратите внимание, что именно приложение несет ответственность за поддержание согласованности взаимосвязей во время выполнения - например, за обеспечение того, чтобы «одна» и «многие» стороны двунаправленной взаимосвязи согласовывались друг с другом, когда приложение обновляет взаимосвязь во время выполнения. .
Для меня уловка заключалась в использовании отношения «многие ко многим». Предположим, что ваш объект A - это подразделение, которое может иметь подразделения. Затем (пропуская нерелевантные подробности):
@Entity
@Table(name = "DIVISION")
@EntityListeners( { HierarchyListener.class })
public class Division implements IHierarchyElement {
private Long id;
@Id
@Column(name = "DIV_ID")
public Long getId() {
return id;
}
...
private Division parent;
private List<Division> subDivisions = new ArrayList<Division>();
...
@ManyToOne
@JoinColumn(name = "DIV_PARENT_ID")
public Division getParent() {
return parent;
}
@ManyToMany
@JoinTable(name = "DIVISION", joinColumns = { @JoinColumn(name = "DIV_PARENT_ID") }, inverseJoinColumns = { @JoinColumn(name = "DIV_ID") })
public List<Division> getSubDivisions() {
return subDivisions;
}
...
}
Поскольку у меня была обширная бизнес-логика вокруг иерархической структуры, а JPA (на основе реляционной модели) очень слаб для ее поддержки, я представил интерфейс IHierarchyElement
и прослушиватель сущностей HierarchyListener
:
public interface IHierarchyElement {
public String getNodeId();
public IHierarchyElement getParent();
public Short getLevel();
public void setLevel(Short level);
public IHierarchyElement getTop();
public void setTop(IHierarchyElement top);
public String getTreePath();
public void setTreePath(String theTreePath);
}
public class HierarchyListener {
@PrePersist
@PreUpdate
public void setHierarchyAttributes(IHierarchyElement entity) {
final IHierarchyElement parent = entity.getParent();
// set level
if (parent == null) {
entity.setLevel((short) 0);
} else {
if (parent.getLevel() == null) {
throw new PersistenceException("Parent entity must have level defined");
}
if (parent.getLevel() == Short.MAX_VALUE) {
throw new PersistenceException("Maximum number of hierarchy levels reached - please restrict use of parent/level relationship for "
+ entity.getClass());
}
entity.setLevel(Short.valueOf((short) (parent.getLevel().intValue() + 1)));
}
// set top
if (parent == null) {
entity.setTop(entity);
} else {
if (parent.getTop() == null) {
throw new PersistenceException("Parent entity must have top defined");
}
entity.setTop(parent.getTop());
}
// set tree path
try {
if (parent != null) {
String parentTreePath = StringUtils.isNotBlank(parent.getTreePath()) ? parent.getTreePath() : "";
entity.setTreePath(parentTreePath + parent.getNodeId() + ".");
} else {
entity.setTreePath(null);
}
} catch (UnsupportedOperationException uoe) {
LOGGER.warn(uoe);
}
}
}
Top
связь. Страница 93 спецификации JPA 2.0, Слушатели сущностей и методы обратного вызова: «В общем, метод жизненного цикла переносимого приложения не должен вызывать операции EntityManager или Query, обращаться к другим экземплярам сущностей или изменять отношения». Правильно? Дай мне знать, если я уйду.