JPA: 如何具有相同实体类型的一对多关系

有一个实体 A 级。类 A 的子类可能具有相同类型的“ A”。如果是个孩子,“ A”也应该抱着孩子的父母。

Is this possible? If so how should I map the relations in the Entity class? [“ A”有一个 id 列。]

107416 次浏览

是的,这是可能的。这是标准双向 @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.

这种行为可以通过 A@OneToMany@ManyToOne注释上的 cascade属性进行配置。例如,如果我在这两个注释上都设置了 cascade=CascadeType.ALL,我就可以安全地持久化其中一个实体而忽略其他实体。假设我在事务中持久化了 parent。JPA 实现遍历 parentchildren属性,因为它用 CascadeType.ALL标记。JPA 实现在那里找到 sonA0。然后它以我的名义持久化两个孩子,即使我没有明确地请求它。

还有一个音符。更新双向关系的双方始终是程序员的责任。换句话说,无论何时向某个父级添加子级,都必须相应地更新该子级的父级属性。只更新双向关系的一侧是 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 运行时的关系。

对我来说,诀窍就是使用多对多的关系。假设您的实体 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:

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);
}
}


}