--- title: JPA2.0(Hibernate4)ノウハウまとめ tags: [] categories: ["Programming", "Java", "javax", "persistence"] date: 2012-10-08T18:28:02Z updated: 2012-10-08T18:28:02Z --- JPAを使う始めていろいろノウハウがたまってきたのでメモしておく。 ### はじめに JPAを使うとSQLを書かなくて済んだり、RDBMSの違いを 意識しなくて良い場合もあるが、SQLやRDBMSを学習しなくて よいという訳ではないことに注意。 ### 前提 * JPA 2.0 * Hibernate 4.X を想定。 他のプロバイダのことは考えていない。極力JPA標準の仕様を使うが、便利であればHibernate実装依存のものも使う(ノウハウのほとんどはHibernate依存)。 自分はSpring+JPA(Hibernate)を使っているので、JavaEE6の場合とは少し違うかも。 ### SQL Logging 開発中は必ずSQLログを出力すること。意図せぬSQLの発行や、flush忘れ等に気づける。 * hibeneteの場合、persistence.xmlに * `` * `` * `` * 標準ログより[log4jdbc][1]を使った方がよい。 ### Commit Log SQLだけではなく、トランザクションのコミットの有無もログに出しておくとトランザクションの設定ミスに気づける。 ### LazyLoad 関連に関しては基本的にLazyにしておく。`@ManyToOne`、`@OneToOne`はデフォルトでEAGERなので`@ManyToOne(fetch=FetchType.LAZY)`を指定してLazyに変える。 `@Lob`など使用頻度の低いフィールドにも`@Basic(fetch=FetchType.LAZY)`を付ける。 * OpenEntityManagerInViewFilterパターン * ExtendedPersistenceContext EAGERでフェッチしたい場合はQueryで実現する。 * N+1 Selects ProblemにはFETCH JOINかReportingで説明するsummary objectを使用して対処。 example // SELECT * FROM Posts List posts = entityManager.createQuery("SELECT x from Post x", Post.class) .getResultList(); for (Post post : posts) { // lazy loading of comments list causes: // SELECT * FROM Comments where post_id = @p0 for (Comment comment : post.getComments()) { //print comment... } } fetch joinを使って書き直す // SELECT * FROM Posts x LEFT JOIN Comments c ON c.post_id = x.id List posts = entityManager.createQuery("SELECT DISTINCT x From Post x FETCH JOIN x.comments", Post.class) .getResultList(); ### Reporting 検索や一覧表示で複数のエンティティをジョインした結果を出力する必要がある場合、summary object(必要なフィールドだけを持つPOJO=DTO)の使用を検討すること。必要なフィールドだけ取得できるので、性能劣化を抑えることが出来る。 コンストラクタ式 fetch joinを二回以上する場合は要検討。 WARNING: firstResult/maxResults specified with collection fetch; applying in memory! が出てたら注意。全件を取得した後にメモリ上でページングしているため。この場合コンストラクタ式でsummary objectを作成すること。 ### Batch Update #### JPQL Batch Update List users = entityManager.createQuery("SELCT x FROM User"); for (User user : users) { user.setActive(false); } entityManager.flush(); ユーザー数だけupdate文が発行される。 entityManager.createQuery("UPDATE User SET active = false").executeUpdate(); ただし、楽観ロック用の`@Version`の更新は行われない点に注意。 #### JDBC Batch Update for (int i = 0; i < N; i++) { User u = new User(i , "name" + i); entityManager.save(u); } entityManager.flush(); 一件ずつ insert(Statement#executeUpdate)の代わりに、複数件まとめてinsert(Statement#executeBatch)させる。Hibernateの場合、`hibernate.jdbc.batch_size`に0より大きい値を設定すればexecuteBatchになる。 `` という設定すれば30件ずつexecuteBatch が実行されるので、 N=3000の場合、SQL発行回数は100回に抑えられる。 ### Robustness Queryを書く場合はNamed Query推奨。起動時にクエリがコンパイルされるので性能も良いし、文法ミスを検出できる。またSQLインジェクションを防ぐ。 保守性を高めるためにxmlファイルに記述するのが良いとおもう。persistence.xmlに xxx/yyy/ItemOrm.xml エンティティ毎(Repository/DAO毎)にxmlを作成するのが良い。 Queryのnameはできれば定数にしたい。xmlから定数クラスを自動生成するツールを作ったので、今度公開する。 org.hibernate.cfg.annotations.QueryBinderカテゴリをdebugレベルにしておくと、起動時にNamedQuery一覧を出力できる。 を設定しておくと、NamedQueryのnameがコメントに付加されるため、 DB側からのトレーサビリティが高くなる。 動的クエリはCriteriaQueryで良いが読みにくい。。 ### Tips Hibernate専用であるが、 `@Where`をつけると参照SQLのWHERE句に常に特定の条件をつけることができる。 @Where(clause = "active = true") @Entity public class User { @Id private Integer id; @Column private String name; @Column private boolean active; // setter/getter... } [1]: http://blog.ik.am/entry/view/id/115/title/Log4JDBC%E3%81%A7SQL%E3%83%AD%E3%82%B0%E3%82%92%E5%87%BA%E5%8A%9B%E3%81%99%E3%82%8B/