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

(EJB QLではなく)SQLを使ったクエリについてです。

3.5.5 SQL Queries

SQLの結果は複数エンティティ、スカラー値、またはその両方の組み合わせで構成されるそうです。クエリから返される結果には異なった型のエンティティを含めることができるらしいです。スカラー値って日本語でいうと単一の値?ここでは、エンティティに含まないで単一で扱いたい値のことをさしているような気がします。
SQLのクエリによって複数のエンティティが返されるときはSqlResultSetMappingアノテーションを使うらしいです。単一のエンティティが返されるときはSqlResultSetMappingアノテーションの使用は必須ではないらしいです。
エンティティを返すときは関連するエンティティの外部キーも含めてエンティティにマップされる値すべてをSQLのSELECT節に指定すべきだそうです。
追記:そういえば、書くの忘れてました。SQLを使う場合はEntityManagerのcreateNativeQueryメソッドにSQLを渡してQueryオブジェクトを取得します。



試してみます。

OrderエンティティとItemエンティティを使います。OrderとItemはManyToOneで関連は単方向です。それぞれのエンティティにSqlResultSetMappingを定義しました。最初はOrderのほうに複数定義しようかなと思ったんですが、ひとつのエンティティに複数定義はできないようです。定義をいくつも持ちたいときはどうするのでしょう。ところで、ドキュメントにはEntityResultの要素にentityClassがあるのですが、なぜか使えませんでした。変わりにnameにクラス名を指定しました。

@Entity(access = AccessType.FIELD)
@Table(name="ODR")
@SqlResultSetMapping(
    name="orderResults",
    entities=@EntityResult(name="study.ejb.entity.Order"))
public class Order {

  @Id(generate = GeneratorType.AUTO)
  private int id;

  private int quantity;

  @ManyToOne
  private Item item;

  public Order(){
  }
  
  public Order(Item item, int quantity) {
    this.item = item;
    this.quantity = quantity;
  }
  //getter, setter省略
}
@Entity(access = AccessType.FIELD)
@SqlResultSetMapping(
    name="orderItemResults",
    entities={
        @EntityResult(name="study.ejb.entity.Order"),
        @EntityResult(name="study.ejb.entity.Item")})
public class Item {
  
  @Id(generate = GeneratorType.AUTO)
  private int id;

  private String name;
  
  private String description;

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

ステートレスセッションBean。3つのビジネスメソッドを持ちます。queryはSqlResultSetMappingを使わないでOrderのみを取得するメソッド。queryOrderResultsはSqlResultSetMappingを使ってOrderのみを取得するメソッド。queryOrderItemResultsはSqlResultSetMappingを使ってOrderとItemを同時に取得するメソッドです。

@Stateless
public class ClientBean implements Client {

  @PersistenceContext
  private EntityManager em;

  public void prepare() {
    Item item = new Item("ポテチ", null);
    Order order1 = new Order(item, 5);
    Order order2 = new Order(item, 10);
    em.persist(item);
    em.persist(order1);
    em.persist(order2);
  }

  public List query() {
    return em
        .createNativeQuery(
            "select o.id, o.quantity, o.item_id "
                + "from Odr o, Item i where (o.item_id = i.id) and (i.name = 'ポテチ')",
            Order.class).getResultList();
  }

  public List queryOrderResults() {
    return em
        .createNativeQuery(
            "select o.id, o.quantity, o.item_id "
                + "from Odr o, Item i where (o.item_id = i.id) and (i.name = 'ポテチ')",
            "orderResults").getResultList();
  }

  public List queryOrderItemResults() {
    return em
        .createNativeQuery(
            "select o.id, o.quantity, o.item_id, i.id, i.name, i.description "
                + "from Odr o, Item i where (o.item_id = i.id) and (i.name = 'ポテチ')",
            "orderItemResults").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();
    System.out.println("\n## query");
    print(client.query());
    System.out.println("\n## queryOrderResults");
    print(client.queryOrderResults());
    System.out.println("\n## queryOrderItemResults");
    print(client.queryOrderItemResults());

    EJB3StandaloneBootstrap.shutdown();
  }

  private static void print(List values) {
    for (Object each : values) {
      if (each instanceof Order) {
        Order order = (Order) each;
        System.out.println(order.getItme().getName() + " : "
            + order.getQuantity());
      } else {
        Object[ ] o = (Object[ ]) each;
        Order order = (Order) o[0];
        Item item = (Item) o[1];
        System.out
            .println(item.getName() + " : " + order.getQuantity());
      }
    }
  }
}

実行結果

Hibernate: insert into Item (name, description, id) values (?, ?, null)
Hibernate: call identity()
Hibernate: insert into ODR (quantity, item_id, id) values (?, ?, null)
Hibernate: call identity()
Hibernate: insert into ODR (quantity, item_id, id) values (?, ?, null)
Hibernate: call identity()

## query
Hibernate: select o.id, o.quantity, o.item_id from Odr o, Item i where (o.item_id = i.id) and (i.name = 'ポテチ')
Hibernate: select item0_.id as id2_0_, item0_.name as name2_0_, item0_.description as descript3_2_0_ from Item item0_ where item0_.id=?
ポテチ : 5
ポテチ : 10

## queryOrderResults
Hibernate: select o.id, o.quantity, o.item_id from Odr o, Item i where (o.item_id = i.id) and (i.name = 'ポテチ')
Hibernate: select item0_.id as id2_0_, item0_.name as name2_0_, item0_.description as descript3_2_0_ from Item item0_ where item0_.id=?
ポテチ : 5
ポテチ : 10

## queryOrderItemResults
Hibernate: select o.id, o.quantity, o.item_id, i.id, i.name, i.description from Odr o, Item i where (o.item_id = i.id) and (i.name = 'ポテチ')
ポテチ : 5
ポテチ : 10

結果はすべて同じです。
queryとqueryOrderResultsメソッドを使った場合、OrderからItemへリレーションシップがはってあるのでItemが取得できちゃいます。queryOrderItemResultsを使ったときは一発でふたつのエンティティを取得してます。結果がObjectの配列になってしまいますけど。

上のコードでは使いませんでしたが@FieldResultというアノテーションがあります。これはカラムの別名をエンティティにマッピングするときに使うようです。@EntityResultに入れ子させて使います。

スカラー値の取得を定義するために@ColumnResultというアノテーションがあるのですが、これはまだ実装されていないようです。使ってみたら「org.hibernate.cfg.NotYetImplementedException: Scalar returns are not supported」といわれました。


これでChapter 3 終了です。やっと終わった。