64
79

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaEE使い方メモ(JPA その4 - クライテリアAPI)

Posted at

環境構築
JPA の基本的な話
マッピングの話
JPQL の話

コード

#クライテリアAPI とは
JPQL をプログラムで動的に生成するための API。

文字列連結で JPQL を生成する場合、構文ミスをコンパイルレベルで検知することができない。
クライテリア API を使えば Java プログラムで JPQL の構築ができるので、 JPQL の構文ミスを回避できる。

#基本
##実装
エンティティモデル

jpa.JPG

実装(エンティティ)

Kisume.java
package sample.javaee.jpa.entity.criteria;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Kisume {
    @Id
    private Long id;

    @Override
    public String toString() {
        return "Kisume{" + "id=" + id + '}';
    }
}

データ

jpa.JPG

実装

CriteriaEjb.java
package sample.javaee.jpa.ejb;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import sample.javaee.jpa.entity.criteria.Kisume;

@Stateless
public class CriteriaEjb {

    @PersistenceContext(unitName = "SampleUnit")
    private EntityManager em;
    
    public void hello() {
        CriteriaBuilder builder = this.em.getCriteriaBuilder();
        CriteriaQuery<Kisume> query = builder.createQuery(Kisume.class);
        
        Root<Kisume> root = query.from(Kisume.class);
        query.select(root)
             .where(builder.lessThan(root.get("id"), 3L));
        
        TypedQuery<Kisume> q = this.em.createQuery(query);
        System.out.println(q.getResultList());
    }
}

動作確認

GlassFishコンソール出力
情報:   [Kisume{id=1}, Kisume{id=2}]

##説明

CriteriaEjb.java(一部)
CriteriaBuilder builder = this.em.getCriteriaBuilder();
CriteriaQuery<Kisume> query = builder.createQuery(Kisume.class);

Root<Kisume> root = query.from(Kisume.class);
query.select(root)
     .where(builder.lessThan(root.get("id"), 3L));

TypedQuery<Kisume> q = this.em.createQuery(query);
System.out.println(q.getResultList());

この実装は、次の JPQL と同じになる。

SELECT k
  FROM Kisume k
 WHERE k.id < 3

クライテリア API を使うときのおおまかな手順は次のようになる。

  • EntityManager#getCriteriaBuilder()CriteriaBuilder のインスタンスを取得する。
  • CriteriaBuilder#createQuery()CriteriaQuery のインスタンスを取得する。
    • このとき、 createQuery() の引数には、最終的に取得する検索結果の型(Class)を渡す。
  • CriteriaQuery#from() メソッドで、検索対象のエンティティを指定する。
  • CriteriaQuery#select() メソッドで、取得する項目(エンティティ)を指定する。
  • CriteriaQuery#where() メソッドで、細かい検索条件を指定する。
  • クエリの構築が完了したら、 EntityManager#createQuery(CriteriaQuery)Query のインスタンスを取得する。
  • 後は、名前付きクエリを使用したときと同じように Query#getResultList() などで結果を取得する。

#メタモデル(Metamodel)
プロパティ名などを文字列で指定していると、エンティティのプロパティ名とかを変更したときに修正漏れが発生するおそれがある。

JPA 2.0 で追加されたメタモデルの仕組みを利用すれば、そのへんの問題を回避することができるようになる。

##基本
###実装

フォルダ構成
|-build.gradle
`-src/main/
   |-resources/META-INF/
   |  `-persistence.xml
   `-java/sample/jpa/
      |-entity/
      |  `-TestTable.java
      `-web/
         |-HelloEjb.java
         `-HelloServlet.java
build.gradle
apply plugin: 'war'

repositories {
    mavenCentral()
}

dependencies {
    providedCompile 'javax:javaee-api:7.0'
    providedCompile 'org.eclipse.persistence:org.eclipse.persistence.jpa.modelgen.processor:2.5.0'
}

sourceCompatibility = '1.8'
targetCompatibility = '1.8'

compileJava {
    def path = new File(project.projectDir, 'src/main/resources/META-INF/persistence.xml').absolutePath
    options.compilerArgs.addAll '-Aeclipselink.persistencexml=' + path
}

war.baseName = 'jpa-mdetamodel'
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
  xmlns="http://xmlns.jcp.org/xml/ns/persistence"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
  http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">

  <persistence-unit name="SampleUnit">
    <jta-data-source>jdbc/Local_MySQL_test</jta-data-source>
  </persistence-unit>
