JPA: как иметь отношение "один ко многим" одного и того же типа сущности
есть класс сущностей "A". Класс A может иметь детей того же типа "A". Также "A" должен содержать его родителя, если это ребенок.
это возможно? Если да, то как я должен сопоставить отношения в классе сущности? ["A" имеет столбец id.]
2 ответа:
Да, это возможно. Это частный случай стандартного двунаправленного
@ManyToOne
/@OneToMany
отношения. Он особенный, потому что сущность на каждом конце отношений одна и та же. Общий случай подробно описан в разделе 2.10.2 JPA 2.0 spec.вот работающий пример. Во-первых, класс сущности
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
оформление несостоятельность.это поведение настраивается через на
A
' s@OneToMany
и@ManyToOne
Примечание. Например, если я установилcascade=CascadeType.ALL
на обоих этих аннотациях я мог бы безопасно сохранить один из сущности и игнорировать другие. Скажи, что я упорствовалparent
в моей сделке. Реализация JPA проходитparent
' schildren
свойство, потому что оно помеченоCascadeType.ALL
. Реализация JPA находитson
иdaughter
там. Затем он продолжает обоих детей от моего имени, хотя я явно не просил об этом.еще одно замечание. Это всегда ответственность программиста, чтобы обновить обе стороны двунаправленных отношений. Другими словами, всякий раз, когда я добавляю ребенок к какому-то родителю, я должен обновить родительское свойство ребенка соответственно. Обновление только одной стороны двунаправленного отношения является ошибкой в JPA. Всегда обновляйте обе стороны отношений. Это однозначно написано на странице 42 спецификации JPA 2.0:
обратите внимание, что именно приложение несет ответственность за поддержание согласованности отношений времени выполнения-например, за обеспечение того, чтобы" одна "и" многие " стороны двунаправленного отношения согласуются друг с другом, когда приложение обновляет связь во время выполнения.
для меня трюк состоял в том, чтобы использовать отношения "многие ко многим". Предположим, что ваша сущность-это подразделение, которое может иметь подразделы. Затем (пропуская несущественные детали):
@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); } } }