[Java][Spring Boot] JPQLのクエリの作成方法

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

前回はDAOでクエリを実行しました。
今回は、クエリの実行手段を複数紹介します。
JPA自体はJavaとデータベースを繋ぐものでSpringの機能ではありませんが、基本として必要なので紹介します。

環境

Mac OSX 10.10.5 Yosemite
Eclipse Mars2
Java 8
Spring Boot 1.3.6
PostgreSQL 9.5.1

JPQLの手順

1.EntityManagerの用意

@PersistenceContext
EntityManager entityManager;

これを元にクエリを作成します。 アプリ実行時に1つだけBeanに登録されるので、基本は@PresistenceContextで接続します。
複数箇所で接続するとエラーになるので記述場所に注意が必要です。
@Autowiredでも可能ですが制限があるようです。

2.クエリ作成

以下3種類の方法を紹介します。

方法① JPQLで作成
Query query = entityManager
		.createQuery("from Fruit where id = :id")
		.setParameter("id", 変数);

JPQLでクエリを作成する場合。setParameterで変数をセットできます。
2行目の「:id」には、3行目で設定したパラメータが適用されます。

方法② SQLで作成
Query query = entityManager
		.createNativeQuery("select * from Fruit where id = ?1 or id = ?2")
		.setParameter(1, 変数)
		.setParameter(2, 変数);

2行目、SQLでクエリを作成する場合。
2行目の「?1」には、3行目で設定したパラメータが適用されます。
上記ではパラメータの設定方法を変えましたが、JPQLでもSQLでも共通で使えます。

方法③ クエリアノテーションで作成
@NamedQuery(
	name="クエリ名",
	query="JPQLもしくはSQL"
)

エンティティクラスにアノテートします。
queryに書いたものが、nameで呼ばれる訳ですね。
使用例については最後に書いておきます。

3.実行と取得

List<'クラス名'> list = query.getResultList();
Object result = query.getSingleResult();

1行目、クエリの実行結果をリストで取得しています。
2行目、実行結果を1件だけ取得します。
こちらは取得できなかったり、複数取得するとExceptionが発生します。

また、クエリ作成から取得までを下記の様に短縮可能です。

List<'クラス名'> list = entityManager
		.createQuery("from テーブル名 where id = :id")
		.setParameter("id",変数)
		.getResultList();

DAOでクエリアノテーションを使ってみる

テーブルを用意

CREATE TABLE fruit (
    id VARCHAR(2) NOT NULL,
    name VARCHAR(10),
    price integer,
    PRIMARY KEY(id)
);
 
INSERT INTO fruit VALUES
  ('1','apple',300),
  ('2','orange',200),
  ('3','banana',100);
postgres=# select * from fruit;
 id |  name  | price 
----+--------+-------
 1  | apple  |   300
 2  | orange |   200
 3  | banana |   100
(3 rows)

エンティティ

package com.jpa.mydao;

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

import lombok.Data;

@Entity
@Data
@Table(name="fruit")
@NamedQueries({
	@NamedQuery(
		name="findAllFruitWithName",
		query="SELECT f FROM Fruit f WHERE f.name LIKE :fruitName"
	)
	,
	@NamedQuery(
		name="findLikeName",
		query="from Fruit where name LIKE :fruitName"
	)
})
public class Fruit {

	@Id
	private String id;
	private String name;
	private Integer price;

}

基本は、15~18行目20~23行目の様に@NamedQueryでJPQLかSQLをqueryに設定し、それらを呼び出す名称をnameに設定します。
14行目の様に、@NamedQueriesで囲めば複数のNamedQueryを設定可能です。
15~18行目、SQL。
20~23行目、PSQL。

DAOインターフェース

package com.jpa.mydao.dao;

import java.io.Serializable;
import java.util.List;

public interface FruitDao <T> extends Serializable {
	public List<T> findAllFruitWithName(String name);
	public List<T> findLikeName(String name);
}

分かりやすく、クエリ名と同じメソッド名で作成しました。

DAOオブジェクト

package com.jpa.mydao.dao;

import java.util.List;

import javax.persistence.EntityManager;

import org.springframework.stereotype.Repository;

import com.jpa.mydao.Fruit;

@Repository
public class FruitDaoJpql implements FruitDao<Fruit>{

	private EntityManager entityManager;

	public FruitDaoJpql(EntityManager entityManager) {
		super();
		this.entityManager = entityManager;
	}

	@Override
	public List<Fruit> findAllFruitWithName(String name) {
		List<Fruit> list = entityManager.createNamedQuery("findAllFruitWithName")
				.setParameter("fruitName", "%" + name + "%")
				.getResultList();
		return list;
	}
	
	@Override
	public List<Fruit> findLikeName(String name) {
		List<Fruit> list = entityManager.createNamedQuery("findLikeName")
				.setParameter("fruitName", "%" + name + "%")
				.getResultList();
		return list;
	}
}

DAOインターフェースのメソッド:findAllFruitWithName。
23行目、エンティティで設定したクエリ名を呼んでいます。
24行目、クエリはLikeを使ったあいまい検索なので、パラメータのnameを%で囲んでいます。

DAOインターフェースのメソッド:findLikeName。
こちらも上記と同様ですね。

コントローラー

package com.jpa.mydao;

import java.util.List;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.jpa.mydao.dao.FruitDaoJpql;

@RestController
public class EntityManagerController {

	@PersistenceContext
	EntityManager entityManager;

	FruitDaoJpql dao;
	
	@PostConstruct
	public void init() {
		dao = new FruitDaoJpql(entityManager);
	}

	/* SQL */
	@RequestMapping(value="/findallwith", method=RequestMethod.GET)
	public List<Fruit> findAllWith(@RequestParam("name") String name) {
		List<Fruit> list = dao.findAllFruitWithName(name);
		return list;
	}

	/* JPQL */
	@RequestMapping(value="/findlike", method=RequestMethod.GET)
	public List<Fruit> findLike(@RequestParam("name") String name) {
		List<Fruit> list = dao.findLikeName(name);
		return list;
	}	
}

24~26行目、EntityManagerをDAOに渡して準備完了。
残り2つのメソッドは同じ内容ですね。
無駄にも感じますが、分かりやすさ重視という事で...

ターミナルで実行

実行コマンドと結果①

テーブルのカラムnameから「pp」をあいまい検索した結果。

$ curl http://localhost:8080/findlike?name=pp
[{"id":"1","name":"apple","price":300}]

実行コマンドと結果②

テーブルのカラムnameから「e」をあいまい検索した結果。

$ curl http://localhost:8080/findallwith?name=e
[{"id":"1","name":"apple","price":300},{"id":"2","name":"orange","price":200}]

さいごに

クエリアノテーションは中々見ない書き方で、アノテーション地獄の未来が見えてきますが、流れを覚えてしまえば慣れかなと思います。