Extension for Spring Data REST

虽然建议用户对 REST 数据访问端点使用 REST 数据和 Panache,但 Quarkus 提供了 Spring Data REST 的兼容层,采用 spring-data-rest 拓展的形式。

While users are encouraged to use REST Data with Panache for the REST data access endpoints generation, Quarkus provides a compatibility layer for Spring Data REST in the form of the spring-data-rest extension.

Prerequisites

Unresolved directive in spring-data-rest.adoc - include::{includes}/prerequisites.adoc[]

Solution

我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。

We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.

克隆 Git 存储库: git clone {quickstarts-clone-url},或下载 {quickstarts-archive-url}[存档]。

Clone the Git repository: git clone {quickstarts-clone-url}, or download an {quickstarts-archive-url}[archive].

解决方案位于 spring-data-rest-quickstart directory 中。

The solution is located in the spring-data-rest-quickstart directory.

Creating the Maven project

首先,我们需要一个新项目。使用以下命令创建一个新项目:

First, we need a new project. Create a new project with the following command:

Unresolved directive in spring-data-rest.adoc - include::{includes}/devtools/create-app.adoc[]

此命令生成带 spring-data-rest 拓展的项目。

This command generates a project with the spring-data-rest extension.

如果已配置 Quarkus 项目,则可以通过在项目基础目录中运行以下命令将其添加到项目中:

If you already have your Quarkus project configured, you can add the spring-data-rest extension to your project by running the following command in your project base directory:

Unresolved directive in spring-data-rest.adoc - include::{includes}/devtools/extension-add.adoc[]

这会将以下内容添加到构建文件中:

This will add the following to your build file:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-spring-data-rest</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-spring-data-rest")

此外,还需要添加以下依赖项

Furthermore, the following dependency needs to be added

对于测试,还需要 REST Assured。将其添加到构建文件中:

For the tests you will also need REST Assured. Add it to the build file:

pom.xml
<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
</dependency>
build.gradle
testImplementation("io.rest-assured:rest-assured")

注意:resteasy-jacksonresteasy-jsonb 都得到支持,且可以互换。

Note: both resteasy-jackson and resteasy-jsonb are supported and can be interchanged.

Define the Entity

在整个本指南的过程中,将使用以下 JPA 实体:

Throughout the course of this guide, the following JPA Entity will be used:

package org.acme.spring.data.rest;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

@Entity
public class Fruit {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private String color;


    public Fruit() {
    }

    public Fruit(String name, String color) {
        this.name = name;
        this.color = color;
    }

    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 String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

Configure database access properties

将以下属性添加到 application.properties,以配置对本地 PostgreSQL 实例的访问。

Add the following properties to application.properties to configure access to a local PostgreSQL instance.

quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus_test
quarkus.datasource.password=quarkus_test
quarkus.datasource.jdbc.url=jdbc:postgresql:quarkus_test
quarkus.datasource.jdbc.max-size=8
quarkus.hibernate-orm.database.generation=drop-and-create

此配置假定 PostgreSQL 将在本地运行。

This configuration assumes that PostgreSQL will be running locally.

实现此目的的一种非常简单的方法是使用以下 Docker 命令:

A very easy way to accomplish that is by using the following Docker command:

docker run -it --rm=true --name quarkus_test -e POSTGRES_USER=quarkus_test -e POSTGRES_PASSWORD=quarkus_test -e POSTGRES_DB=quarkus_test -p 5432:5432 postgres:14.1

如果您计划使用其他设置,请相应地更改 application.properties

If you plan on using a different setup, please change your application.properties accordingly.

Prepare the data

为了更轻松地展示 Spring Data REST 在 Quarkus 上的一些功能,应将一些测试数据插入数据库,为此,将以下内容添加到名为 src/main/resources/import.sql 的新文件中:

To make it easier to showcase some capabilities of Spring Data REST on Quarkus, some test data should be inserted into the database by adding the following content to a new file named src/main/resources/import.sql:

INSERT INTO fruit(id, name, color) VALUES (1, 'Cherry', 'Red');
INSERT INTO fruit(id, name, color) VALUES (2, 'Apple', 'Red');
INSERT INTO fruit(id, name, color) VALUES (3, 'Banana', 'Yellow');
INSERT INTO fruit(id, name, color) VALUES (4, 'Avocado', 'Green');
INSERT INTO fruit(id, name, color) VALUES (5, 'Strawberry', 'Red');

Hibernate ORM 将在应用程序启动时执行这些查询。

Hibernate ORM will execute these queries on application startup.

Define the repository

现在是定义用于访问 Fruit 的存储库的时候了。采用典型的 Spring Data 方式,如下创建存储库:

It is now time to define the repository that will be used to access Fruit. In a typical Spring Data fashion, create a repository like so:

package org.acme.spring.data.rest;

import org.springframework.data.repository.CrudRepository;

public interface FruitsRepository extends CrudRepository<Fruit, Long> {
}

上面的 FruitsRepository 扩展了 Spring Data 的 org.springframework.data.repository.CrudRepository,这意味着后者的所有方法都可用在 FruitsRepository 中。

The FruitsRepository above extends Spring Data’s org.springframework.data.repository.CrudRepository which means that all the latter’s methods are available to FruitsRepository.

spring-data-jpa 扩展将为此存储库生成一个实现。然后 spring-data-rest 扩展将为其生成一个 REST CRUD 资源。

The spring-data-jpa extension will generate an implementation for this repository. Then the spring-data-rest extension will generate a REST CRUD resource for it.

Update the test

若要测试 FruitsRepository 的功能,请继续将 FruitsRepositoryTest 的内容更新为:

To test the capabilities of FruitsRepository proceed to update the content of FruitsRepositoryTest to:

package org.acme.spring.data.rest;

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.core.IsNot.not;

@QuarkusTest
class FruitsRepositoryTest {

