有一个实体类“A”。 A类可能有同一类型“A”的孩子。如果它是一个孩子,“A”应该是它的父母。JPA:如何具有相同实体类型的一对多关系
这可能吗?如果是这样,我应该如何映射实体类中的关系? [“A”有一个id列。]
有一个实体类“A”。 A类可能有同一类型“A”的孩子。如果它是一个孩子,“A”应该是它的父母。JPA:如何具有相同实体类型的一对多关系
这可能吗?如果是这样,我应该如何映射实体类中的关系? [“A”有一个id列。]
是的,这是可能的。这是标准双向关系的特例。这很特殊,因为关系的每一端的实体是相同的。一般情况详见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实施遍历parent
的children
属性,因为它标记为CascadeType.ALL
。 JPA实现在那里找到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);
}
}
}
为什么不使用简单的@OneToMany(mappedBy =“DIV_PARENT_ID”)而不是@ManyToMany(...)和自引用属性?重新调整表格和列名称会违反DRY。也许这是有原因的,但我没有看到它。另外,EntityListener示例是整洁但不可移植的,假设'Top'是一种关系。 JPA 2.0规范的第93页,实体监听器和回调方法:“通常,可移植应用程序的生命周期方法不应该调用EntityManager或Query操作,访问其他实体实例或修改关系。对?让我知道我是否离开。 – 2010-08-03 06:05:31
我的解决方案是3岁使用JPA 1.0。我从生产代码中改变了它。我确信我可以列出一些列名,但这不是重点。你的答案完全简单,不知道为什么当时我使用了多对多的方法 - 但它确实有效,我相信更复杂的解决方案是有原因的。尽管如此,我现在不得不重新访问。 – topchef 2010-08-03 06:37:49
是的,top是一个自我引用,因此是一种关系。严格地说,我不修改它 - 只是初始化。而且,它是单向的,所以没有其他方式的依赖关系,它不会引用除自我之外的其他实体。根据你的报价规范有“一般”,这意味着它不是严格的定义。如果有的话,我相信在这种情况下可移植性风险非常低。 – topchef 2010-08-03 06:44:53
非常感谢您的详细解释!这个例子是关键,并在第一次运行。 – sanjayav 2010-08-03 06:11:00
@sunnyj很高兴帮助。祝你的项目好运。 – 2010-08-03 06:22:29
在创建具有子类别的类别实体之前,它遇到了此问题。这很有帮助! – 2010-08-03 06:33:28