</persistence>
TestTable.java
package sample.jpa.entity;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="test_table")
public class TestTable {
    @Id
    private Long id;
    private String value;
    
    @Override
    public String toString() {
        return "TestTable [id=" + id + ", value=" + value + "]";
    }
}
HelloEjb.java
package sample.jpa.web;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import sample.jpa.entity.TestTable;
import sample.jpa.entity.TestTable_;

@Stateless
public class HelloEjb {
    
    @PersistenceContext(unitName="SampleUnit")
    private EntityManager em;
    
    public void metamodel() {
        CriteriaBuilder builder = this.em.getCriteriaBuilder();
        CriteriaQuery<TestTable> query = builder.createQuery(TestTable.class);
        
        Root<TestTable> root = query.from(TestTable.class);
        query.select(root);
        
        Predicate idEqual2 = builder.equal(root.get(TestTable_.id), 2L);
        query.where(idEqual2);
        
        this.em.createQuery(query).getResultList().forEach(System.out::println);
    }
}
HelloServlet.java
package sample.jpa.web;

import java.io.IOException;

import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    
    @EJB
    private HelloEjb ejb;
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.ejb.hello();
    }
}

データベース

jpa.JPG

###動作確認

ビルド
> gradle war

build/libs/jpa-metamodel.war が出力されるので、 GlassFish にデプロイする。

http://localhost:8080/jpa-metamodel/metamodel に GET リクエストを送る。

GlassFishコンソール出力
[2015-05-26T22:42:42.861+0900] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=32 _ThreadName=Thread-8] [timeMillis: 1432647762861] [levelValue: 800] [[TestTable [id=2, value=buzz]]]

###説明
####メタモデルを使った実装

HelloEjb.java
import sample.jpa.entity.TestTable;
import sample.jpa.entity.TestTable_;

...

    public void metamodel() {
        CriteriaBuilder builder = this.em.getCriteriaBuilder();
        CriteriaQuery<TestTable> query = builder.createQuery(TestTable.class);
        
        Root<TestTable> root = query.from(TestTable.class);
        query.select(root);
        
        Predicate idEqual2 = builder.equal(root.get(TestTable_.id), 2L);
        query.where(idEqual2);
        
        this.em.createQuery(query).getResultList().forEach(System.out::println);
    }
  • メタモデルの生成を有効にすると、エンティティクラスの名前の末尾にアンダーバー (_) が付いたクラスが自動生成される。
    • 上記例の場合、 TestTable_ クラスが TestTable エンティティのメタモデルになる。
  • メタモデルにはオリジナルのエンティティが持つものと同じ名前のフィールドが定義されている。
  • このフィールドを利用することで、検索条件の構築などを静的に、かつ型安全に実装することができるようになる。

ちなみに、自動生成された TestTable_ クラスは以下のようになっている。

TestTable_.java
package sample.jpa.entity;

import javax.annotation.Generated;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;

@Generated(value="EclipseLink-2.5.0.v20130507-rNA", date="2015-05-26T22:35:16")
@StaticMetamodel(TestTable.class)
public class TestTable_ { 

    public static volatile SingularAttribute<TestTable, Long> id;
    public static volatile SingularAttribute<TestTable, String> value;

}

####コンパイル時にメタモデルを生成させるようにする

build.gradle
repositories {
    mavenCentral()
}

dependencies {
    providedCompile 'javax:javaee-api:7.0'
    providedCompile 'org.eclipse.persistence:org.eclipse.persistence.jpa.modelgen.processor:2.5.0'
}

compileJava {
    def path = new File(project.projectDir, 'src/main/resources/META-INF/persistence.xml').absolutePath
    options.compilerArgs.addAll '-Aeclipselink.persistencexml=' + path
}
  • メタモデルは、注釈処理で自動生成させる。
  • GlassFish 4.1 の場合、 JPA の実装は EclipseLink 2.5.0 なので、 EclipseLink が提供しているメタモデル生成用の jar を依存関係に追加する(org.eclipse.persistence.jpa.modelgen.processor)。
  • EclipseLink でメタモデルの生成を有効にするには、コンパイル時に -Aeclipselink.persistencexml オプションに persistence.xml へのパスを渡す。

##Eclipse で開発する場合
Eclipse などの IDE で開発する場合、注釈処理がバックグランドで自動で走るようにしておくことで、特に意識することなくメタモデルを利用できるようになる。

「プロジェクト・ファセット」を有効にして、 [JPA] のチェックを入れる。

jpa.JPG

