Jawaban:
Ya, ini mungkin. Ini adalah kasus khusus dari standar dua arah @ManyToOne
/ @OneToMany
hubungan. Itu istimewa karena entitas di setiap ujung hubungan adalah sama. Kasus umum dirincikan dalam Bagian 2.10.2 dari spesifikasi JPA 2.0 .
Inilah contoh yang berhasil. Pertama, kelas entitas 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...
}
Berikut adalah main()
metode kasar yang mempertahankan tiga entitas tersebut:
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();
}
Dalam kasus ini, ketiga contoh entitas harus dipertahankan sebelum transaksi dilakukan. Jika saya gagal mempertahankan salah satu entitas dalam grafik hubungan induk-anak, maka pengecualian akan diterapkan commit()
. Di Eclipselink, ini adalah RollbackException
detail ketidakkonsistenan.
Perilaku ini dapat dikonfigurasi melalui cascade
atribut pada A
's @OneToMany
dan @ManyToOne
penjelasan. Misalnya, jika saya menyetel cascade=CascadeType.ALL
kedua anotasi tersebut, saya dapat dengan aman mempertahankan salah satu entitas dan mengabaikan yang lainnya. Katakanlah saya bertahan parent
dalam transaksi saya. Pelaksanaan traverse JPA parent
's children
properti karena ditandai dengan CascadeType.ALL
. Implementasi JPA menemukan son
dan di daughter
sana. Itu kemudian berlanjut pada kedua anak atas nama saya, meskipun saya tidak secara eksplisit memintanya.
Satu catatan lagi. Itu selalu menjadi tanggung jawab programmer untuk memperbarui kedua sisi hubungan dua arah. Dengan kata lain, setiap kali saya menambahkan anak ke beberapa orang tua, saya harus memperbarui properti induk anak tersebut. Memperbarui hanya satu sisi hubungan dua arah adalah kesalahan menurut JPA. Selalu perbarui kedua sisi hubungan. Ini ditulis dengan jelas pada halaman 42 dari spesifikasi JPA 2.0:
Perhatikan bahwa aplikasilah yang memikul tanggung jawab untuk menjaga konsistensi hubungan waktu proses — misalnya, untuk memastikan bahwa sisi "satu" dan "banyak" dari hubungan dua arah konsisten satu sama lain ketika aplikasi memperbarui hubungan pada waktu proses .
Bagi saya triknya adalah menggunakan hubungan banyak-ke-banyak. Misalkan entitas A Anda adalah divisi yang dapat memiliki sub-divisi. Kemudian (melewatkan detail yang tidak relevan):
@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;
}
...
}
Karena saya memiliki logika bisnis yang luas di sekitar struktur hierarki dan JPA (berdasarkan model relasional) sangat lemah untuk mendukungnya, saya memperkenalkan antarmuka IHierarchyElement
dan pendengar entitas 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
adalah hubungan. Halaman 93 dari spesifikasi JPA 2.0, Entity Listeners dan Metode Callback: "Secara umum, metode siklus hidup aplikasi portabel tidak boleh menjalankan operasi EntityManager atau Query, mengakses instance entitas lain, atau mengubah hubungan". Baik? Beri tahu saya jika saya tidak aktif.