JPA: Cách có mối quan hệ một-nhiều của cùng một loại Đối tượng


99

Có một Lớp Thực thể "A". Lớp A có thể có con cùng loại "A". Ngoài ra "A" nên giữ nó là cha mẹ nếu nó là một đứa trẻ.

Điều này có khả thi không? Nếu vậy, tôi nên ánh xạ các quan hệ trong lớp Thực thể như thế nào? ["A" có một cột id.]

Câu trả lời:


170

Có, điều này là có thể. Đây là một trường hợp đặc biệt của mối quan hệ @ManyToOne/ hai chiều tiêu chuẩn @OneToMany. Nó đặc biệt vì thực thể ở mỗi đầu của mối quan hệ là như nhau. Trường hợp chung được trình bày chi tiết trong Phần 2.10.2 của thông số kỹ thuật JPA 2.0 .

Đây là một ví dụ hoạt động. Đầu tiên, lớp thực thể 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...
}

Đây là một main()phương pháp cơ bản duy trì ba thực thể như vậy:

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

Trong trường hợp này, cả ba phiên bản thực thể phải được duy trì trước khi cam kết giao dịch. Nếu tôi không thể duy trì một trong các thực thể trong biểu đồ của mối quan hệ cha-con, thì một ngoại lệ sẽ được đưa ra commit(). Trên Eclipselink, đây là RollbackExceptionchi tiết về sự không nhất quán.

Hành vi này có thể được định cấu hình thông qua cascadethuộc tính on A's @OneToMany@ManyToOnecác chú thích. Ví dụ: nếu tôi đặt cascade=CascadeType.ALLtrên cả hai chú thích đó, tôi có thể duy trì một cách an toàn một trong các thực thể và bỏ qua các thực thể khác. Giả sử tôi vẫn tiếp tục parentgiao dịch của mình. Việc thực hiện Bộ móc cần cẩu JPA parent's childrentài sản bởi vì nó được đánh dấu bằng CascadeType.ALL. Việc triển khai JPA tìm thấy sondaughterở đó. Sau đó, nó vẫn tồn tại cả hai đứa trẻ thay mặt tôi, mặc dù tôi không yêu cầu rõ ràng.

Thêm một lưu ý nữa. Lập trình viên luôn có trách nhiệm cập nhật cả hai mặt của mối quan hệ hai chiều. Nói cách khác, bất cứ khi nào tôi thêm một đứa trẻ vào một số phụ huynh, tôi phải cập nhật tài sản phụ huynh của đứa trẻ đó cho phù hợp. Chỉ cập nhật một mặt của mối quan hệ hai chiều là một lỗi trong JPA. Luôn cập nhật cả hai mặt của mối quan hệ. Điều này được viết rõ ràng trên trang 42 của thông số JPA 2.0:

Lưu ý rằng ứng dụng phải chịu trách nhiệm duy trì tính nhất quán của các mối quan hệ thời gian chạy — ví dụ: để đảm bảo rằng các mặt “một” và “nhiều” của mối quan hệ hai chiều nhất quán với nhau khi ứng dụng cập nhật mối quan hệ trong thời gian chạy .


Cảm ơn bạn rất nhiều vì lời giải thích chi tiết! Ví dụ là đến điểm và hoạt động trong lần chạy đầu tiên.
sanjayav

@sunnyj Rất vui được giúp đỡ. Chúc may mắn với những dự án của bạn).
Dan LaRocque

Nó đã gặp vấn đề này trước đây khi tạo thực thể danh mục có danh mục phụ. Nó rất hữu dụng!
Trường Hà

@DanLaRocque có lẽ tôi đang hiểu nhầm (hoặc có lỗi ánh xạ thực thể), nhưng tôi đang thấy hành vi không mong muốn. Tôi có mối quan hệ một-nhiều giữa Người dùng và Địa chỉ. Khi Người dùng hiện tại thêm Địa chỉ, tôi đã làm theo đề xuất của bạn để cập nhật cả Người dùng và Địa chỉ (và gọi 'lưu' trên cả hai). Nhưng điều này dẫn đến một hàng trùng lặp được chèn vào bảng Địa chỉ của tôi. Điều này có phải do tôi đã định cấu hình sai CascadeType của mình trên trường địa chỉ của Người dùng không?
Alex

@DanLaRocque có thể xác định mối quan hệ này là một chiều không ??
Ali Arda Orhan

8

Đối với tôi, mẹo là sử dụng mối quan hệ nhiều-nhiều. Giả sử rằng thực thể A của bạn là một bộ phận có thể có các bộ phận con. Sau đó (bỏ qua các chi tiết không liên quan):

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

Vì tôi đã có một số logic kinh doanh sâu rộng xung quanh cấu trúc phân cấp và JPA (dựa trên mô hình quan hệ) rất yếu để hỗ trợ nó, tôi đã giới thiệu giao diện IHierarchyElementvà trình nghe thực thể 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
Tại sao không sử dụng @OneToMany (mappedBy = "DIV_PARENT_ID") đơn giản hơn thay vì @ManyToMany (...) với các thuộc tính tự tham chiếu? Việc gõ lại tên bảng và cột như vậy là vi phạm DRY. Có lẽ có lý do cho điều đó, nhưng tôi không thấy nó. Ngoài ra, ví dụ EntityListener là gọn gàng nhưng không di động, giả sử Toplà một mối quan hệ. Trang 93 của thông số kỹ thuật JPA 2.0, Trình xử lý thực thể và Phương thức gọi lại: "Nói chung, phương thức vòng đời của một ứng dụng di động không được gọi EntityManager hoặc hoạt động Truy vấn, truy cập các cá thể thực thể khác hoặc sửa đổi các mối quan hệ". Đúng? Hãy cho tôi biết nếu tôi tắt.
Dan LaRocque

Giải pháp của tôi đã được 3 năm sử dụng JPA 1.0. Tôi đã điều chỉnh nó không thay đổi so với mã sản xuất. Tôi chắc chắn rằng tôi có thể lấy ra một số tên cột nhưng nó không phải là vấn đề. Câu trả lời của bạn thực hiện chính xác và đơn giản hơn, không chắc tại sao hồi đó tôi sử dụng nhiều-nhiều - nhưng nó hoạt động và tôi tin rằng giải pháp phức tạp hơn là có lý do. Tuy nhiên, tôi sẽ phải xem lại điều này ngay bây giờ.
topchef

Vâng, top là một tự tham chiếu, do đó là một mối quan hệ. Nói một cách chính xác, tôi không sửa đổi nó - chỉ khởi tạo. Ngoài ra, nó đơn hướng nên không có sự phụ thuộc theo chiều ngược lại, nó không tham chiếu đến các thực thể khác ngoài bản thân. Theo thông số báo giá của bạn có "nói chung" có nghĩa là nó không phải là định nghĩa nghiêm ngặt. Tôi tin rằng trong trường hợp này có rất ít rủi ro về tính di động nếu có.
topchef
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.