JPA: как иметь отношение «один ко многим» одного и того же типа Entity

Существует class сущностей «А». Класс A может иметь детей одного типа «A». Также «A» должен содержать родительский элемент, если он является дочерним.

Это возможно? Если да, то как мне сопоставить отношения в classе Entity? [“A” имеет столбец id.]

    Да, это возможно. Это особый случай стандартного двунаправленного отношения @ManyToOne / @OneToMany . Это особенность, потому что сущность на каждом конце отношений одинакова. Общий случай подробно описан в разделе 2.10.2 спецификации JPA 2.0 .

    Вот пример работы. Во-первых, class сущности A :

     @Entity public class A implements Serializable { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @ManyToOne private A parent; @OneToMany(mappedBy="parent") private Collection 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 annotations @OneToMany и @ManyToOne Например, если я установил cascade=CascadeType.ALL в обе эти annotations, я мог бы надежно сохранить одно из объектов и игнорировать остальные. Предположим, что я сохранил parent в своей транзакции. Реализация JPA пересекает CascadeType.ALL свойство parent поскольку оно отмечено 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 subDivisions = new ArrayList(); ... @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 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); } } } 
    Давайте будем гением компьютера.