プロジェクトのプロパティに [JPA] が追加されるので、それを選択。
「正規メタモデル(JPA 2.0)」の「ソース・フォルダー」でエンティティを含むソースフォルダを指定する。

jpa.JPG

これで、エンティティのソースと同じパッケージにメタモデルのソースが出力されるようになる。

jpa.JPG

#検索条件を構築する
##基本
エンティティモデル

jpa.JPG

実装(エンティティ)

KurodaniYmame.java
package sample.javaee.jpa.entity.criteria;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="kurodani_yamame")
public class KurodaniYmame {
    @Id
    private Long id;
    private String string;
    private int number;

    @Override
    public String toString() {
        return "KurodaniYmame{" + "id=" + id + ", string=" + string + ", number=" + number + '}';
    }
}

データ

jpa.JPG

実装

CriteriaEjb.java
    CriteriaBuilder builder = this.em.getCriteriaBuilder();
    CriteriaQuery<KurodaniYmame> query = builder.createQuery(KurodaniYmame.class);
    
    Root<KurodaniYmame> root = query.from(KurodaniYmame.class);
    
    query.select(root)
         .where(
             builder.greaterThanOrEqualTo(root.get(KurodaniYmame_.number), 200),
             builder.like(root.get(KurodaniYmame_.string), "%o%")
         );
    
    TypedQuery<KurodaniYmame> q = this.em.createQuery(query);
    System.out.println(q.getResultList());

実行結果

GlassFishコンソール出力
情報:   [KurodaniYmame{id=2, string=two, number=200}, KurodaniYmame{id=4, string=four, number=400}]

これは、次の JPQL と同じになる。

SELECT k
  FROM KurodaniYamame k
 WHERE 200 <= k.number
   AND k.string LIKE '%o%'
  • CriteriaQuery#where() で、検索条件を設定する。
  • 引数に渡す条件式(Expression)は、 CriteriaBuilder に定義されているメソッドを使って作成する。
    • JPQL に用意されている条件式と対応しているので、メソッド名からだいたい用途を想像できる。 CriteriaBuilder の Javadoc
  • CriteriaBuilder に定義された条件式を作成するためのメソッドは、基本的に次の引数を受け取る。
    • 第一引数に比較したい項目。
    • 第二引数に比較値。
  • 比較したい項目は、 Root#get() メソッドで取得する。
  • where() メソッドは条件式を可変長引数で受け取ることができる。
    • 複数の条件式を受け取った場合、それらは AND で結合される。

##OR 条件を使用する
エンティティモデル

jpa.JPG

実装(エンティティ)

MizuhashiParsee.java
package sample.javaee.jpa.entity.criteria;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="mizuhashi_parsee")
public class MizuhashiParsee {
    @Id
    private Long id;
    private String value;

    @Override
    public String toString() {
        return "MizuhashiParsee{" + "id=" + id + ", value=" + value + '}';
    }
}

データ

jpa.JPG

実装

CriteriaEjb.java
    CriteriaBuilder builder = this.em.getCriteriaBuilder();
    CriteriaQuery<MizuhashiParsee> query = builder.createQuery(MizuhashiParsee.class);
    
    Root<MizuhashiParsee> root = query.from(MizuhashiParsee.class);
    
    query.select(root)
         .where(
             builder.or(
                 builder.equal(root.get(MizuhashiParsee_.id), 1L),
                 builder.equal(root.get(MizuhashiParsee_.id), 3L)
             )
         );
    
    TypedQuery<MizuhashiParsee> q = this.em.createQuery(query);
    System.out.println(q.getResultList());

実行結果

GlassFishコンソール出力
情報:   [MizuhashiParsee{id=1, value=hoge}, MizuhashiParsee{id=3, value=piyo}]

JPQL は次と同じになる。

SELECT m
  FROM MizuhashiParsee m
 WHERE m.id = 1
    OR m.id = 3
  • CriteriaBuilder#or() メソッドを使うことで、 OR 条件を定義できる。
  • 引数は可変長引数になっているので、複数の条件を連結できる。
  • CriteriaBuilder#and() メソッドもあるので、 AND 条件を定義することもできる。

##IN や IS NULL
INIS NULLCriteriaBuilder から作るのではなく、 Root#get() で取得した Path から作る。

Root<MizuhashiParsee> root = query.from(MizuhashiParsee.class);

query.select(root)
     .where(root.get(MizuhashiParsee_.id).isNotNull())

#パラメータを定義する
##実装
エンティティモデル

jpa.JPG

実装(エンティティ)

