「JPA入門」本が今日発売されました。

Seasar2とHibernateで学ぶデータベースアクセス JPA入門

Seasar2とHibernateで学ぶデータベースアクセス JPA入門

新宿の紀伊国屋にいったら棚に10冊ほど積まれていました。自分で書いたものが売られているっていうのは不思議な感じですね。記念に携帯のカメラで写そうかなって思ったんですけど、お店の人の目が気になってあきらめちゃいました。。。

よかったら是非読んでみてください。


話は変わりまして、竹添さん、小林さん書評ありがとうございます!

Java Persistence API(JPA)入門本

Seasar2とHibernateで学ぶデータベースアクセス JPA入門

Seasar2とHibernateで学ぶデータベースアクセス JPA入門

Java Persistence APIに関する本を書きました。

私は、仕様書を読み、試行錯誤でHibernateを動かしてJPAを学んだのですが、だれかがあらかじめこうやって教えてくれたらもっと早く理解できただろうなぁという観点からまとめました。まだ発売までちょっと日がありますが、よかったら読んでみてくださいm(_ _)m。

目次はMYCOM BOOKSの紹介ページで見ることができます。

HibernateとS2DaoとS2JDBCの考え方で思ったこと

エンティティとテーブルのマッピング方針の違いとかに焦点があてられているので特に触れられていないですけど、ストアドプロシージャーとかストアドファンションがどれだけ簡単に呼び出せるかで比較してもおもしろいと思います。この観点で比較すると、ダントツでS2JDBCが便利です。
S2JDBCは、OraclePostgreSQLのようにOUTパラメータで結果セット(カーソル)を返すストアドに対応しているし、複数の結果セットを返すストアドも呼び出せます。(ところで、Railsはストアド呼び出しのサポートってあるのかなぁ?)


ストアド好きな人にはきっと魅力的なんじゃないかなぁって思います。


あと、S2JDBCJPAのトラぶりやすい機能を除いたプロダクトであることは間違いないですけど、名前が表すとおりJDBCに一番近いプロダクトでもあります(selectBySql()やselectBySqlFile()を使う場合)。

内製フレームワークとかでJDBC直または薄くラップして使っている現場ってまだまだ多いと思うんですが、2JDBCは、そういうところで導入しても効果があるんじゃないでしょうか。いきなり新しい方法を取り入れるといろいろコストがかかりますしね。

そういう環境ではDIとかAOPとかいろんな制約で使いにくかったりすると思うんですが、そのときは、わりきってサービスロケータでJdbcManagerを取得して使うって方法で十分有効じゃないかって思います。

JPQLのdistinct

気になったのでちょっと調査。

部署(Department)と従業員(Employee)のエンティティが多対1のときで考えてみます。
JPQLでdistinctつけてDepartmentからEmployeeをfetch joinします。

JPQL
List<Department> list = entityManager
        .createQuery(
                "select distinct d from Department d left outer join fetch d.employees")
        .getResultList();

すると、こんなSQLに変換されます。

SQL
  select
        distinct department0_.deptno as deptno0_0_,
        employees1_.empno as empno1_1_,
        department0_.dname as dname0_0_,
        department0_.loc as loc0_0_,
        department0_.versionNo as versionNo0_0_,
        department0_.active as active0_0_,
        employees1_.ename as ename1_1_,
        employees1_.job as job1_1_,
        employees1_.mgr as mgr1_1_,
        employees1_.hiredate as hiredate1_1_,
        employees1_.sal as sal1_1_,
        employees1_.comm as comm1_1_,
        employees1_.tstamp as tstamp1_1_,
        employees1_.deptno as deptno1_1_,
        employees1_.city as city1_1_,
        employees1_.zip as zip1_1_,
        employees1_.deptno as deptno0__,
        employees1_.empno as empno0__ 
    from
        Dept department0_ 
    left outer join
        Emp employees1_ 
            on department0_.deptno=employees1_.deptno

SQLにもdistinctがつくので、ResultSet上からの重複はなくなるわけですが、Departmentだけを見ると何度も同じものが登場します。
イメージとしてはこんな感じ。

deptno empno
1 1
2 2
1 3
2 4
3 5

なのでこのままResultSetをDepartmentエンティティに変換すると重複するエンティティが存在することになっちゃいます。

Hibernateはどうしているかというと、distinctがついているときはちゃんとJavaのロジックで重複を取り除いていますね。
(QueryTranslatorImplのlistメソッド)

Hibernateの商用版バイナリ

JBoss COMPASS Tokyo 2007に行ってきたんですが、面白いこと聞きました。
HibernateにはOSSのコミュニティ版とは別に商用版があって、バイナリが異なるらしいです。

商用版バイナリって機能的にどうちがうんですかねぇ。とっても気になります。

Queryのiterate()とscroll()

なんかよくわかっていなかったので、試してみた。

iterate()

テストコード
public void testIterator() throws Exception {
    Session session = getSession();
    Iterator<Employee> iterator =
        session.createQuery("from Employee e where e.id < 3").iterate();
    try {
        while (iterator.hasNext()) {
            Employee e = iterator.next();
            assertTrue(e instanceof HibernateProxy);
            assertTrue(session.contains(e));
            e.getEmployeeName();
        }
    } finally {
        Hibernate.close(iterator);
    }
}
実行されるSQL
Hibernate: 
    select
        employee0_.EMPLOYEE_ID as col_0_0_ 
    from
        Employee employee0_ 
    where
        employee0_.EMPLOYEE_ID<3
