[java] JPA : 동일한 엔티티 유형의 일대 다 관계를 갖는 방법

엔티티 클래스 “A”가 있습니다. 클래스 A에는 동일한 유형 “A”의 하위가있을 수 있습니다. 또한 “A”는 자녀 인 경우 부모를 보유해야합니다.

이게 가능해? 그렇다면 Entity 클래스에서 관계를 어떻게 매핑해야합니까? [ “A”에는 id 열이 있습니다.]



답변

네, 가능합니다. 이것은 표준 양방향 @ManyToOne/ @OneToMany관계 의 특별한 경우입니다 . 관계의 각 끝에있는 엔티티가 동일하기 때문에 특별합니다. 일반적인 경우는 JPA 2.0 사양 의 섹션 2.10.2에 자세히 설명되어 있습니다.

다음은 작동하는 예입니다. 첫째, 엔티티 클래스 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속성을 통해 구성 할 수 있습니다. 예를 들어 두 주석을 모두 설정 하면 엔티티 중 하나를 안전하게 유지하고 나머지는 무시할 수 있습니다. 거래를 지속했다고 가정 해 보겠습니다 . JPA 구현 은로 표시되어 있으므로 의 속성을 탐색 합니다. JPA를 구현 발견 하고 있다. 그런 다음 내가 명시 적으로 요청하지 않았더라도 나를 대신하여 두 자녀를 모두 유지합니다.A@OneToMany@ManyToOnecascade=CascadeType.ALLparentparentchildrenCascadeType.ALLsondaughter

메모 하나 더. 양방향 관계의 양쪽을 업데이트하는 것은 항상 프로그래머의 책임입니다. 즉, 어떤 부모에게 자식을 추가 할 때마다 그에 따라 자식의 부모 속성을 업데이트해야합니다. 양방향 관계의 한쪽 만 업데이트하는 것은 JPA에서 오류입니다. 항상 관계의 양쪽을 모두 업데이트하십시오. 이것은 JPA 2.0 사양의 42 페이지에 명확하게 작성되었습니다.

예를 들어, 애플리케이션이 런타임에 관계를 업데이트 할 때 양방향 관계의 “일”측면과 “다”측면이 서로 일치하도록 보장하기 위해 런타임 관계의 일관성을 유지하는 책임은 애플리케이션입니다. .


답변

나에게 비결은 다 대다 관계를 사용하는 것이었다. 엔티티 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);
        }
    }

}


답변