HoshigumaYugi.java
package sample.javaee.jpa.entity.criteria;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="hoshiguma_yugi")
public class HoshigumaYugi {
    @Id
    private Long id;
    private String value;

    @Override
    public String toString() {
        return "HoshigumaYugi{" + "id=" + id + ", value=" + value + '}';
    }
}

データ

jpa.JPG

実装

CriteriaEjb.java
    CriteriaBuilder builder = this.em.getCriteriaBuilder();
    CriteriaQuery<HoshigumaYugi> query = builder.createQuery(HoshigumaYugi.class);
    
    Root<HoshigumaYugi> root = query.from(HoshigumaYugi.class);
    
    ParameterExpression<Long> id = builder.parameter(Long.class);
    ParameterExpression<String> value = builder.parameter(String.class);
    
    query.select(root)
         .where(
             builder.or(
                 builder.equal(root.get(HoshigumaYugi_.id), id),
                 builder.equal(root.get(HoshigumaYugi_.value), value)
             )
         );
    
    TypedQuery<HoshigumaYugi> q = this.em.createQuery(query);
    q.setParameter(id, 2L);
    q.setParameter(value, "piyo");
    
    System.out.println(q.getResultList());

実行結果

GlassFishコンソール出力
情報:   [HoshigumaYugi{id=2, value=fuga}, HoshigumaYugi{id=3, value=piyo}]

##説明

パラメータの定義
ParameterExpression<Long> id = builder.parameter(Long.class);
ParameterExpression<String> value = builder.parameter(String.class);

CriteriaBuilder#parameter(Class) で、 ParameterExpression のインスタンスを生成する。

パラメータをクエリに埋め込む
query.select(root)
     .where(
         builder.or(
             builder.equal(root.get(HoshigumaYugi_.id), id),
             builder.equal(root.get(HoshigumaYugi_.value), value)
         )
     );

次に、 CriteriaBuilder#equal() などで条件式を生成するときに、先ほど作成した ParameterExpression を比較値として設定する。

Queryにパラメータの値をセットする
TypedQuery<HoshigumaYugi> q = this.em.createQuery(query);
q.setParameter(id, 2L);
q.setParameter(value, "piyo");

最後に、 Query#<T>setParameter(Parameter<T>, T) でクエリにパラメータの値をセットする。

第一引数の Parameter<T> には、最初に作成した ParameterExpression<T> を渡すことができる(ParameterExpressionParameter を継承している)。

#エンティティの関連を辿る
##実装
エンティティモデル

jpa.JPG

実装(エンティティ)

KomeijiSatori.java
package sample.javaee.jpa.entity.criteria;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Table;

@Entity
@Table(name="komeiji_satori")
public class KomeijiSatori {
    @Id
    private Long id;
    @JoinColumn(name="komeiji_koishi_id")
    private KomeijiKoishi komeijiKoishi;

    @Override
    public String toString() {
        return "KomeijiSatori{" + "id=" + id + ", komeijiKoishi=" + komeijiKoishi + '}';
    }
}
KomeijiKoishi.java
package sample.javaee.jpa.entity.criteria;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="komeiji_koishi")
public class KomeijiKoishi {
    @Id
    private Long id;
    private String value;

    @Override
    public String toString() {
        return "KomeijiKoishi{" + "id=" + id + ", value=" + value + '}';
    }
}

データ

jpa.JPG

jpa.JPG

実装

CriteriaEjb.java
    CriteriaBuilder builder = this.em.getCriteriaBuilder();
    CriteriaQuery<KomeijiSatori> query = builder.createQuery(KomeijiSatori.class);
    
    Root<KomeijiSatori> root = query.from(KomeijiSatori.class);
    
    query.select(root)
         .where(
             builder.equal(
                 root.get(KomeijiSatori_.komeijiKoishi)
                     .get(KomeijiKoishi_.value),
                 "fuga"
             )
         );
    
    TypedQuery<KomeijiSatori> q = this.em.createQuery(query);
    System.out.println(q.getResultList());

実行結果

GlassFishコンソール出力
情報:   [KomeijiSatori{id=2, komeijiKoishi=KomeijiKoishi{id=2, value=fuga}}]

##説明
上記実装は、下記 JPQL と同じになる。

SELECT k
  FROM KomeijiSaori k
 WHERE k.komeijiKoishi.value = 'fuga'
