简体   繁体   English

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

[英]JPA: How to have one-to-many relation of the same Entity type

There's an Entity Class "A". 有一个实体类“ A”。 Class A might have children of the same type "A". A类可能具有相同类型“ A”的子级。 Also "A" should hold it's parent if it is a child. 如果“ A”是孩子,则也应保留它的父母。

Is this possible? 这可能吗? If so how should I map the relations in the Entity class? 如果是这样,我应该如何在Entity类中映射关系? ["A" has an id column.] [“ A”有一个id列。]

Yes, this is possible. 是的,这是可能的。 This is a special case of the standard bidirectional @ManyToOne / @OneToMany relationship. 这是标准双向@ManyToOne / @OneToMany关系的@OneToMany 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 . 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() . 如果我无法在父子关系图中持久保留其中一个实体,则会在commit()上引发异常。 On Eclipselink, this is a RollbackException detailing the inconsistency. 在Eclipselink上,这是一个RollbackException详细说明了不一致之处。

This behavior is configurable through the cascade attribute on A 's @OneToMany and @ManyToOne annotations. 此行为可以通过A@OneToMany@ManyToOne批注上的cascade属性来配置。 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. 例如,如果我在这两个注释上都设置了cascade=CascadeType.ALL ,则可以安全地保留其中一个实体而忽略其他实体。 Say I persisted parent in my transaction. 假设我在交易中坚持parent The JPA implementation traverses parent 's children property because it is marked with CascadeType.ALL . JPA实现会遍历parentchildren属性,因为它已用CascadeType.ALL标记。 The JPA implementation finds son and daughter there. JPA实施在这里找到了sondaughter It then persists both children on my behalf, even though I didn't explicitly request it. 然后,即使我没有明确要求,它也代表我保留了两个孩子。

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. 换句话说,每当我将某个孩子添加到某个父母中时,都必须相应地更新该孩子的parent属性。 Updating only one side of a bidirectional relationship is an error under JPA. 在JPA下,仅更新双向关系的一侧是错误。 Always update both sides of the relationship. 始终更新关系的双方。 This is written unambiguously on page 42 of the JPA 2.0 spec: 这是明确写在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. 请注意,应用程序负责维护运行时关系的一致性,例如,当应用程序在运行时更新关系时,确保双向关系的“一侧”和“许多”侧彼此一致。

For me the trick was to use many-to-many relationship. 对我而言,诀窍是使用多对多关系。 Suppose that your entity A is a division that can have sub-divisions. 假设您的实体A是可以具有细分的部门。 Then (skipping irrelevant details): 然后(跳过无关的详细信息):

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

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM