[英]JPA: How to have one-to-many relation of the same Entity type
有一个实体类“ A”。 A类可能具有相同类型“ A”的子级。 如果“ A”是孩子,则也应保留它的父母。
这可能吗? 如果是这样,我应该如何在Entity类中映射关系? [“ A”有一个id列。]
是的,这是可能的。 这是标准双向@ManyToOne
/ @OneToMany
关系的@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
详细说明了不一致之处。
此行为可以通过A
的@OneToMany
和@ManyToOne
批注上的cascade
属性来配置。 例如,如果我在这两个注释上都设置了cascade=CascadeType.ALL
,则可以安全地保留其中一个实体而忽略其他实体。 假设我在交易中坚持parent
。 JPA实现会遍历parent
的children
属性,因为它已用CascadeType.ALL
标记。 JPA实施在这里找到了son
和daughter
。 然后,即使我没有明确要求,它也代表我保留了两个孩子。
还有一张便条。 更新双向关系的双方始终是程序员的责任。 换句话说,每当我将某个孩子添加到某个父母中时,都必须相应地更新该孩子的parent属性。 在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);
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.