query.select(root)
     .where(
         builder.equal(
             root.get(KomeijiSatori_.komeijiKoishi)
                 .get(KomeijiKoishi_.value),
             "fuga"
         )
     );
  • Root#get() の戻り値に対して、さらに get() メソッドを呼び出すことで、エンティティの関連を辿ることができる。

#任意のプロパティを SELECT する
エンティティモデル

jpa.JPG

実装(エンティティ)

KaenbyoRin.java
package sample.javaee.jpa.entity.criteria;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="kaenbyo_rin")
public class KaenbyoRin {
    @Id
    private Long id;
    private int number;
    private String string;

    @Override
    public String toString() {
        return "KaenbyoRin{" + "id=" + id + ", number=" + number + ", string=" + string + '}';
    }
}

データ

jpa.JPG

##基本
実装

CriteriaEjb.java
    CriteriaBuilder builder = this.em.getCriteriaBuilder();
    CriteriaQuery<String> query = builder.createQuery(String.class);
    
    Root<KaenbyoRin> root = query.from(KaenbyoRin.class);
    query.select(root.get(KaenbyoRin_.string));
    
    TypedQuery<String> q = this.em.createQuery(query);
    System.out.println(q.getResultList());

実行結果

GlassFishコンソール出力
情報:   [hoge, fuga, piyo]

JPQL に置き換えると以下になる。

SELECT k.string
  FROM KaenbyoRin k
  • CriteriaQuery.select() メソッドの引数に、 Root#get() で取得したプロパティの Selection を渡すことで、任意のプロパティだけを取得できる。

##Tuple を使って複数のプロパティを取得する

CriteriaEjb.java
import javax.persistence.criteria.Path;
import javax.persistence.Tuple;

...

    CriteriaBuilder builder = this.em.getCriteriaBuilder();
    CriteriaQuery<Tuple> query = builder.createTupleQuery();
    
    Root<KaenbyoRin> root = query.from(KaenbyoRin.class);
    Path<Integer> number = root.get(KaenbyoRin_.number)
    Path<String> string = root.get(KaenbyoRin_.string);
    
    query.select(builder.tuple(number, string));
    
    TypedQuery<Tuple> q = this.em.createQuery(query);
    
    q.getResultList()
     .stream()
     .map(tuple -> "{" + tuple.get(number) + ", " + tuple.get(string) + "}")
     .forEach(System.out::println);
GlassFishコンソール出力
情報:   {111, hoge}
情報:   {222, fuga}
情報:   {333, piyo}

JPQL に置き換えると以下になる。

SELECT k.number
      ,k.string
  FROM KaenbyoRin k
  • 検索結果を Tuple 型で受け取るようにすることで、複数のプロパティを取得できるようになる。
    • Tuple とは、複数の値を組み合わせたものを表している(2つ以上も可)。
    • CriteriaQuery<Tuple> を取得するための専用のメソッド(createTupleQuery())が CriteriaBuilder に用意されている(createQuery(Tuple.class) と意味は同じ)。
  • CriteriaQuery#select() メソッドの引数には、 CriteriaBuilder#tuple() で作成した CompoundSelection インスタンスを渡す。
  • 検索結果の Tuple から値を取得するときは、 Root#get() メソッドで予め取得しておいた Path オブジェクトを使うと安全。

#関数の実行結果を SELECT する
エンティティモデル

jpa.JPG

実装(エンティティ)

ReiujiUtsubo.java
package sample.javaee.jpa.entity.criteria;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="reiuji_utsubo")
public class ReiujiUtsubo {
    @Id
    private Long id;
    private int number;

    @Override
    public String toString() {
        return "ReiujiUtsubo{" + "id=" + id + ", number=" + number + '}';
    }
}

データ

jpa.JPG

実装

CriteriaEjb.java
    CriteriaBuilder builder = this.em.getCriteriaBuilder();
    CriteriaQuery<Integer> query = builder.createQuery(Integer.class);
    
    Root<ReiujiUtsubo> root = query.from(ReiujiUtsubo.class);
    query.select(builder.sum(root.get(ReiujiUtsubo_.number)));
    
    TypedQuery<Integer> q = this.em.createQuery(query);
    System.out.println(q.getSingleResult());

実行結果

GlassFishコンソール出力
情報:   600
  • CriteriaBuilder で作成した関数呼び出しの式を CriteriaQuery.select() メソッドに渡す。

#ORDER BY
エンティティモデル

jpa.JPG

実装(エンティティ)

Nazrin.java
package sample.javaee.jpa.entity.criteria;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Nazrin {
    @Id
    private Long id;
    private int number;

    @Override
    public String toString() {
        return "Nazrin{" + "id=" + id + ", number=" + number + '}';
    }
}

