ZetCode

Spring Boot Data JPA Query By Example

最后修改于 2023 年 7 月 25 日

在本文中,我们将展示如何使用 Spring Data JPA Query By Example 技术创建查询。

Spring 是一个流行的 Java 应用程序框架,用于创建企业应用程序。Spring Boot 是 Spring 框架的演进,它有助于以最小的 effort 创建基于 Spring 的独立、生产级应用程序。

Spring Data JPA

Spring Data JPA 促进了基于 JPA 的存储库的实现。它增强了对基于 JPA 的数据访问层的支持。它使构建使用数据访问技术的 Spring 应用程序变得更容易。Spring Data JPA 是更大的 Spring Data 系列的一部分。

Spring Data JPA Query By Example

Query by Example (QBE) 是一种用户友好的查询技术,具有简单的界面。它允许动态创建查询。我们不需要用特定于存储的查询语言编写查询。

我们使用三个对象。probe 是一个实际的域对象的示例,其中包含已填充的字段。ExampleMatcher 包含有关如何匹配特定字段的详细信息。Example 由 probe 和 ExampleMatcher 组成。它用于创建查询。

QBE 有一些限制;它无法创建一些更高级的查询。

Spring Boot Data JPA QBE 示例

以下应用程序使用 QBE 生成查询。

build.gradle 
...
src
├───main
│   ├───java
│   │   └───com
│   │       └───zetcode
│   │           │   Application.java
│   │           │   MyRunner.java
│   │           ├───model
│   │           │       City.java
│   │           ├───repository
│   │           │       CityRepository.java
│   │           └───service
│   │                   CityService.java
│   │                   ICityService.java
│   └───resources
│           application.properties
│           data-h2.sql
│           schema-h2.sql
└───test
    └───java

这是项目结构。

build.gradle
plugins {
    id 'org.springframework.boot' version '3.1.1'
    id 'io.spring.dependency-management' version '1.1.0'
    id 'java'
}

group = 'com.zetcode'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.h2database:h2'
}

Gradle 构建文件包含 H2 数据库和 Spring Boot Data JPA 的依赖项。

resources/application.properties
spring.main.banner-mode=off
spring.sql.init.platform=h2
spring.jpa.hibernate.ddl-auto=none

application.properties 文件中,我们编写了 Spring Boot 应用程序的各种配置设置。使用 spring.main.banner-mode 属性,我们关闭了 Spring banner。

spring.sql.init.platform 设置数据库的供应商名称。它用于初始化脚本中。spring.jpa.hibernate.ddl-auto 禁用了从实体自动创建模式的功能。

com/zetcode/model/City.java
package com.zetcode.model;

import java.util.Objects;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "cities")
public class City {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;
    private int population;

    public City() {
    }

    public City(String name, int population) {
        this.name = name;
        this.population = population;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPopulation() {
        return population;
    }

    public void setPopulation(int population) {
        this.population = population;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 79 * hash + Objects.hashCode(this.id);
        hash = 79 * hash + Objects.hashCode(this.name);
        hash = 79 * hash + this.population;
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final City other = (City) obj;
        if (this.population != other.population) {
            return false;
        }
        if (!Objects.equals(this.name, other.name)) {
            return false;
        }
        return Objects.equals(this.id, other.id);
    }

    @Override
    public String toString() {

        var builder = new StringBuilder();
        builder.append("City{id=").append(id).append(", name=")
                .append(name).append(", population=")
                .append(population).append("}");

        return builder.toString();
    }
}

这是 City 实体。每个实体都必须至少定义两个注解:@Entity@Id

resources/schema-h2.sql
CREATE TABLE cities(id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255), population INT);

当应用程序启动时,将执行 schema-h2.sql 脚本。它创建一个新的数据库表。

resources/data-h2.sql
INSERT INTO cities(name, population) VALUES('Bratislava', 432000);
INSERT INTO cities(name, population) VALUES('Budapest', 1759000);
INSERT INTO cities(name, population) VALUES('Prague', 1280000);
INSERT INTO cities(name, population) VALUES('Warsaw', 1748000);
INSERT INTO cities(name, population) VALUES('Los Angeles', 3971000);
INSERT INTO cities(name, population) VALUES('New York', 8550000);
INSERT INTO cities(name, population) VALUES('Brest', 139163);
INSERT INTO cities(name, population) VALUES('Edinburgh', 464000);
INSERT INTO cities(name, population) VALUES('Suzhou', 4327066);
INSERT INTO cities(name, population) VALUES('Zhengzhou', 4122087);
INSERT INTO cities(name, population) VALUES('Berlin', 3671000);
INSERT INTO cities(name, population) VALUES('Bucharest', 1836000);

