엔티티 클래스 “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
@ManyToOne
cascade=CascadeType.ALL
parent
parent
children
CascadeType.ALL
son
daughter
메모 하나 더. 양방향 관계의 양쪽을 업데이트하는 것은 항상 프로그래머의 책임입니다. 즉, 어떤 부모에게 자식을 추가 할 때마다 그에 따라 자식의 부모 속성을 업데이트해야합니다. 양방향 관계의 한쪽 만 업데이트하는 것은 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);
}
}
}