[Java] 48. JPAでQueryを使う方法(JPQLクエリを作成する方法)


Study / Java    作成日付 : 2019/10/13 22:55:52   修正日付 : 2021/06/17 16:20:51

こんにちは。明月です。


この投稿はJPAでQueryを使う方法(JPQLクエリを作成する方法)に関する説明です。


JPAで基本的にデータを取得する方法はEntityで宣言されているNameQueryを通ってデータを取得します。そしてリファレンスのJoinになったデータを取得するために取得するデータでListの関数get、size関数を通ってデータを取得します。


import java.util.List;
import java.util.Optional;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import model.User;
import model.Info;
 
public class Main {
  // ラムダ式のインタフェース
  interface Expression {
    void run(EntityManager em);
  }
  // PersistenceからEntityManagerを呼び出してから実行と終了する関数
  private static void transaction(Expression lambda) {
    // FactoryManagerを生成する。"JpaExample"はpersistence.xmlに書いている名だ。
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("JpaExample");
    // Managerを生成する。
    EntityManager em = emf.createEntityManager();
    try {
      // transactionを取得。
      EntityTransaction transaction = em.getTransaction();
      try {
        // transaction実行
        transaction.begin();
        // ラムダ式を実行する。
        lambda.run(em);
        // transactionをコミットする。
        transaction.commit();
      } catch (Throwable e) {
        // エラーが発生すればrollbackする。
        if (transaction.isActive()) {
          transaction.rollback();
        }
        // エラー出力
        e.printStackTrace();
      }
    } finally {
      // 各FactoryManagerとManagerを閉める。
      em.close();
      emf.close();
    }
  }
  // 実行関数
  @SuppressWarnings("unchecked")
  public static void main(String... args) {
    transaction((em) -> {
      // userクラスのNamedQueryのUser.findAllのクエリ通りデータを取得する。
      List<User> users = em.createNamedQuery("User.findAll").getResultList();
      // nowonbunデータ取得
      Optional<User> op = users.stream().filter(x -> "nowonbun".equals(x.getId())).findFirst();
      // データがあれば
      if (op.isPresent()) {
        // データ取得
        User user = op.get();
        // コンソール出力
        System.out.println(user.getId());
        System.out.println(user.getName());
      }
    });
  }
}


でも、上の方法の問題はデータをすべて取得してコード上のfilterや分岐でデータを取得します。

つまり、データが多いとシステムが遅くなります。


そうならクエリでデータベースからデータを取得する段階でデータを分類して取得した後、処理する方が良いです。

JPAはクエリでデータを取得する方法が二つがあります。一つはJPAで使うJPQLクエリで取得する方法とデータベースで使うSQLクエリで取得する方法です。


まず、データベースで使うSQLクエリを利用する例です。

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;

public class Main {
  // ラムダ式のインタフェース
  interface Expression {
    void run(EntityManager em);
  }
  // PersistenceからEntityManagerを呼び出してから実行と終了する関数
  private static void transaction(Expression lambda) {
    // FactoryManagerを生成する。"JpaExample"はpersistence.xmlに書いている名だ。
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("JpaExample");
    // Managerを生成する。
    EntityManager em = emf.createEntityManager();
    try {
      // transactionを取得。
      EntityTransaction transaction = em.getTransaction();
      try {
        // transaction実行
        transaction.begin();
        // ラムダ式を実行する。
        lambda.run(em);
        // transactionをコミットする。
        transaction.commit();
      } catch (Throwable e) {
        // エラーが発生すればrollbackする。
        if (transaction.isActive()) {
          transaction.rollback();
        }
        // エラー出力
        e.printStackTrace();
      }
    } finally {
      // 各FactoryManagerとManagerを閉める。
      em.close();
      emf.close();
    }
  }
  // 実行関数
  public static void main(String... args) {
    transaction((em) -> {
      // SQLクエリ入力
      Query qy = em.createNativeQuery("select * from user u where u.id = ?");
      // パラメータ入力
      qy.setParameter(1, "nowonbun");
      // データ取得
      Object[] user = (Object[])qy.getSingleResult();
      // コンソール出力
      System.out.println(user[0]);
      System.out.println(user[1]);
    });
  }
}


ManagerクラスのcreateNativeQuery関数を利用してデータベースのSQLクエリでデータを取得することができます。

問題はJPAで生成したEntityクラスのマッピングでできないことです。