データ

jpa.JPG

実装

CriteriaEjb.java
    CriteriaBuilder builder = this.em.getCriteriaBuilder();
    CriteriaQuery<Nazrin> query = builder.createQuery(Nazrin.class);
    
    Root<Nazrin> root = query.from(Nazrin.class);
    query.select(root)
         .orderBy(builder.desc(root.get(Nazrin_.number)));
    
    TypedQuery<Nazrin> q = this.em.createQuery(query);
    q.getResultList().forEach(System.out::println);

実行結果

GlassFishコンソール出力
情報:   Nazrin{id=2, number=300}
情報:   Nazrin{id=1, number=200}
情報:   Nazrin{id=3, number=100}

JPQL にすると以下のようになる。

  SELECT n
    FROM Nazrin n
ORDER BY n.number DESC
  • CriteriaBuilder#asc(Expression) または CriteriaBuilder#desc(Expression) で、 Order オブジェクトを生成する。
  • 生成した Order オブジェクトを、 CriteriaQuery#orderBy(Order...) に渡す。

#GROUP BY と HAVING
##GROUP BY
エンティティモデル

jpa.JPG

実装(エンティティ)

TataraKogasa.java
package sample.javaee.jpa.entity.criteria;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="tatara_kogasa")
public class TataraKogasa {
    @Id
    private Long id;
    private String string;
    private int number;

    @Override
    public String toString() {
        return "TataraKogasa{" + "id=" + id + ", string=" + string + ", number=" + number + '}';
    }
}

データ

jpa.JPG

実装

CriteriaEjb.java
import javax.persistence.criteria.Expression;

...

    CriteriaBuilder builder = this.em.getCriteriaBuilder();
    CriteriaQuery<Tuple> query = builder.createQuery(Tuple.class);
    
    Root<TataraKogasa> root = query.from(TataraKogasa.class);
    Path<String> string = root.get(TataraKogasa_.string);
    Expression<Integer> sum = builder.sum(root.get(TataraKogasa_.number));
    
    query.select(builder.tuple(string, sum))
         .groupBy(string);
    
    TypedQuery<Tuple> q = this.em.createQuery(query);
    
    q.getResultList()
     .stream()
     .map(tuple -> tuple.get(string) + " -> " + tuple.get(sum))
     .forEach(System.out::println);

実行結果

GlassFishコンソール出力
情報:   fuga -> 150
情報:   hoge -> 80
情報:   piyo -> 130

JPQL にすると、以下のようになる。

  SELECT t.string
        ,SUM(t.number)
    FROM TataraKogasa t
GROUP BY t.string
  • CriteriaQuery#groupBy(Expression<?>...) で、集約条件を設定する。
  • 検索結果は Tuple で受け取るようにすれば、奇麗に値を取得できる。

##HAVING
実装

CriteriaEjb.java
    CriteriaBuilder builder = this.em.getCriteriaBuilder();
    CriteriaQuery<Tuple> query = builder.createQuery(Tuple.class);
    
    Root<TataraKogasa> root = query.from(TataraKogasa.class);
    Path<String> string = root.get(TataraKogasa_.string);
    Expression<Integer> sum = builder.sum(root.get(TataraKogasa_.number));
    
    query.select(builder.tuple(string, sum))
-        .groupBy(string);
+        .groupBy(string)
+        .having(builder.greaterThan(sum, 100));
    
    TypedQuery<Tuple> q = this.em.createQuery(query);
    
    q.getResultList()
     .stream()
     .map(tuple -> tuple.get(string) + " -> " + tuple.get(sum))
     .forEach(System.out::println);

実行結果

GlassFishコンソール出力
情報:   fuga -> 150
情報:   piyo -> 130

JPQL にすると以下。

  SELECT t.string
        ,SUM(t.number)
    FROM TataraKogasa t
GROUP BY t.string
  HAVING 100 < SUM(t.number)
  • CriteriaQuery#having(Expression<Boolean>) で、 HAVING 句を指定できる。

#JOIN
エンティティモデル

jpa.JPG

実装(エンティティ)

KumoiIchirin.java
package sample.javaee.jpa.entity.criteria;

import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name="kumoi_ichirin")
public class KumoiIchirin {
    @Id
    private Long id;
    @OneToMany(fetch = FetchType.EAGER)
    @JoinColumn(name="ichirin_id")
    private List<KumoiUnzan> unzanList;

