エンハンス
Play Framework でビルドすると、コードのコンパイルの後に、エンティティに対して「エンハンス」という操作が行われます。
EbeanをORマッパーとして使用する場合、
(1)Play Frameworkのエンハンス
(2)Ebeanのエンハンス
という順で行われます。
ここで、上記それぞれのエンハンスで何が行われるかを正しく理解していないとハマることになりますので要注意です。
Play Framework のエンハンス
Play Framework では、ソースコードはプロジェクトホームディレクトリ直下の"app"配下にあることが想定されています。
そこで、"app"配下にあるソースコードを探索して、エンティティに対して、getterとsetterがついていないものはそれをつけるようにします。
その後で、エンティティのフィールド直接アクセスを、getter/setter経由になるように書き換えます。エンティティ内に限らず呼び出し元も書き換えてくれるので便利です。
Ebean の エンハンス
@Entity
がついているソースに対して、遅延ローディングや楽観的ロックの制御等の目的で、getterやsetterソースコードが改変されます。
また、Entity 内において、フィールドを直接参照しているコードを、getter/setter経由で呼ぶように改変します。 エンティティ外からのフィールドアクセスについてはsetter/getter経由に呼び直すという書き換えは行いません。
2つのエンハンスを経て何がどうなるか
"app"配下にあるものは何事もなく、フィールド直アクセスであっても遅延ローディングや楽観的ロックの恩恵を受けることができます。
しかし、"app"配下にないコード(別プロジェクトと共通で使うライブラリなど)で、同じ調子でフィールドへの直接アクセスをやっていると、存在するはずのフィールドが存在せず、結果、NullPointerExceptionで落ちたりします。
自分は未確認ですが、末尾参考文献によれば、Scalaコードからのアクセスも要注意のようです。
(Play Framework 2 は至る所にScalaコードがあるため、気づかないうちにハマる可能性もあります)
ベストプラクティス
予期せぬ挙動を防ぐために、以下を守った方が安全です。
- フィールドへのアクセスは変数直アクセスではなく、setter/getterを使うようにする。
- そのために、エンティティのsetter/getterは明示的に宣言しておく。(そうしないとコンパイルが通らない)
その他
Play Frameworkのエンハンスでは、関連テーブルのfetchを自動でやってくれるようになります。
なので、エンティティのフィールドにある別のエンティティも、一番上の(外側の)エンティティをfindするだけで、内側のフィールドも値が入っています。Ebeanのエンハンスではそれが行われないため、明示的に内側のフィールドもfetchしておかないと、内側のフィールドはnullになります。
これもハマりポイントです。
参考文献
https://groups.google.com/forum/#!msg/play-framework/XaGlipnEgBc/wPdpB6Hm_4QJ
https://groups.google.com/forum/#!msg/ebean/6sM7_bCZoSc/Rwc354MIPTgJ