Java JPA:如何拥有相同实体类型的一对多关系
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3393515/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
JPA: How to have one-to-many relation of the same Entity type
提问by sanjayav
There's an Entity Class "A". Class A might have children of the same type "A". Also "A" should hold it's parent if it is a child.
有一个实体类“A”。A 类可能有相同类型“A”的孩子。如果它是一个孩子,“A”也应该持有它的父母。
Is this possible? If so how should I map the relations in the Entity class? ["A" has an id column.]
这可能吗?如果是这样,我应该如何映射实体类中的关系?[“A”有一个 id 列。]
采纳答案by Dan LaRocque
Yes, this is possible. This is a special case of the standard bidirectional @ManyToOne
/@OneToMany
relationship. It is special because the entity on each end of the relationship is the same. The general case is detailed in Section 2.10.2 of the JPA 2.0 spec.
是的,这是可能的。这是标准双向@ManyToOne
/@OneToMany
关系的特例。它很特别,因为关系两端的实体是相同的。JPA 2.0 规范的第 2.10.2 节详细介绍了一般情况。
Here's a worked example. First, the entity class A
:
这是一个有效的例子。一、实体类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...
}
Here's a rough main()
method that persists three such entities:
这是一个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.
在这种情况下,所有三个实体实例都必须在事务提交之前持久化。如果我未能在父子关系图中持久化实体之一,则会在 上引发异常commit()
。在 Eclipselink 上,这是一个RollbackException
详细说明不一致的地方。
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.
此行为是通过可配置cascade
的属性A
的@OneToMany
和@ManyToOne
注释。例如,如果我设置cascade=CascadeType.ALL
了这两个注释,我就可以安全地保留其中一个实体并忽略其他实体。假设我坚持parent
我的交易。JPA 实现遍历parent
的children
属性,因为它用CascadeType.ALL
. JPA实现的发现son
和daughter
那里。然后它代表我坚持两个孩子,即使我没有明确要求它。
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:
再一记。更新双向关系的双方始终是程序员的责任。换句话说,每当我将一个孩子添加到某个父级时,我必须相应地更新孩子的父级属性。仅更新双向关系的一侧是 JPA 下的错误。始终更新关系的双方。这是明确地写在 JPA 2.0 规范的第 42 页上:
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.
请注意,应用程序负责维护运行时关系的一致性——例如,当应用程序在运行时更新关系时,确保双向关系的“一”边和“多”边彼此一致.
回答by topchef
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):
对我来说,诀窍是使用多对多关系。假设您的实体 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;
}
...
}
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
:
由于我在层次结构周围有一些广泛的业务逻辑,并且 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);
}
}
}