JPAを利用して取得したデータはデータを修正すると、その値をデータベースにupdateするし、削除するし、様々な作業ができます。でも、クラスのマッピングができなければJPAを使う理由がないです。

そのため、JPQLクエリでデータを取得したデータではなければJPA機能を完全に使えないです。実はJPQLクエリでもSQLと全然違う種類のクエリではありません。ほぼ90%は似てます。

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;
import model.User;

public class Main {
  // ラムダ式のインタフェース
  interface Expression {
    void run(EntityManager em);
  }
  // PersistenceからEntityManagerを呼び出してから実行と終了する関数
  private static void transaction(Expression lambda) {
    // FactoryManagerを生成する。"JpaExample"はpersistence.xmlに書いている名だ。
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("JpaExample");
    // Managerを生成する。
    EntityManager em = emf.createEntityManager();
    try {
      // transactionを取得。
      EntityTransaction transaction = em.getTransaction();
      try {
        // transaction実行
        transaction.begin();
        // ラムダ式を実行する。
        lambda.run(em);
        // transactionをコミットする。
        transaction.commit();
      } catch (Throwable e) {
        // エラーが発生すればrollbackする。
        if (transaction.isActive()) {
          transaction.rollback();
        }
        // エラー出力
        e.printStackTrace();
      }
    } finally {
      // 各FactoryManagerとManagerを閉める。
      em.close();
      emf.close();
    }
  }
  // 実行関数
  public static void main(String... args) {
    transaction((em) -> {
      // JPQLクエリ
      Query qy = em.createQuery("select u from User u where u.id = :id");
      // パラメータ入力
      qy.setParameter("id", "nowonbun");
      // データ取得
      User user = (User)qy.getSingleResult();
      // コンソール出力
      System.out.println(user.getId());
      System.out.println(user.getName());
    });
  }
}


SQLクエリと違う点はテーブル名です。SQLにはテーブル名を大小文字区分なしで取得ができますが、JPQLはテーブル名ではなくクラス名です。

つまり、クラス名は大小文字を区分するので、userではなく、正確にUserということに作成しなければならないです。


そしてアスタリスクマーク(*)ですべてのデータを取得することもありません。

リターンするクラスを使います。我々はUserクラスでリターンするためにUserクラスの置換名(Aliases)というuを使います。


そしてwhere節にある検索のフィールド名はSQLのカラム名ではなく、クラスの変数名です。

現在の例は変数名とSQLカラム名が同じなので同じくみえます。


そうなら変数名を変更してテストしましょう。


idに@Columnアトリビュートを使ってsqlのカラム名をマッピングして変数名はtestに変更しましょう。

그럼 JPQL에서는 id가 아닌 test를 사용해야 합니다.

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;
import model.User;

public class Main {
  // ラムダ式のインタフェース
  interface Expression {
    void run(EntityManager em);
  }
  // PersistenceからEntityManagerを呼び出してから実行と終了する関数
  private static void transaction(Expression lambda) {
    // FactoryManagerを生成する。"JpaExample"はpersistence.xmlに書いている名だ。
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("JpaExample");
    // Managerを生成する。
    EntityManager em = emf.createEntityManager();
    try {
      // transactionを取得。
      EntityTransaction transaction = em.getTransaction();
      try {
        // transaction実行
        transaction.begin();
        // ラムダ式を実行する。
        lambda.run(em);
        // transactionをコミットする。
        transaction.commit();
      } catch (Throwable e) {
        // エラーが発生すればrollbackする。
        if (transaction.isActive()) {
          transaction.rollback();
        }
        // エラー出力
        e.printStackTrace();
      }
    } finally {
      // 各FactoryManagerとManagerを閉める。
      em.close();
      emf.close();
    }
  }
  // 実行関数
  public static void main(String... args) {
    transaction((em) -> {
      // JQPLクエリ入力
      Query qy = em.createQuery("select u from User u where u.test = :id");
      // パラメータ入力
      qy.setParameter("id", "nowonbun");
      // データ取得
      User user = (User)qy.getSingleResult();
      // コンソール出力
      System.out.println(user.getId());
      System.out.println(user.getName());
    });
  }
}


上のクエリではidではなく、testの変数名を使いました。もちろん、変数名なので大小文字区分は重要です。

そしたら結果はObject配列タイプではなく、UserタイプでリターンしてUserデータを使います。


JQPLのクエリはFETCHのキーワードでリファレンスデータの結果も制御できます。


上の例みたいにinfoテーブルにageが21のデータを大量インサートしました。

