JavaEE 7 JPA 2.1の新機能 ネイティブクエリの改善

JPAは1.0からネイティブクエリに対してEntityにマッピングする機能はあった。
面倒ではあるけど、まぁ使いたい場合もあるのだろう。

あくまでもObjectとマッピングするのが目的と考えるとこれでいいのだろうが、実際Entityと素直にマッピングするような用途ならJPQLで足りることが多いはずだ。

JPQLでは1.0からEntity以外にマッピングする方法が用意されている。

NEW句だ。様々なクエリを作る場合これが活躍する。というか、これが一番大事な機能だったりする。


と、JPQLを利用する場合、Entityを利用する場合はいいのだが、それから外れると一気にサポートがなくなっていた。


JPAは2.1でやっとネイティブクエリへのサポートが強化された。

一言で表すとネイティブクエリに対してNEW句が使えるようになった。


では、サンプルを作成してみる。Entityやテーブルの構成は前々回と変わらず(コンバータを適用前に戻す)。


やりたいことは住所でグループ化してその件数を数えるというもの。これだけならJPQLでいいのだがネイティブクエリであえてやってみる。

データベース

customer

id name address
1 A 北海道
2 B 青森県
3 C 北海道

これからグループ化するための文字列address、件数の数値型countの2つを返すようにする。
発行するSQLは以下のもの

select address , count(*) as count
  from customer
  group by address

実行結果は以下になる。

address count
北海道 2
青森県 1

検索結果を格納するもの

public class Hoge {
    public final String address;
    public final int count;

    public Hoge(String address, int count) {
        this.address = address;
        this.count = count;
    }
}

必要なのはコンストラクタ。ここにSQLの結果をマッピングさせる。
つまり、ロジックを入れ込みたい場合はここで処理を書けばよろしい。

Entity

@SqlResultSetMapping(
        name = "address_count",
        classes = {
            @ConstructorResult(targetClass = Hoge.class,
            columns = {
                @ColumnResult(name="address"),
                @ColumnResult(name="count")
            })
        })

@Entity
@Access(AccessType.FIELD)
public class Customer implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    public Long id;
    public String name;
    public String address;
    
}

必要なのは@SqlResultSetMapping。classes 属性がJPA 2.1で追加されていてここにマッピングする。
ColumnResultにはコンストラクタに渡す型を指定することもできる。
たとえば上のcountはInteger型であるが、ここのコンストラクタがLongの場合Longの指定が必要。

実行するコード

public static void main(String[] args) throws SQLException {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpatestPU");
        
        EntityManager em = emf.createEntityManager();

        String sql = "select address , count(*) as count from customer group by address";
        Query q = em.createNativeQuery(sql, "address_count");
        List<Hoge> result = q.getResultList();
        for(Hoge h : result){
            System.out.printf("%s=%d件%n",
                    h.address, h.count);
        }
        
        em.close();
        
        emf.close();
        
}

createNativeQueryでEntityの@SqlResultSetMappingに設定した名前を渡す。それだけ。


まぁ、手間は手間であるが多少は前進はした。
マッピングを静的に設定することからNamedNativeQueryも登録しておくのがいいのだろうか。


順調にJPAは使いやすくはなっていってるが、ネイティブクエリは他と比べるとやはり弱い。基本ネイティブクエリは使わないもの、どうしても必要がある時の最終手段として仕方なく使うもの、と考えればさほど問題はないのかもしれない。生Connectionからの処理も普通に書けるし、それは別ライブラリ使えばいいという割り切り方もありだろう。

JPA 2.1ではストアドプロシージャやネイティブのファンクションを使えるのでDB固有のガリガリ処理するものはそちらにまかせるというのがいいのだろう。Entityからデータベースを作成するというのも正式に実装されたわけで、ますます新規アプリではDBありき、よりEntityクラスありきで設計するのが正しい、となるだろう。

SQLというネイティブクエリ中心ではO/Rマッパを扱いきれないため、仕方がないところ。