Câu trả lời:
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à RollbackException
chi 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 cascade
thuộc tính on A
's @OneToMany
và @ManyToOne
các chú thích. Ví dụ: nếu tôi đặt cascade=CascadeType.ALL
trê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 parent
giao dịch của mình. Việc thực hiện Bộ móc cần cẩu JPA parent
's children
tà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 son
và daughter
ở đó. 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 .
Đố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 IHierarchyElement
và 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);
}
}
}
Top
là 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.