2010-08-03 2 views

답변

128

예, 가능합니다. 이것은 표준 양방향 @ManyToOne/@OneToMany 관계의 특별한 경우입니다. 그것은 관계의 양쪽 끝에있는 엔티티가 동일하기 때문에 특별합니다. 일반적인 경우는 JPA 2.0 spec의 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입니다.

이 동작은 A@OneToMany@ManyToOne 주석에 cascade 특성을 통해 구성 할 수 있습니다. 예를 들어 두 주석 모두에 cascade=CascadeType.ALL을 설정하면 엔티티 중 하나를 안전하게 유지하고 다른 엔티티는 무시할 수 있습니다. 거래 내에서 parent을 지속했다고 가정 해보십시오. JPA 구현은 CascadeType.ALL으로 표시되어 parentchildren 속성을 통과합니다. JPA 구현은 sondaughter을 찾습니다. 그런 다음 명시 적으로 요청하지 않았지만 두 자녀를 모두 대신 유지합니다.

한 번 더 메모 양방향 관계의 양면을 갱신하는 것은 항상 프로그래머의 책임입니다. 즉, 어떤 부모에게 자식을 추가 할 때마다 그 자식의 부모 속성을 적절히 업데이트해야합니다. 양방향 관계의 한쪽 만 업데이트하면 JPA에서 오류가 발생합니다. 항상 관계의 양면을 업데이트하십시오. 이 작업은 JPA 2.0 스펙의 42 페이지에 명확하게 기록 : 그것은 실행의 일관성 유지에 대한 책임을지지 응용 프로그램입니다

하는 것으로 관계를 - 예를 들어,은 "한"과 "많은 것을 보장에 대한 양방향 관계의 측면은 응용 프로그램이 런타임에 관계를 갱신 할 때 서로 일치합니다.

+0

자세한 설명 주셔서 대단히 감사합니다!이 예가 중요한 것이며 첫 번째 실행에서 작동했습니다. – sanjayav

+0

@sunnyj 기꺼이 도와 드리겠습니다. 귀하의 프로젝트와 함께 행운을 빌어 요. –

+0

하위 카테고리가있는 카테고리 엔티티를 생성하기 전에이 문제가 발생했습니다. 도움이된다! –

5

나를 위해 트릭은 다 대다 관계를 사용하는 것이 었습니다. 엔티티 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); 
     } 
    } 

} 
+1

자체 참조 속성이있는 @ManyToMany (...) 대신 간단한 @OneToMany (mappedBy = "DIV_PARENT_ID")를 사용하지 않는 이유는 무엇입니까? DRY에 위배되는 테이블 및 열 이름을 다시 입력하십시오. 어쩌면 거기에는 이유가 있지만, 나는 그것을 보지 못합니다. 또한, EntityListener 예제는 깔끔하지만 이동성이 없으며,'Top'은 관계라고 가정합니다. 엔티티 리스너 및 콜백 메소드의 93 페이지 : "일반적으로 이식 가능한 응용 프로그램의 라이프 사이클 메소드는 EntityManager 또는 쿼리 작업을 호출하거나 다른 엔티티 인스턴스에 액세스하거나 관계를 수정해서는 안됩니다. 권리? 내가 쉬면 알려줘. –

+0

내 솔루션은 JPA 1.0을 사용하여 3 년 전입니다. 필자는 프로덕션 코드에서 변경하지 않고 그대로 적용했습니다. 일부 열 이름을 제거 할 수 있다고 확신하지만 그 점은 중요하지 않습니다. 귀하의 대답은 정확하고 간단합니다. 그 당시에는 다 대다를 많이 사용했는지 확신 할 수 없었습니다. 그러나 작동하고 더 복잡한 솔루션이 이유가 있었음을 확신합니다. 그러나, 나는 이것을 지금 다시 방문해야 할 것이다. – topchef

+0

예, top은 자체 참조이므로 관계입니다. 엄밀히 말하면, 저는 그것을 수정하지 않습니다 - 단지 초기화하십시오. 또한 단방향이므로 의존성이 없으며 자기 자신 외에 다른 엔티티를 참조하지 않습니다. 귀하의 견적 스펙은 "일반적으로"엄격한 정의가 아님을 의미합니다. 나는이 경우 매우 낮은 이식성 위험이 있다고 믿는다. – topchef

관련 문제