EJB 3.0(Public Draft)入門記 Java Persistence API Chapter3 その11

今回はQuery APIについてです。すでにちょこっと使っていますが。

3.5 Query API

Query APIは静的クエリ(たとえば名前つきクエリ)や動的クエリの両方に使われるそうです。Query APIは名前つきパラメータとのバインディングやページングもサポートするらしいです。
動的クエリってパラメータを渡せるクエリだけでなく条件によってwhere節が変化するようなクエリも含んでいるんでしょうか?

3.5.1 Query Interface

インタフェースの定義は次のとおり

public interface Query {
  public List getResultList();
  public Object getSingleResult();
  public int executeUpdate();
  public Query setMaxResults(int maxResult);
  public Query setFirstResult(int startPosition);
  public Query setHint(String hintName, Object value);
  public Query setParameter(String name, Object value);
  public Query setParameter(String name, java.util.Date value, TemporalType temporalType);
  public Query setParameter(String name, java.util.Calendar value, TemporalType temporalType);
  public Query setParameter(int position, Object value);
  public Query setParameter(int position, java.util.Date value, TemporalType temporalType);
  public Query setParameter(int position, java.util.Calendar value, TemporalType temporalType);
  public Query setFlushMode(FlushModeType flushMode);
}

getResultListとgetSingleResultとexecuteUpdate以外は全部Queryを返します。setFlushModeがここにもある、なんかいろんなところ(EntityManagerとかアノテーションとか)で設定できますね。
setMaxResultsやsetFirstResultはページングに関連するメソッドぽいです。
setHintで渡すヒントはデータベースのヒントとは別ものだというのがどこかに書いてあったんですけどどこかは忘れました。
DateやCalendarをsetParameterするときはTemporalTypeをいっしょに渡すようになってます。TemporalTypeって何でしょうか?

select節がふたつ以上の値から成るようなクエリの結果セットはObjectの配列だそうです。
IllegalArgumentExceptionが次のときにスローされるそうです。

  • 指定されたパラメータの名前とクエリ文字列の名前つきパラメータが一致しないとき
  • 位置を示す値(positional value)とクエリ文字列の位置パラメータ(positional parameter)が一致しないとき
  • パラメータの型がクエリに不適切なとき

Queryインタフェースのメソッド内でスローされる実行時例外によりトランザクションロールバックされます。

3.5.1.1 Example
例を写してみます。

public List findWithName(String name) {
  return em.createQuery(
    "SELECT c FROM Customer c WHERE c.name LIKE :custName")
    .setParameter("custName", name)
    .setMaxResult(10)
    .setResultList();
}

名前つきパラメータ使ってます。setMaxResult(10)で先頭から10件のみを取得しているのだと思います。

3.5.2 Parameter Names

名前つきパラメータは「:」で始まらなければいけないそうです。上の例で言うと「:custName」ですね。名前つきパラメータはEJB QLでしか使えなくて、ネイティブなクエリには使用できないそうです。

3.5.3 Named Queries

名前つきクエリはメタデータであらわされる静的なクエリだそうです。EJB QL、SQLどちらをつかっても定義できるそうです。それぞれにNamedQuery、NamedNativeQueryというアノテーションがあります。

ドキュメントのサンプルコードを適当に補って試してみたいと思います。
NamedQueryアノテーションをエンティティに指定して要素にクエリとクエリの名前を定義します。NamedQueryアノテーションはエンティティに指定しますが、スコープは永続ユニット(persistence unit)なんだとか。NamedQueryのようなアプリケーションレベルのメタデータをどのように扱うかはまだ検討中だそうです。エンティティには定義したくない気はします。

@Entity(access = AccessType.FIELD)
@NamedQuery(
    name = "findAllCustomersWtihName", 
    queryString = "SELECT c FROM Customer c WHERE c.name Like :custName")
public class Customer {
  @Id(generate = GeneratorType.AUTO)
  private int id;

  private String name;

  public Customer() {
  }

  public Customer(String name) {
    this.name = name;
  }
  
  //getter, setter省略
}

ステートレスセッションBean。EntityManagerのcreateNamedQueryメソッドを使っています。

@Stateless
public class ClientBean implements Client {

  @PersistenceContext
  private EntityManager em;
  
  public void prepare() {
    Customer a = new Customer("Smith Paul");
    Customer b = new Customer("Smith Jhon");
    Customer c = new Customer("Jhon Smith");    
    em.persist(a);
    em.persist(b);
    em.persist(c);
  }

  public List findWithName(String name) {
    return em.createNamedQuery("findAllCustomersWtihName")
    .setParameter("custName", name)
    .getResultList();
  }
  
  public static void main(String[] args) throws Exception {
    EJB3StandaloneBootstrap.boot(null);
    EJB3StandaloneBootstrap.deployXmlResource("ejb3-deployment.xml");

    InitialContext ctx = new InitialContext();
    Client client = (Client) ctx.lookup(Client.class.getName());

    client.prepare();
    List customers = client.findWithName("Smith%");
    for (Object each : customers) {
      Customer c = (Customer)each;
      System.out.println(c.getName());
    }
    
    EJB3StandaloneBootstrap.shutdown();
  }
}

実行結果

Hibernate: insert into Customer (name, id) values (?, null)
Hibernate: call identity()
Hibernate: insert into Customer (name, id) values (?, null)
Hibernate: call identity()
Hibernate: insert into Customer (name, id) values (?, null)
Hibernate: call identity()
Hibernate: select customer0_.id as id0_, customer0_.name as name0_ from Customer customer0_ where customer0_.name like ?
Smith Paul
Smith Jhon

3.5.4 Polymophic Queries

デフォルトですべてのクエリがポリモーフィックだそうです。つまり、クエリのFROM節は明示的に参照する特定のクラスのインスタンスだけでなくそのサブクラスも対象にするんだとか。
例を見るとすぐにわかります。

@Entity
public class Employee {
  //...
}
@Entity
public class Manager extends Employee {
  //...
}
@Entity
public class Exempt extends Employee {
  //...
}

上記のような継承関係をもつエンティティがある場合、

select e from Employee e

というクエリがEmployeeだけでなくManagerやExemptも対象にするということです。

いままでの入門記で何回か使ってました。O/RマッピングといえばS2Daoしか使ったことなくてあまり意識してませんでしたがポリモーフィックなクエリって強力かもと思いはじめました。


次の「3.5.5 SQL Queries」はEJB QLじゃなくてネイティブなSQLを使ったクエリについてです。EJB QLの表現力がどれほどのものかまだわかってないので、ネイティブなSQLが実際にどれほど必要となるのかわかってないです。そんなに使うことあるのか?ちょっとこの節とばしちゃおうかなぁという誘惑が...。うーん、駄目だ駄目だ、ちゃんとやります。でも気持ちを切り替えたいので次回に。