読者です 読者をやめる 読者になる 読者になる

JPAについて調べてみた

ほぼ毎日使うのだけれど、かなり曖昧な理解のまま放置していた。
エラーが起こるたびに場当たり的な対処をしてきましたが、これではいけないと1から調べてみました。
間違っている箇所もあるかもしれませんので、お気づきの点はぜひご指摘ください。

永続化コンテキスト、EntityManager
JPAには「永続化コンテキスト」と呼ばれる入れ物が用意されている。永続化コンテキストとHibernateのセッションは同じことらしい。
この入れ物にはエンティティと呼ばれるDBの1行1行に対応するモデルが入っている。例えばDBに「性=山田、名=太郎、年齢=40歳」みたいな1行のレコードがあるとすると、それに対応するエンティティにはプロパティが3つあってlastNameに山田, firstNameに太郎, ageに40が入っているような形。EntityManagerはこの永続化コンテキストという入れ物や各エンティティを管理するもの。

エンティティをsaveしたり、既存のデータをDBから持ってくると、レコードの内容がエンティティとして永続化コンテキストの中に放り込まれる。このときに永続化コンテキスト内で管理されているエンティティはmanagedという状態になる。永続化コンテキストの中に入れたエンティティを、アプリケーション側で変更してからsaveを実行してもDBには反映されない。saveはアプリケーションで行ったエンティティの変更を永続化コンテキストに反映させているだけなので、永続化コンテキストからDBに書き込みを行うには別途flush(commit)する必要がある。ただこの辺りはアプリケーションや使ってるフレームワークがどう処理するかによって異なる。

管理されていたエンティティが永続化コンテキストから切り離されるとエンティティはdetached状態になる。
detached状態のエンティティはDBから取得したり、エンティティの変更を反映させたりといった処理ができなくなる。開発している時、PlayのインメモリDBを使っていると時々出てくる「detached entity passed to persist」というエラーは、操作しようとしたエンティティが何らかの理由で永続化コンテキストから切り離されているため、操作ができないという意味になる。

エンティティに対して行う処理:
EntityManagerがエンティティに対して行う処理にはpersist, remove, merge, refreshがある。persist, removeは永続化コンテキストに対する処理なので、上で触れたように実行しても実際にアプリケーションで行ったエンティティへの変更がデータベースに反映されるのはflush(commit)処理の後になる。

persist: 新しいオブジェクトを永続化コンテキストに追加する
remove: 永続化コンテキストにあるエンティティを削除する
merge: detached状態のエンティティをmanaged状態にする
refresh: エンティティの内容をデータベースと同期する。変更内容はデータベースのもので上書きされる

clearはすべてのエンティティをdetached状態にする。
closeは永続化コンテキストを破棄してEntityManagerを終了する。

ref: http://openjpa.apache.org/builds/latest/docs/docbook/manual.html#jpa_overview_em_lifecycle

トランザクション
トランザクションは追加、更新、削除といったDB処理をまとめた作業の単位。トランザクションに対する命令にはbegin, commit, rollbackがある。トランザクションを開始するにはbeginを呼び出す。実施するにはcommitを呼び出す。beginからcommitまでの間に行うDB処理を記述する。トランザクション中に処理が失敗した場合はrollbackによりトランザクションで行おうとしていたDBへの処理をすべて取り消すことができる。
ref1: http://openjpa.apache.org/builds/latest/docs/docbook/manual.html#jpa_overview_emfactory_perscontext_trans
ref2: http://www.techscore.com/tech/sql/SQL11/11_01.html/

リレーション:
エンティティと関連付けられたエンティティ(OneToManyとか)に対する処理を自動で行う場合はCascadeTypeを指定する。
CascadeType.PERSIST: 新規に永続化コンテキストにエンティティを追加する場合、このフィールドのエンティティも追加する。PERSISTの指定がなく、永続化コンテキストに追加されていないエンティティが関連付けられているとエラーが出る
CascadeType.REMOVE: 親が削除されると指定したフィールドのエンティティも削除される
CascadeType.REFRESH: 親がrefresh()されるとこのフィールドのエンティティもrefresh()される
CascadeType.MERGE: 親がmerge()されるとこのフィールドのエンティティもmerge()される
ref: http://openjpa.apache.org/builds/latest/docs/docbook/manual.html#jpa_overview_meta_cascade

関連付けされたフィールドをどのように呼び出すかをEagerかLazyで指定できる。
Eager Lodingはフィールドの呼び出しを最初の呼び出しで行う。
Lazy Loadingはそのフィールドにアクセスがあった時点でフィールドをデータベースから呼び出す。
顧客テーブルと電話番号テーブルが1対多で関連付けされていて「山田さん」には「携帯」「自宅」「実家」という複数の電話番号がひも付けされているとする。Eager Loadingを指定すると、親になる「山田さん」エンティティを呼び出したときに、一緒に電話番号たちも持ってくる。Lazy Loadingだと「山田さん」エンティティだけを先に呼び出しておいて、電話番号はアプリケーション側で呼ばれてから(iteratorへのアクセス等、利用されるときに)取ってくる。
ref: http://openjpa.apache.org/builds/latest/docs/docbook/manual.html#jpa_overview_meta_fetch