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();
}
In this case, all three entity instances must be persisted before transaction commit. If I fail to persist one of the entities in the graph of parent-child relationships, then an exception is thrown on commit()
. On Eclipselink, this is a RollbackException
detailing the inconsistency.
This behavior is configurable through the cascade
attribute on A
's @OneToMany
and @ManyToOne
annotations. For instance, if I set cascade=CascadeType.ALL
on both of those annotations, I could safely persist one of the entities and ignore the others. Say I persisted parent
in my transaction. The JPA implementation traverses parent
's children
property because it is marked with CascadeType.ALL
. The JPA implementation finds son
and daughter
there. It then persists both children on my behalf, even though I didn't explicitly request it.
One more note. It is always the programmer's responsibility to update both sides of a bidirectional relationship. In other words, whenever I add a child to some parent, I must update the child's parent property accordingly. Updating only one side of a bidirectional relationship is an error under JPA. Always update both sides of the relationship. This is written unambiguously on page 42 of the JPA 2.0 spec:
Note that it is the application that bears responsibility for maintaining the consistency of runtime relationships—for example, for insuring that the “one” and the “many” sides of a bidirectional relationship are consistent with one another when the application updates the relationship at runtime.
For me the trick was to use many-to-many relationship. Suppose that your entity A is a division that can have sub-divisions. Then (skipping irrelevant details):
@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;
}
...
}
Since I had some extensive business logic around hierarchical structure and JPA (based on relational model) is very weak to support it I introduced interface IHierarchyElement
and entity listener 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);
}
}
}
'development' 카테고리의 다른 글
스크립트 오류시 Postgres SQL 실패 (0) | 2020.09.07 |
---|---|
javax.websocket 클라이언트 간단한 예제 (0) | 2020.09.07 |
쉘 스크립트의 줄 끝에 세미콜론이 불필요합니까? (0) | 2020.09.07 |
Capybara에서 찾기와 함께 fill_in을 사용하는 방법 (가능한 경우) (0) | 2020.09.07 |
TensorFlow에서 정규화를 추가하는 방법은 무엇입니까? (0) | 2020.09.07 |