    @Test
    void testListAllFruits() {
        //List all, should have all 3 fruits the database has initially:
        given()
                .accept("application/json")
                .when().get("/fruits")
                .then()
                .statusCode(200)
                .body(
                        containsString("Cherry"),
                        containsString("Apple"),
                        containsString("Banana")
                );

        //Delete the Cherry:
        given()
                .when().delete("/fruits/1")
                .then()
                .statusCode(204);

        //List all, cherry should be missing now:
        given()
                .accept("application/json")
                .when().get("/fruits")
                .then()
                .statusCode(200)
                .body(
                        not(containsString("Cherry")),
                        containsString("Apple"),
                        containsString("Banana")
                );

        //Create a new Fruit
        given()
                .contentType("application/json")
                .accept("application/json")
                .body("{\"name\": \"Orange\", \"color\": \"Orange\"}")
                .when().post("/fruits")
                .then()
                .statusCode(201)
                .body(containsString("Orange"))
                .body("id", notNullValue())
                .extract().body().jsonPath().getString("id");

        //List all, Orange should be present now:
        given()
                .accept("application/json")
                .when().get("/fruits")
                .then()
                .statusCode(200)
                .body(
                        not(containsString("Cherry")),
                        containsString("Apple"),
                        containsString("Orange")
                );
    }
}

可以通过发出以下命令轻松运行测试:

The test can be easily run by issuing:

Unresolved directive in spring-data-rest.adoc - include::{includes}/devtools/test.adoc[]

Package and run the application

Quarkus 开发模式适用于已定义的存储库,就像适用于任何其他 Quarkus 扩展一样,大大提高了开发周期中的生产力。可以使用以下命令像平常一样在开发模式下启动应用程序:

Quarkus dev mode works with the defined repositories just like with any other Quarkus extension, greatly enhancing your productivity during the dev cycle. The application can be started in dev mode as usual using:

Unresolved directive in spring-data-rest.adoc - include::{includes}/devtools/dev.adoc[]

Run the application as a native binary

当然,你可以根据 Building native executables 指南的说明创建一个本机可执行文件。

You can of course create a native executable following the instructions of the Building native executables guide.

Supported Spring Data REST functionalities

Quarkus 目前支持 Spring Data REST 功能的子集,即最有用和最常用的功能。

Quarkus currently supports a subset of Spring Data REST features, namely the most useful and most commonly used features.

What is supported

以下部分介绍了 Spring Data REST 最重要的受支持功能。

The following sections describe the most important supported features of Spring Data REST.

Automatic REST endpoint generation

扩展以下任何 Spring Data 存储库的接口都会自动生成 REST 端点:

Interfaces that extend any of the following Spring Data repositories get automatically generated REST endpoints:

  • org.springframework.data.repository.CrudRepository

  • org.springframework.data.repository.PagingAndSortingRepository

  • org.springframework.data.jpa.repository.JpaRepository

从以上存储库生成的端点公开五个常见的 REST 操作:

Endpoints generated from the above repositories expose five common REST operations:

  • GET /fruits - lists all entities or returns a page if PagingAndSortingRepository or JpaRepository is used.

  • GET /fruits/:id - returns an entity by ID.

  • POST /fruits - creates a new entity.

  • PUT /fruits/:id - updates an existing entity or creates a new one with a specified ID (if allowed by the entity definition).

