回答:
はい、可能です。これは、標準の双方向@ManyToOne
/ @OneToMany
関係の特殊なケースです。関係の両端のエンティティは同じであるため、これは特別です。一般的なケースの詳細は、JPA 2.0仕様のセクション2.10.2に記載されています。
これが実際の例です。まず、エンティティクラス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...
}
このmain()
ようなエンティティを3つ保持する大まかな方法を次に示します。
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();
}
この場合、3つのエンティティインスタンスはすべて、トランザクションのコミット前に永続化する必要があります。親子関係のグラフのエンティティの1つを永続化できない場合、例外がスローされcommit()
ます。Eclipselinkでは、これはRollbackException
不整合の詳細です。
この動作はを通じて設定可能であるcascade
上の属性A
の@OneToMany
と@ManyToOne
注釈。たとえばcascade=CascadeType.ALL
、これらの注釈の両方を設定した場合、エンティティの1つを安全に永続化し、他のエンティティを無視できます。私parent
が私のトランザクションに固執したとしましょう。JPA実装はでマークされているため、parent
のchildren
プロパティを走査しCascadeType.ALL
ます。JPA実装発見son
とdaughter
そこに。その後、私が明示的に要求しなかったとしても、それは私の代わりに両方の子を永続化します。
もう1つ注意してください。双方向の関係の両側を更新することは、常にプログラマの責任です。言い換えると、ある親に子を追加するたびに、それに応じて子の親プロパティを更新する必要があります。双方向関係の片側のみを更新すると、JPAではエラーになります。関係の両側を常に更新します。これは、JPA 2.0仕様の42ページに明確に記述されています。
ランタイムの関係の一貫性を維持する責任があるのはアプリケーションです。たとえば、アプリケーションが実行時に関係を更新したときに双方向の関係の「片側」と「多数」の側が互いに一貫していることを保証します。 。
私にとってのコツは、多対多の関係を使用することでした。エンティティ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;
}
...
}
階層構造の周りにいくつかの広範なビジネスロジックがあり、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);
}
}
}
Top
はありません。JPA 2.0仕様のページ93、エンティティリスナーとコールバックメソッド:「一般的に、ポータブルアプリケーションのライフサイクルメソッドは、EntityManagerまたはQueryオペレーションを呼び出したり、他のエンティティインスタンスにアクセスしたり、関係を変更したりするべきではありません。」正しい?休みかどうか教えてください。