    @Override
    public String toString() {
        return "KumoiIchirin{" + "id=" + id + ", unzanList=" + unzanList + '}';
    }
}
KumoiUnzan.java
package sample.javaee.jpa.entity.criteria;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="kumoi_unzan")
public class KumoiUnzan {
    @Id
    private Long id;
    private String value;

    @Override
    public String toString() {
        return "KumoiUnzan{" + "id=" + id + ", value=" + value + '}';
    }
}

データ

jpa.JPG

jpa.JPG

実装

CriteriaEjb.java
    CriteriaBuilder builder = this.em.getCriteriaBuilder();
    CriteriaQuery<KumoiIchirin> query = builder.createQuery(KumoiIchirin.class);
    
    Root<KumoiIchirin> root = query.from(KumoiIchirin.class);
    Join<KumoiIchirin, KumoiUnzan> join = root.join(KumoiIchirin_.unzanList);

    query.select(root)
         .distinct(true)
         .where(builder.like(join.get(KumoiUnzan_.value), "%e%"));
    
    TypedQuery<KumoiIchirin> q = this.em.createQuery(query);
    
    q.getResultList()
     .forEach(System.out::println);

実行結果

GlassFishコンソール出力
情報:   KumoiIchirin{id=1, unzanList=[KumoiUnzan{id=1, value=one}, KumoiUnzan{id=2, value=two}, KumoiUnzan{id=3, value=three}]}
情報:   KumoiIchirin{id=2, unzanList=[KumoiUnzan{id=4, value=four}, KumoiUnzan{id=5, value=five}]}

JPQL にすると、以下のようになる。

  SELECT 
DISTINCT ki
    FROM KumoiIchirin ki
    JOIN ki.unzanList ul
   WHERE ul.value LIKE '%e%'
  • Root#join() メソッドで、 JPQL の JOIN を実行できる。

#FETCH JOIN
実装

CriteriaEjb.java
    CriteriaBuilder builder = this.em.getCriteriaBuilder();
    CriteriaQuery<KumoiIchirin> query = builder.createQuery(KumoiIchirin.class);
    
    Root<KumoiIchirin> root = query.from(KumoiIchirin.class);
    root.fetch(KumoiIchirin_.unzanList);

    query.select(root)
         .distinct(true);
    
    TypedQuery<KumoiIchirin> q = this.em.createQuery(query);
    
    q.getResultList()
     .forEach(System.out::println);

実行結果

GlassFishコンソール出力
普通:   SELECT DISTINCT t1.ID, t0.ID, t0.VALUE FROM kumoi_unzan t0, kumoi_ichirin t1 WHERE (t0.ichirin_id = t1.ID)
情報:   KumoiIchirin{id=1, unzanList=[KumoiUnzan{id=1, value=one}, KumoiUnzan{id=2, value=two}, KumoiUnzan{id=3, value=three}]}
情報:   KumoiIchirin{id=2, unzanList=[KumoiUnzan{id=4, value=four}, KumoiUnzan{id=5, value=five}]}
情報:   KumoiIchirin{id=3, unzanList=[KumoiUnzan{id=6, value=six}]}
  • リスト項目(unzanList)の取得が、1 つの SQL で実行されている。
  • Root#fetch() で、引数で指定したコレクション型のプロパティを FETCH JOIN させることができる。

ちなみに、 FETCH JOIN を使わない場合は以下のようになる。

GlassFishコンソール出力
普通:   SELECT DISTINCT ID FROM kumoi_ichirin
普通:   SELECT ID, VALUE FROM kumoi_unzan WHERE (ichirin_id = ?)
	bind => [1]
普通:   SELECT ID, VALUE FROM kumoi_unzan WHERE (ichirin_id = ?)
	bind => [2]
普通:   SELECT ID, VALUE FROM kumoi_unzan WHERE (ichirin_id = ?)
	bind => [3]
情報:   KumoiIchirin{id=1, unzanList=[KumoiUnzan{id=1, value=one}, KumoiUnzan{id=2, value=two}, KumoiUnzan{id=3, value=three}]}
情報:   KumoiIchirin{id=2, unzanList=[KumoiUnzan{id=4, value=four}, KumoiUnzan{id=5, value=five}]}
情報:   KumoiIchirin{id=3, unzanList=[KumoiUnzan{id=6, value=six}]}

#サブクエリ
エンティティモデル

jpa.JPG

実装(エンティティ)

package sample.javaee.jpa.entity.criteria;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="murasa_minamitsu")
public class MurasaMinamitsu {
    @Id
    private Long id;

