Help us understand the problem. What is going on with this article?

Spring Data JPAのQBE(Query by Example)を使った検索のサンプルコード

More than 3 years have passed since last update.

概要

QBE(Query by Example:”例示による問い合わせ”)とは問い合わせ言語の1つで、下記にwikipediaの説明を引用させて頂きました。

例示による問い合わせ
関係データベース (リレーショナルデータベース) 向けの問い合わせ言語の一つである。 QBEは、1970年代半ばにアメリカ合衆国ニューヨーク州ヨークタウンのIBM研究センターで、別の問い合わせ言語SQLの開発と並行して、モシェ・ズルーフが考案した。 QBEは、最初の視覚的な問い合わせ言語である。

Spring Data JPAではバージョン1.10よりサポートされていたので、簡単な検索処理のサンプルコードを書いてみました。

環境

  • Windows10 Professional
  • Java 1.8.0_144
  • Spring Boot 1.5.6
  • Spring Data JPA 1.11.6

参考

QBEで検索するには

QBEで問い合わせを行うには、Spring Data JPAのExample(Exampleというとサンプルコードなどを連想してしまいますが、org.springframework.data.domain.Exampleというクラスのことです)、ExampleMatcherを使用します。またQueryByExampleExecutorというインターフェースも使いますが、JpaRepositoryがこれを継承しているのでJpaRepositoryを利用していれば実装で改めて意識する必要はありません。

サンプルコード

下記に簡単ですが、QBEで検索するサンプルコードを掲載します。QBEでは簡単な検索処理を簡潔に記述することができますが、複雑な条件での検索には向いていないように感じました。

エンティティ

QBE検索のサンプルコードで使用するItemエンティティです。

@Entity
@Table(name="item")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString(exclude = {"itemStocks"})
@EqualsAndHashCode(exclude = {"itemStocks"})
public class Item implements Serializable {

    private static final long serialVersionUID = -3153084093423004609L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name="name", nullable = false)
    private String name;
    @Column(name="price", nullable = false)
    private Integer price;
    @Column(name="sales_from", nullable = false)
    private LocalDateTime salesFrom;
    @Column(name="sales_to", nullable = false)
    private LocalDateTime salesTo;
    @Enumerated(EnumType.ORDINAL)
    @Column(name="standard_type", nullable = false)
    private StandardType standardType;
    @JoinColumn(name = "category_id", nullable = false)
    @ManyToOne
    private Category category;
    @Column(name="del_flag", nullable = false)
    private Boolean delFlag;
    @Column(name="create_at", nullable = false)
    private LocalDateTime createAt;
    @Column(name="update_at", nullable = false)
    private LocalDateTime updateAt;

    @OneToMany(mappedBy = "item", cascade = CascadeType.ALL)
    private List<ItemStock> itemStocks;
}

リポジトリ

ItemRepositoryです。特にQBE用のカスタムメソッドの実装は行っていません。
JpaRepositoryはQueryByExampleExecutorを継承しているので、このままでQBEでの検索が行えます。

import com.example.domain.entity.Item;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ItemRepository extends JpaRepository<Item, Long> { }

ちなみに、QueryByExampleExecutorインターフェースで定義されているメソッドはfindOne, findAll, count, existsがあります。

Example

もっとも簡単な検索は、検索条件をエンティティで表現し、それを使ってExampleインスタンスを生成し、リポジトリの検索メソッドへ渡す方法です。

このサンプルは、priceが1000、standardTypeがDのデータを検索します。
エンティティのプロパティにnull以外の値がセットされると検索条件として使用されます。

@Autowired
private ItemRepository repository;

public void find() {

  // 検索条件をエンティティにセット
  Item probe = new Item();
  probe.setPrice(1000);
  probe.setStandardType(StandardType.D);

  // 検索条件からExampleインスタンスを生成
  Example<Item> example = Example.of(probe);

  List<Item> items = repository.findAll(example);
  items.forEach(System.out::println);
}

ExampleMatcher

もう少し複雑な条件で検索したい場合はExampleMatcherで検索条件を組み立てます。

matching / matchingAll

検索条件で指定するプロパティすべてが一致するデータを検索するExampleMatcherを生成するstaticメソッドです。
なお、matchingは内部でExampleMatcher#matchingAllで呼んでいます。

このサンプルは、nameが大文字/小文字を問わず"kitchen"で始まり、priceが800のデータを検索します。nameプロパティに対する検索条件はExampleMatcherで組み立てます。

Item item = new Item();
item.setName("kitchen");
item.setPrice(800);

ExampleMatcher matcher = ExampleMatcher.matching()
    .withMatcher("name", match -> match.ignoreCase().startsWith());

Example<Item> example = Example.of(item, matcher);

List<Item> items = repository.findAll(example);
items.forEach(System.out::println);

matchingAny

検索条件で指定するプロパティのどれか1つ以上が一致するデータを検索するExampleMatcherを生成するstaticメソッドです。

このサンプルは、nameが大文字/小文字を問わず"kitchen"で始まるか、又はpriceが800のデータを検索します。

Item item = new Item();
item.setName("kitchen");
item.setPrice(800);

ExampleMatcher matcher = ExampleMatcher.matchingAny()
    .withMatcher("name", match -> match.ignoreCase().startsWith());

Example<Item> example = Example.of(probe, matcher);

List<Item> items = repository.findAll(example);
items.forEach(System.out::println);

withIgnorePaths

検索に使用するエンティティのプロパティがnull以外だと検索条件として扱われてしまいます。特定のプロパティを検索条件としたくない場合はwithIgnorePathsでプロパティ名を指定して除外することができます。

このサンプルは、id, price, standardTypeプロパティを検索条件から除外します。

ExampleMatcher matcher = ExampleMatcher.matchingAny()
    .withMatcher("name", match -> match.ignoreCase().startsWith())
    .withIgnorePaths("id", "price", "standardType");

withIncludeNullValues

nullの場合も検索したい場合はwithIncludeNullValuesを使用します。ただし、上記のwithIgnorePathsも併用して対象のプロパティを絞り込まないとすべてのプロパティに対して適用されてしまうので注意が必要です。SQL文では対応するカラムに"カラム名 is null"という条件が加えられます。

ExampleMatcher matcher = ExampleMatcher.matchingAny()
    .withMatcher("name", match -> match.ignoreCase().startsWith())
    .withIncludeNullValues();
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした