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ももちろん対応しているので調べてみるとよいだろう。