    @Override
    public String toString() {
        return "MurasaMinamitsu{" + "id=" + id + '}';
    }
}
ToramaruShou.java
package sample.javaee.jpa.entity.criteria;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="toramaru_shou")
public class ToramaruShou {
    @Id
    private Long id;

    @Override
    public String toString() {
        return "ToramaruShou{" + "id=" + id + '}';
    }
}

データ

jpa.JPG

jpa.JPG

実装

CriteriaEjb.java
    CriteriaBuilder builder = this.em.getCriteriaBuilder();
    CriteriaQuery<MurasaMinamitsu> query = builder.createQuery(MurasaMinamitsu.class);
    
    Subquery<Long> subquery = query.subquery(Long.class);
    Root<ToramaruShou> toramaru = subquery.from(ToramaruShou.class);
    subquery.select(toramaru.get(ToramaruShou_.id));
    
    Root<MurasaMinamitsu> murasa = query.from(MurasaMinamitsu.class);
    query.select(murasa)
         .where(murasa.get(MurasaMinamitsu_.id).in(subquery));
    
    TypedQuery<MurasaMinamitsu> q = this.em.createQuery(query);
    
    q.getResultList()
     .forEach(System.out::println);

実行結果

GlassFishコンソール出力
情報:   MurasaMinamitsu{id=1}
情報:   MurasaMinamitsu{id=3}

JPQL にすると、以下のようになる。

SELECT m
  FROM MurasaMinamitsu m
 WHERE m.id IN (
       SELECT t.id
         FROM ToramaruShou t
     )
  • CriteriaQuery#subquery(Class) で、サブクエリを定義するための Subquery のインスタンスを取得できる。
    • 引数の Class は、最終的にサブクエリが返す値の型を指定する。
  • サブクエリの構築は、 CriteriaQuery の場合と同じ要領で行う。
  • 最後に、でき上がった Subquery インスタンスをメインの CriteriaQuery の条件式に渡して利用する。

#UPDATE, DELETE
##UPDATE
エンティティモデル

jpa.JPG

実装(エンティティ)

HijiriByakuren.java
package sample.javaee.jpa.entity.criteria;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="hijiri_byakuren")
public class HijiriByakuren {
    @Id
    private Long id;
    private String value;
}

データ

jpa.JPG

実装

CriteriaEjb.java
    CriteriaBuilder builder = this.em.getCriteriaBuilder();
    CriteriaUpdate<HijiriByakuren> update = builder.createCriteriaUpdate(HijiriByakuren.class);
    
    Root<HijiriByakuren> root = update.from(HijiriByakuren.class);
    
    update.set(root.get(HijiriByakuren_.value), "update!!")
          .where(root.get(HijiriByakuren_.id).in(1, 3));
    
    Query q = this.em.createQuery(update);
    q.executeUpdate();

実行結果

jpa.JPG

  • UPDATE 文を発行したい場合は、まず CriteriaBuilder#createCriteriaUpdate(Class)CriteriaUpdate のインスタンスを取得する。
    • 引数の Class には、更新対象のエンティティの Class オブジェクトを渡す。
  • CriteriaUpdate#set() メソッドで、更新したい項目と、更新後の値を設定する。
  • CriteriaUpdate の準備が整ったら、 EntityManager#createQuery(CriteriaUpdate) を使って Query のインスタンスを生成する。
  • 取得した QueryexecuteUpdate() で更新を実行する。
  • 条件の設定は CriteriaQuery を使った場合と同様。

##DELETE
エンティティモデル

jpa.JPG

実装(エンティティ)

HojuNue.java
package sample.javaee.jpa.entity.criteria;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="hojo_nue")
public class HojuNue {
    @Id
    private Long id;
}

データ

jpa.JPG

実装

CriteriaEjb.java
    CriteriaBuilder builder = this.em.getCriteriaBuilder();
    CriteriaDelete<HojuNue> delete = builder.createCriteriaDelete(HojuNue.class);
    
    Root<HojuNue> root = delete.from(HojuNue.class);
    
    delete.where(builder.equal(root.get(HojuNue_.id), 2L));
    
    Query q = this.em.createQuery(delete);
    q.executeUpdate();

実行結果

jpa.JPG

  • DELETE 文を発行する場合は、 EntityManager#createQueryDelete(Class)CriteriaDelete インスタンスを取得する。
  • あとは、 UPDATE の場合と同じ。

#参考

64
79
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
64
79

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?