稍后,将执行 data-h2.sql 文件。它用数据填充表。

com/zetcode/repository/CityRepository.java
package com.zetcode.repository;

import com.zetcode.model.City;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import org.springframework.stereotype.Repository;

@Repository
public interface CityRepository extends CrudRepository<City, Long>,
        QueryByExampleExecutor<City> {

}

我们通过从 QueryByExampleExecutor 扩展我们的存储库来启用 QBE API。

com/zetcode/service/ICityService.java
package com.zetcode.service;

import com.zetcode.model.City;

import java.util.List;

public interface ICityService {

    List<City> findByNameEnding(String ending);
    List<City> findByName(String name);
}

ICityService 提供了两个契约方法。

com/zetcode/service/CityService.java
package com.zetcode.service;

import com.zetcode.model.City;
import com.zetcode.repository.CityRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.stereotype.Service;

import java.util.List;

import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers.exact;

@Service
public class CityService implements ICityService {

    private final CityRepository cityRepository;

    @Autowired
    public CityService(CityRepository cityRepository) {
        this.cityRepository = cityRepository;
    }

    @Override
    public List<City> findByNameEnding(String ending) {

        var city = new City();
        city.setName(ending);

        var matcher = ExampleMatcher.matching()
                .withMatcher("name", ExampleMatcher.GenericPropertyMatcher::endsWith)
                .withIgnorePaths("population");

        var example = Example.of(city, matcher);
        return (List<City>) cityRepository.findAll(example);
    }

    @Override
    public List<City> findByName(String name) {

        var city = new City();
        city.setName(name);

        var matcher = ExampleMatcher.matching()
                .withMatcher("name", exact())
                .withIgnorePaths("population");

        var example = Example.of(city, matcher);
        return (List<City>) cityRepository.findAll(example);
    }
}

CityService 包含服务方法实现。

var city = new City();
city.setName(ending);

我们有一个 City 域对象。这被称为 probe。

var matcher = ExampleMatcher.matching()
    .withMatcher("name", match -> match.endsWith())
    .withIgnorePaths("population");

匹配器将字符串结尾与城市名称匹配,并忽略人口。

var example = Example.of(city, matcher);

Example 由域对象和匹配器创建。

return (List<City>) cityRepository.findAll(example);

Example 被传递给 findAll 方法。

com/zetcode/MyRunner.java
package com.zetcode;

import com.zetcode.service.ICityService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class MyRunner implements CommandLineRunner {

    private static final Logger logger = LoggerFactory.getLogger(MyRunner.class);

    private final ICityService cityService;

    @Autowired
    public MyRunner(ICityService cityService) {
        this.cityService = cityService;
    }

    @Override
    public void run(String... args) throws Exception {

        logger.info("Finding cities by name");
        var res1 = cityService.findByName("Bratislava");
        logger.info("{}", res1);

        var res2 = cityService.findByName("Berlin");
        logger.info("{}", res2);

        logger.info("Finding cities by name ending with est");
        var res3 = cityService.findByNameEnding("est");
        logger.info("{}", res3);
    }
}

MyRunner 查找 'Bratislava' 和 'Berlin' 城市,并找到所有名称以 'est' 结尾的城市。

com/zetcode/Application.java
package com.zetcode;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Application 设置了 Spring Boot 应用程序。@SpringBootApplication 启用了自动配置和组件扫描。

$ ./gradlew bootRun
...
... Finding cities by name
... [City{id=1, name=Bratislava, population=432000}]
... [City{id=11, name=Berlin, population=3671000}]
... Finding cities by name ending with est
... [City{id=2, name=Budapest, population=1759000}, City{id=7, name=Brest, population=139163}, 
     City{id=12, name=Bucharest, population=1836000}]
...

我们运行应用程序。

在本文中,我们使用 Spring Data JPA Query By Example 技术来生成查询。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出 所有 Spring Boot 教程