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

时间:2021-12-22 20:24:21

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.]

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

2 个解决方案

#1


133  

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.

此行为可通过A的@OneToMany和@ManyToOne注释上的级联属性进行配置。例如,如果我在这两个注释上设置cascade = CascadeType.ALL,我可以安全地保留其中一个实体而忽略其他实体。说我在我的交易中坚持父母。 JPA实现遍历父项的子属性,因为它标记为CascadeType.ALL。 JPA实施在那里找到儿子和女儿。然后它代表我坚持这两个孩子,即使我没有明确要求它。

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.

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

#2


5  

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

}

#1


133  

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.

此行为可通过A的@OneToMany和@ManyToOne注释上的级联属性进行配置。例如,如果我在这两个注释上设置cascade = CascadeType.ALL,我可以安全地保留其中一个实体而忽略其他实体。说我在我的交易中坚持父母。 JPA实现遍历父项的子属性,因为它标记为CascadeType.ALL。 JPA实施在那里找到儿子和女儿。然后它代表我坚持这两个孩子,即使我没有明确要求它。

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.

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

#2


5  

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

}