でも、私は20のデータだけInfoリストに検索したいです。

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;
import model.User;

public class Main {
  // ラムダ式のインタフェース
  interface Expression {
    void run(EntityManager em);
  }
  // PersistenceからEntityManagerを呼び出してから実行と終了する関数
  private static void transaction(Expression lambda) {
    // FactoryManagerを生成する。"JpaExample"はpersistence.xmlに書いている名だ。
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("JpaExample");
    // Managerを生成する。
    EntityManager em = emf.createEntityManager();
    try {
      // transactionを取得。
      EntityTransaction transaction = em.getTransaction();
      try {
        // transaction実行
        transaction.begin();
        // ラムダ式を実行する。
        lambda.run(em);
        // transactionをコミットする。
        transaction.commit();
      } catch (Throwable e) {
        // エラーが発生すればrollbackする。
        if (transaction.isActive()) {
          transaction.rollback();
        }
        // エラー出力
        e.printStackTrace();
      }
    } finally {
      // 各FactoryManagerとManagerを閉める。
      em.close();
      emf.close();
    }
  }
  // 実行関数
  public static void main(String... args) {
    transaction((em) -> {
      // JPQL作成
      Query qy = em.createQuery("select u from User u join fetch u.infos i where u.id = :id and i.age = 20");
      // パラメータ入力
      qy.setParameter("id", "nowonbun");
      // データ取得
      User user = (User)qy.getSingleResult();
      // コンソール出力
      System.out.println(user.getInfos().size());
      System.out.println(user.getInfos().get(0).getAge());
    });
  }
}


join fetchの構文をいれてinfosをiに置換してi.ageを20だけ検索するように設定しました。

結果はgetInfosのデータの個数は一つだし、そのデータのageは20だけあることを確認できます。


JPQLもstringタイプで作成するクエリなのでソースのあっちこっちに作成すると管理が難しいですね。

それで普通はEntityにクエリを作成して実際のロジックソースにはクエリを読み込んで使うタイプで作成します。


Entityクラスの上に@NamedQueriesアトリビュートを利用してクエリを片付けます。


参考に私はIDEをeclipseツールを使います。Entityの上にクエリを作成するとStringデータでもQuery検査をします。でも、これがバグがあります。fetch join置換値を認識できません。

もちろん、無視してデバッグ実行しても問題ないですが、ツールでエラー表示が出ると見づらいです。

プロジェクトのpreferencesに入ってJPA -> query部分のエラー表示を変更しましょう。


Reference - https://www.eclipse.org/forums/index.php/t/369011/
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;
import model.User;

public class Main {
  // ラムダ式のインタフェース
  interface Expression {
    void run(EntityManager em);
  }
  // PersistenceからEntityManagerを呼び出してから実行と終了する関数
  private static void transaction(Expression lambda) {
    // FactoryManagerを生成する。"JpaExample"はpersistence.xmlに書いている名だ。
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("JpaExample");
    // Managerを生成する。
    EntityManager em = emf.createEntityManager();
    try {
      // transactionを取得。
      EntityTransaction transaction = em.getTransaction();
      try {
        // transaction実行
        transaction.begin();
        // ラムダ式を実行する。
        lambda.run(em);
        // transactionをコミットする。
        transaction.commit();
      } catch (Throwable e) {
        // エラーが発生すればrollbackする。
        if (transaction.isActive()) {
          transaction.rollback();
        }
        // エラー出力
        e.printStackTrace();
      }
    } finally {
      // 各FactoryManagerとManagerを閉める。
      em.close();
      emf.close();
    }
  }
  // 実行関数
  public static void main(String... args) {
    transaction((em) -> {
      // Entityの@NamedQueryからデータを取得する。
      Query qy = em.createNamedQuery("User.nowonbun", User.class);
      // パラメータ入力
      qy.setParameter("id", "nowonbun");
      // データ取得
      User user = (User)qy.getSingleResult();
      // コンソール出力
      System.out.println(user.getInfos().size());
      System.out.println(user.getInfos().get(0).getAge());
    });
  }
}


そしてcreateNamedQueryを通ってEntityクラスからクエリを読み込んでデータを取得しても結果は同じです。

こんなにするとすべてのクエリを一括で管理ができますね。データベースのテーブル変更が発生する時にそのことに関するアップデータ管理も楽になりますね。


ここまでJPAでQueryを使う方法(JPQLクエリを作成する方法)に関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

Study / Java」の他投稿
最新投稿