JavaEE 7 JPA 2.1の新機能 FUNCTION 句

JPQLなどJPAの機能ではカバーできない、RDB固有の機能として関数がある。countやsumなど基本的なものはあるが、それから離れるとダメだ。かといって無駄に関数を増やしたところでユーザー定義関数などもありとてもカバーしきれない。

というわけで、JPA2.1ではついにユーザー定義を含めて関数を自由に呼び出せるようになった。

Entity

@Entity
@Access(AccessType.FIELD)
public class JoinEntity implements Serializable {
    @Id
    public int id;
    public String name;

    public JoinEntity() {
    }

    public JoinEntity(int id, String name) {
        this.id = id;
        this.name = name;
    }
    
}

idとnameのカラムを持つ。

利用例

Derbyの組み込みモードで利用している。同一JVMなため、ユーザー定義関数を簡単に扱えるためだ。ネットワークモードの場合はそちらのVMに設定が必要なのでそこそこ面倒なので注意。

public class DerbyEmbedded2 {

    public static String join(int in1, String in2) {
        return String.format("%03d:%s", in1, in2);
    }

    public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpatestPU2");
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();

        //ユーザー定義関数を設定。
        em.createNativeQuery("create function "
                + "func_join( in1 integer, in2 varchar(256)) returns varchar(256)"
                + "language java "
                + "external name 'embedded.DerbyEmbedded2.join'"
                + "parameter style java").executeUpdate();

        //サンプルデータ追加
        em.persist(new JoinEntity(1, "はろー"));
        em.persist(new JoinEntity(20, "関数"));
        em.persist(new JoinEntity(300, "わーるど"));

        {
            String jpql = "select function('func_join', je.id , je.name) from JoinEntity je order by je.id";
            TypedQuery<String> query = em.createQuery(jpql, String.class);
            for (String text : query.getResultList()) {
                System.out.println(text);
            }
        }
    
        em.getTransaction().rollback();

        em.close();
        emf.close();
    }
}

上のコードはfunc_joinという名前のユーザー定義関数を作成している。実装はjoinメソッド。

Derbyのユーザー定義関数はJavaのコードで書けるため、とっても楽。public staticなメソッドをユーザー定義関数にできるのだ。


JPQLでの使い方も簡単。function(関数名 , パラメータ...)となっている。

実行結果

001:はろー
020:関数
300:わーるど

joinメソッドが各行で呼ばれているのがわかる。

そのほか

ユーザー定義でない関数ももちろんOK。以下は文字列の長さを返すlength関数。

{
    String jpql = "select function('length', je.name) from JoinEntity je order by je.id";
    TypedQuery<Integer> query = em.createQuery(jpql, Integer.class);
    for (Integer len : query.getResultList()) {
        System.out.println(len);
    }
}


JPQLの関数との組み合わせもOK。

{
    String jpql = "select sum(function('length', je.name)) from JoinEntity je";
    Integer result = em.createQuery(jpql, Integer.class).getSingleResult();
    System.out.println(result);
}


sum関数をJPQLのsum関数ではなくてRDBの本来のsum関数で呼ぶこともできる。意味はないと思うけど。

{
    String jpql = "select function('sum', function('length', je.name)) from JoinEntity je";
    Integer result = em.createQuery(jpql, Integer.class).getSingleResult();
    System.out.println(result);
}

これにより、JPAだけでかなりの範囲がカバーされる。定型的なことには強かったJPAだが、その例外的なRDB固有の関数にアクセスできるようになったのは大きな進歩だ。これならばたしかにJPQLの言語拡張を最小限で済む。Criteriaももちろん対応しているので調べてみるとよいだろう。