Hibernate: 
    select
        employee0_.EMPLOYEE_ID as EMPLOYEE1_2_0_,
        employee0_.EMPLOYEE_NO as EMPLOYEE2_2_0_,
        employee0_.EMPLOYEE_NAME as EMPLOYEE3_2_0_,
        employee0_.MANAGER_ID as MANAGER9_2_0_,
        employee0_.HIREDATE as HIREDATE2_0_,
        employee0_.SALARY as SALARY2_0_,
        employee0_.department_id as department8_2_0_,
        employee0_.ADDRESS_ID as ADDRESS7_2_0_,
        employee0_.VERSION as VERSION2_0_ 
    from
        Employee employee0_ 
    where
        employee0_.EMPLOYEE_ID=?
Hibernate: 
    select
        employee0_.EMPLOYEE_ID as EMPLOYEE1_2_0_,
        employee0_.EMPLOYEE_NO as EMPLOYEE2_2_0_,
        employee0_.EMPLOYEE_NAME as EMPLOYEE3_2_0_,
        employee0_.MANAGER_ID as MANAGER9_2_0_,
        employee0_.HIREDATE as HIREDATE2_0_,
        employee0_.SALARY as SALARY2_0_,
        employee0_.department_id as department8_2_0_,
        employee0_.ADDRESS_ID as ADDRESS7_2_0_,
        employee0_.VERSION as VERSION2_0_ 
    from
        Employee employee0_ 
    where
        employee0_.EMPLOYEE_ID=?
  • 最初にIDを取得するクエリを実行している。
  • クライアントにはプロキシを返す。
  • ID以外のプロパティにアクセスする段階で遅延ローディングされる。
    • 毎回プロパティにアクセスするとN+1回のSELECTが実行されることになる。
    • でも、すでにエンティティが永続コンテキストに入っていればSELECTは実行されない。
  • 永続コンテキストに格納される。
  • Iteratorの中で一番目のクエリのResultSetやPreparedStatementを持っているので自分でクローズしなきゃだめ。

scroll()

テストコード
public void testScroll() throws Exception {
    Session session = getSession();
    ScrollableResults results =
        session.createQuery("from Employee e where e.id < 3").scroll();
    try {
        while (results.next()) {
            Employee e = (Employee) results.get(0);
            assertFalse(e instanceof HibernateProxy);
            assertTrue(session.contains(e));
            e.getEmployeeName();
        }
    } finally {
        results.close();
    }
}
実行されるSQL
Hibernate: 
    select
        employee0_.EMPLOYEE_ID as EMPLOYEE1_2_,
        employee0_.EMPLOYEE_NO as EMPLOYEE2_2_,
        employee0_.EMPLOYEE_NAME as EMPLOYEE3_2_,
        employee0_.MANAGER_ID as MANAGER9_2_,
        employee0_.HIREDATE as HIREDATE2_,
        employee0_.SALARY as SALARY2_,
        employee0_.department_id as department8_2_,
        employee0_.ADDRESS_ID as ADDRESS7_2_,
        employee0_.VERSION as VERSION2_ 
    from
        Employee employee0_ 
    where
        employee0_.EMPLOYEE_ID<3
  • エンティティの全プロパティを取得するクエリが実行される。
  • クライアントに返すのはプロキシじゃない。
  • JDBCのスクロールの機能を使ってアクセスされる。
  • 永続コンテキストに格納される。
  • ScrollableResultsの中でResultSetやPreparedStatementを持っているので自分でクローズしなきゃだめ。

Java Persistence API(JPA)はHibernateのNative APIより簡単

Hibernateを使うのだけどJPAがよくわからないからNative APIを直接使っているという方や、Hibernateを学習したいのだけど機能が多すぎて辟易しているという方が実は結構多いんじゃないかと感じています。そこで、今日はSeasar2とは関係なくHibernateのNative APIよりJPAの方が便利だよというところを紹介してみます。

マッピングが簡単

JPAにはマッピングのためのアノテーションが用意されているのでマッピングは結構簡単です。普通(?)に使う分ならば覚えることも多くないです。前にこの日記で挙げたアノテーションだけで一般的なケースはすべて対応できます。
http://d.hatena.ne.jp/taedium/20070604#p1

Native APIを使う場合にxmlマッピングを書くのは大変だと思うのでアノテーションはお勧めです。xmlで書くと必ずといっていいほど書き間違いますが、アノテーションで書く場合はあまりまちがいません。まちがってもコンパイルエラーですぐ気づきますし。

エンティティの登録が簡単

xmlで定義を書く場合、エンティティを登録するxmlが必要だと思うのですがこれは意外に面倒くさいです。JPAとして使うとHibernateがエンティティを自動的に検出してくれるので楽チンです。

学習が簡単

僕の感覚としては2倍とか3倍くらいNative APIよりJPAのほうが簡単です。Hibernateは機能が多すぎると思うのですよね。JPAがわかってくると自然とHibernateもわかってきます。JPAはいいからとにかくHibernateを使いたいだけなんだといった方でもまずはJPAとして使ってみるのがいいと思います。
JPAとして使うとHibernateとしての機能を十分に活かしきれないのではと思う方がいるかもしれませんが、これは大丈夫です。いつでもHibernateの機能を使えるからです。JPAのEntityManagerインターフェースには次のようなメソッドが定義されています。

public Object getDelegate();

このメソッドはSessionを返すので必要なところで次のようにキャストして使えばいいです。

Session session = (Session) entityManager.getDelegate();
Filter filter = session.getEnabledFilter(...);
おまけ

学習は簡単ていうけどJPAは情報量が少ないじゃんて思うかもしれません。大丈夫、メーリングリストがあるのでいろいろ質問しちゃいましょう。
https://ml.seasar.org/mailman/listinfo/jpa