エンティティクラスのequalsメソッドとhashCodeメソッドを定義しなくてはならない場合
Seamの便利な機能の一つに
@SuppressWarnings("unchecked") @Factory(scope = ScopeType.APPLICATION) public List<Hobby> getHobbyList() { return em.createQuery("select hobby from Hobby hobby").getResultList(); }
のようなファクトリーメソッドを定義しておけば、画面で
<s:decorate id="hobbyField" template="/layout/edit.xhtml"> <ui:define name="label">趣味</ui:define> <h:selectManyCheckbox value="#{user.hobbyList}" > <s:selectItems value="#{hobbyList}" var="hobby" label="#{hobby.name}" /> <s:convertEntity /> </h:selectManyCheckbox> </s:decorate>
のようにして、選択肢を表示できるだけでなく、選択結果を自動的にエンティティに復元して設定してくれます。上記の例ではエンティティがほとんど更新されることがない前提で、アプリケーションスコープにさりげなくキャッシュしていますが、これが原因で、最初思うように動作せず原因を突き止めるのに苦労しました。何が問題かというと、アプリケーションスコープに保存されたエンティティのインスタンスは、会話の終了後、永続コンテキストから切り離されたdetached状態になっているということです。その結果、画面で選択しても正しく再設定されず不思議な動作となります。この問題を解決するポイントは、以下のようにエンティティクラスで正しくequalsメソッドをオーバーライドしてやることです。
@Entity public class Hobby implements Serializable { ... @Override public boolean equals(Object other) { if (other == this) return true; if (!(other instanceof Hobby)) { return false; } Hobby hobby = (Hobby)other; return name == null ? false : name.equals(hobby.getName()); } @Override public int hashCode() { if (name == null) return super.hashCode(); return name.hashCode(); } }
JBoss Toolsで自動生成したエンティティクラスにはこのようなequalsメソッドが実装されていないので注意が必要です。JPA仕様的にはエンティティにequalsメソッドを実装することは必須ではないのですが、detached状態のエンティティの等価性を正しく判定するためには、少なくともHibernateではequalsのオーバーライドが必要になります。
(http://docs.jboss.org/hibernate/stable/core/reference/en/html/persistent-classes.html#persistent-classes-equalshashcode)
自動生成されないのは、単純なID比較では不十分なケース(Hash値の不変性が必要な場合など)があり、エンティティのequalsメソッドの実装を一律で行うことが意外に難しいという原因もあるのだと思いますが、チームで開発する場合にはとりあえずデフォルト実装をID比較などで親クラスで実装するか、AspectJのInterType Definitionで織り込むなどの対策が有効でしょう。