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

san*_*yav 90 java orm jpa hierarchy one-to-many

有一个实体类"A".A类可能具有相同类型"A"的子项.如果它是一个孩子,"A"也应该保留它的父母.

这可能吗?如果是这样,我应该如何映射Entity类中的关系?["A"有一个id列.]

Dan*_*que 158

是的,这是可能的.这是标准双向@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...
}
Run Code Online (Sandbox Code Playgroud)

这是一个粗略的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();
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,必须在事务提交之前保留所有三个实体实例.如果我未能在父子关系图中保留其中一个实体,则会抛出异常commit().在Eclipselink上,这是RollbackException详细说明不一致性.

此行为可通过's 和注释的cascade属性进行配置.例如,如果我设置了这两个注释,我可以安全地保留其中一个实体而忽略其他实体.说我坚持我的交易.JPA实现横梁的特性,因为它被标记为.JPA实现发现和那里.然后它代表我坚持这两个孩子,即使我没有明确要求它.A@OneToMany@ManyToOnecascade=CascadeType.ALLparentparentchildrenCascadeType.ALLsondaughter

再说一遍.程序员始终有责任更新双向关系的双方.换句话说,每当我将一个孩子添加到某个父母时,我必须相应地更新孩子的父母属性.仅更新双向关系的一侧是JPA下的错误.始终更新关系的两个方面.这是在JPA 2.0规范的第42页上明确写出的:

请注意,应用程序负责维护运行时关系的一致性 - 例如,当应用程序在运行时更新关系时,确保双向关系的"一"和"多"方彼此一致.


top*_*hef 8

对我来说,诀窍是使用多对多关系.假设您的实体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;
  }
...
}
Run Code Online (Sandbox Code Playgroud)

由于我在层次结构周围有一些广泛的业务逻辑,而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);
        }
    }

}
Run Code Online (Sandbox Code Playgroud)