  • DELETE /fruits/:id - deletes an entity by ID.

支持两种数据类型: application/jsonapplication/hal+json。默认使用前者,但强烈建议使用 Accept 标头指定你更喜欢哪一种。

There are two supported data types: application/json and application/hal+json. The former is used by default, but it is highly recommended to specify which one you prefer with an Accept header.

Exposing many entities

如果一个数据库包含许多实体,一次全部返回可能不是一个好主意。PagingAndSortingRepository 允许 spring-data-rest 扩展分块访问数据。

If a database contains many entities, it might not be a great idea to return them all at once. PagingAndSortingRepository allows the spring-data-rest extension to access data in chunks.

FruitsRepository 中用 PagingAndSortingRepository 替换 CrudRepository

Replace the CrudRepository with PagingAndSortingRepository in the FruitsRepository:

package org.acme.spring.data.rest;

import org.springframework.data.repository.PagingAndSortingRepository;

public interface FruitsRepository extends PagingAndSortingRepository<Fruit, Long> {
}

现在 GET /fruits 将接受三个新查询参数: sortpagesize

Now the GET /fruits will accept three new query parameters: sort, page and size.

Query parameter Description Default value Example values

sort

Sorts the entities that are returned by the list operation

""

?sort=name (ascending name), ?sort=name,-color (ascending name and descending color)

page

Zero indexed page number. Invalid value is interpreted as 0.

0

0, 11, 100

size

Page size. Minimal accepted value is 1. Any lower value is interpreted as 1.

20

1, 11, 100

对于分页响应,spring-data-rest 还返回了一组链接头,可以用来访问其他页面:第一页、前一页、下一页和最后一页。

For paged responses, spring-data-rest also returns a set of link headers that can be used to access other pages: first, previous, next and last.

Fine tuning endpoints generation

这使用户可以指定应公开哪种方法以及应使用什么路径对其进行访问。Spring Data REST 提供了可以使用以下两种注解:@RepositoryRestResource@RestResource.spring-data-rest 扩展支持这些注解的 exported, path collectionResourceRel 属性。

This allows user to specify which methods should be exposed and what path should be used to access them. Spring Data REST provides two annotations that can be used: @RepositoryRestResource and @RestResource. spring-data-rest extension supports the exported, path collectionResourceRel attributes of these annotations.

例如,假设可以按 /my-fruits 路径访问水果存储库但只允许 GET 操作。在这种情况中,FruitsRepository 如下所示:

Assume for example that fruits repository should be accessible by a /my-fruits path and only allow GET operation. In such a case, FruitsRepository would look like so:

package org.acme.spring.data.rest;

import java.util.Optional;

import org.springframework.data.repository.CrudRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;

@RepositoryRestResource(exported = false, path = "/my-fruits")
public interface FruitsRepository extends CrudRepository<Fruit, Long> {

    @RestResource(exported = true)
    Optional<Fruit> findById(Long id);

    @RestResource(exported = true)
    Iterable<Fruit> findAll();
}

spring-data-rest 只使用存储库方法的子集来进行数据访问。为了自定义其 REST 端点,为正确的方法添加注解非常重要:

spring-data-rest uses only a subset of the repository methods for data access. It is important to annotate the correct method in order to customize its REST endpoint:

REST operation CrudRepository PagingAndSortingRepository and JpaRepository

Get by ID

Optional<T> findById(ID id)

Optional<T> findById(ID id)

List

Iterable<T> findAll()

Page<T> findAll(Pageable pageable)

Create

<S extends T> S save(S entity)

<S extends T> S save(S entity)

Update

<S extends T> S save(S entity)

<S extends T> S save(S entity)

Delete

void deleteById(ID id)

void deleteById(ID id)

Securing endpoints

此扩展会自动使用 jakarta.annotation.security 包中的安全注解,这些注解在您的资源接口中定义:

This extension will automatically use the Security annotations within the package jakarta.annotation.security that are defined on your resource interfaces:

import jakarta.annotation.security.DenyAll;
import jakarta.annotation.security.RolesAllowed;

@DenyAll
public interface FruitResource extends CrudRepository<Fruit, Long> {
    @RolesAllowed("superuser")
    Iterable<Fruit> findAll();
}

请注意,此功能由 REST Data with Panache 扩展提供,此扩展在其内部使用。因此,纯 Spring Boot 应用程序的行为可能不同。

Note that this feature is provided by the REST Data with Panache extension that this extension is using under the hood. So, pure Spring Boot applications might not behave the same way.

What is currently unsupported

  • Only the repository methods listed above are supported. No other standard or custom methods are supported.

  • Only the exposed, path and collectionResourceRel annotation properties are supported.

Important Technical Note

请注意,Quarkus 中的 Spring 支持不会启动 Spring 应用程序上下文,也不会运行任何 Spring 基础架构类。Spring 类和注释仅用于读取元数据和/或用作用户代码方法返回类型或参数类型。

Please note that the Spring support in Quarkus does not start a Spring Application Context nor are any Spring infrastructure classes run. Spring classes and annotations are only used for reading metadata and / or are used as user code method return types or parameter types.