Kuina-Dao 開発記 Named Query

KuinaDaoInterceptor での Named Query サポートを SVN にコミットしました.
このテストケースのために,さっそく Diigu を使っています.でもでも...
Ant ファイル diigu/diigu-test.xml をプロジェクトのビルダーとして登録してあるのですが,ソースをいぢった後なんかは明示的に動かしてやらないとダメっぽい?
早いところ Eclipse プラグインを提供したいと思う今日この頃です.


さて,本題.
JPA の Named Query は,エンティティのアノテーションとして指定することができます.

@Entity
@NamedQuery(name="Employee.findByName" query="SELECT e FROM Employee e WHERE e.name = :name")
public class Employee {
    ...
}

あるいは,マッピングファイル (XML) で指定することもできます.

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings
  xmlns="http://java.sun.com/xml/ns/persistence/orm"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_1_0.xsd"
  version="1.0"
>
  <named-query name="Employee.findByName">
    <query><![CDATA[
      SELECT e FROM Employee e WHERE e.name = :name
    ]]></query>
  </named-query>
</entity-mappings>

個人的にはアノテーションよりマッピングファイルがお気に!
Hibernate の場合,マッピングファイルを「エンティティクラス名.xml」という名前でエンティティクラスと同じディレクトリに置けば勝手に見つけてくれるようです. META-INF/orm.xml 以外は明示的に persistence.xml に並べないとダメらしい...


この Named Query を Kuna-Dao で利用するには次のような DAO インタフェースを用意します.

public interface EmployeeDao {
    List<Employee> findByName(String name);
    ・・・
}

こいつに KuinaDaoInterceptor を適用すると,このメソッドを呼ぶだけで Named Query "Employee.findByName" にパラメータをバインドして実行します.


その際,KuinaDaoInterceptor はメソッドの戻り値からエンティティ名を解決し,メソッド名と連結して Named Query を探します.
エンティティ名というのは通常はエンティティクラスの非修飾名.
でもでも,

@Entity(name="Emp")
public class Employee {
    ...
}

みたいに指定することもできちゃいます.
この場合,Kuina-Dao は "Emp.findByName" を探します.


戻り値がエンティティでない場合もあります.
例えば

  <named-query name="Employee.getNameById">
    <query><![CDATA[
      SELECT e.name FROM Employee e WHERE e.id = :id
    ]]></query>
  </named-query>

という Named Query の場合,DAO のメソッドは

    String getNameById(int id);

ってことになってしまうので,シグネチャからエンティティを見つけることができません.
そんな場合は,

    @TargetEntity(Employee.class)
    String getNameById(int id);

アノテーションでエンティティを教えてあげてください.
このアノテーションはインタフェースに付けることもできます.
KuinaDaoInterceptor は,

  1. メソッドの戻り値型
  2. メソッドの @TargetEntity
  3. インタフェースの @TargetEntity

の順でエンティティを解決しようとします.


Named Query の名前をアノテーションで直接指定することもできます.

    @QueryName("Employee.getNameById")
    String getNameById(int id);


メソッドの引数は JPQL 中の Named Parameter と解釈しますが,特殊な名前が二つあります.

  • firstResult
  • maxResults

引数の名前がこのいずれかだった場合はパラメータではなく,javax.persistence.QuerysetFirstResult(int) あるいは setMaxResults(int) の指定だと解釈します.
なので,

    List<Employee> findByDepartmentName(String name, int firstResult, int maxResults);

なんてメソッドを用意すればページングできちゃいます.
引数に @FirstResult アノテーションあるいは @MaxResults アノテーションを付けることで,引数名に関わりなくページングの指定をすることもできます.
逆に @NamedParameter を付けることで,firstResult あるいは maxResults という名前の引数でも Named Parameter として扱わせることができます.


なんとか動いたというレベルでしかありませんが,こんな感じで Named Query が使えます.
明日からは実行時の条件にあわせて JPQL を生成する Dynamic Query に取りかかります.