编辑此页面

使用 Panache 生成 Jakarta REST 资源

许多 Web 应用程序都是单调的 CRUD 应用程序,带有 REST API,编写起来很繁琐。为了简化这项任务,REST Data with Panache 扩展可以为您的实体和存储库生成基本的 CRUD 端点。

目前,此扩展支持带有 Panache 的 Hibernate ORM 和 MongoDB,并且可以生成使用 application/jsonapplication/hal+json 内容的 CRUD 资源,并生成由 Quarkus REST 支持的 REST 资源。

设置 REST Data with Panache

Quarkus 提供了以下扩展来设置 REST Data with Panache。请查看下面的兼容性表,以根据您正在使用的技术使用正确的扩展

表 1. 兼容性表
扩展 状态 Hibernate

quarkus-hibernate-orm-rest-data-panache

稳定版

ORM

quarkus-hibernate-reactive-rest-data-panache

实验性的

响应式

quarkus-mongodb-rest-data-panache

实验性的

ORM

Hibernate ORM

  • 将所需的依赖项添加到您的构建文件中

    • 带有 Panache 扩展的 Hibernate ORM REST Data (quarkus-hibernate-orm-rest-data-panache)

    • JDBC 驱动程序扩展 (quarkus-jdbc-postgresql, quarkus-jdbc-h2, quarkus-jdbc-mariadb, …​)

    • REST JSON 序列化扩展之一(例如 quarkus-rest-jackson

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-hibernate-orm-rest-data-panache</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-jackson</artifactId>
</dependency>
-->
build.gradle
implementation("io.quarkus:quarkus-hibernate-orm-rest-data-panache")
implementation("io.quarkus:quarkus-jdbc-postgresql")

implementation("io.quarkus:quarkus-rest-jackson")

要查看带有 Panache 的 Hibernate ORM REST Data 的实际应用,您可以查看hibernate-orm-rest-data-panache-quickstart快速入门。

Hibernate Reactive

  • 将所需的依赖项添加到您的 pom.xml

    • 带有 Panache 扩展的 Hibernate Reactive REST Data (quarkus-hibernate-reactive-rest-data-panache)

    • Vert.x 响应式数据库驱动程序扩展 (quarkus-reactive-pg-client, quarkus-reactive-mysql-client, …​)

    • Quarkus REST 序列化扩展之一 (quarkus-rest-jackson, quarkus-rest-jsonb, …​)

<dependencies>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-hibernate-reactive-rest-data-panache</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-reactive-pg-client</artifactId>
    </dependency>
   <!-- Use this if you are using REST Jackson for serialization -->
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-rest-jackson</artifactId>
    </dependency>
</dependencies>

MongoDB

  • 将所需的依赖项添加到您的构建文件中

    • MongoDB REST Data with Panache 扩展 (quarkus-mongodb-rest-data-panache)

    • RESTEasy JSON 序列化扩展之一 (quarkus-rest-jacksonquarkus-rest-jsonb)

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-mongodb-rest-data-panache</artifactId>
</dependency>

<!-- Use this if you are using Quarkus REST -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-jackson</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-mongodb-rest-data-panache")

// Use this if you are using Quarkus REST
implementation("io.quarkus:quarkus-rest-jackson")

生成资源

REST Data with Panache 基于您的应用程序中可用的接口生成 Jakarta REST 资源。对于您要生成的每个实体和存储库,请提供一个资源接口。不要实现这些接口,也不要提供自定义方法,因为它们将被忽略。 但是,您可以覆盖扩展接口中的方法以自定义它们(请参阅末尾的部分)。

PanacheEntityResource

如果您的应用程序有一个实体(例如 Person),它扩展了 PanacheEntityPanacheEntityBase 类,您可以指示 REST Data with Panache 使用以下接口生成其 Jakarta REST 资源

public interface PeopleResource extends PanacheEntityResource<Person, Long> {
}

PanacheRepositoryResource

如果您的应用程序有一个简单的实体(例如 Person)和一个存储库(例如 PersonRepository),它实现了 PanacheRepositoryPanacheRepositoryBase 接口,您可以指示 REST Data with Panache 使用以下接口生成其 Jakarta REST 资源

public interface PeopleResource extends PanacheRepositoryResource<PersonRepository, Person, Long> {
}

PanacheMongoEntityResource

如果您的应用程序有一个实体(例如 Person),它扩展了 PanacheMongoEntityPanacheMongoEntityBase 类,您可以指示 REST Data with Panache 使用以下接口生成其 Jakarta REST 资源

public interface PeopleResource extends PanacheMongoEntityResource<Person, Long> {
}

PanacheMongoRepositoryResource

如果您的应用程序有一个简单的实体(例如 Person)和一个存储库(例如 PersonRepository),它实现了 PanacheMongoRepositoryPanacheMongoRepositoryBase 接口,您可以指示 REST Data with Panache 使用以下接口生成其 Jakarta REST 资源

public interface PeopleResource extends PanacheMongoRepositoryResource<PersonRepository, Person, Long> {
}

生成的资源

对于实体和存储库,生成的资源在功能上是等效的。唯一的区别是特定的数据访问模式和正在使用的数据存储。

如果您定义了上述 PeopleResource 接口之一,此扩展将使用特定的数据访问策略生成其实现。然后,实现的类将由生成的 Jakarta REST 资源使用,该资源将如下所示

public class PeopleResourceJaxRs { // The actual class name is going to be unique
    @Inject
    PeopleResource resource;

    @GET
    @Path("{id}")
    @Produces("application/json")
    public RestResponse<Person> get(@PathParam("id") Long id){
        Person person = resource.get(id);
        if (person == null) {
            return ResponseBuilder.create(404).build();
        } else {
            return ResponseBuilder.ok(person).build();
        }
    }

    @GET
    @Produces("application/json")
    public RestResponse<Person> list(@QueryParam("sort") List<String> sortQuery,
            @QueryParam("page") @DefaultValue("0") int pageIndex,
            @QueryParam("size") @DefaultValue("20") int pageSize) {
        Page page = Page.of(pageIndex, pageSize);
        Sort sort = getSortFromQuery(sortQuery);
        List<Person> people = resource.list(page, sort);
        // ... build a response with page links and return a 200 response with a list
    }

    @GET
    @Path("/count")
    public RestResponse<Long> count() {
        return ResponseBuilder.ok(resource.count()).build();
    }

    @Transactional
    @POST
    @Consumes("application/json")
    @Produces("application/json")
    public RestResponse<Person> add(Person personToSave) {
        Person person = resource.add(personToSave);
        // ... build a new location URL and return 201 response with an entity
    }

    @Transactional
    @PUT
    @Path("{id}")
    @Consumes("application/json")
    @Produces("application/json")
    public RestResponse<Person> update(@PathParam("id") Long id, Person personToSave) {
        if (resource.get(id) == null) {
            Person person = resource.update(id, personToSave);
            return ResponseBuilder.create(204).build();
        }
        Person person = resource.update(id, personToSave);
        // ... build a new location URL and return 201 response with an entity
    }

    @Transactional
    @DELETE
    @Path("{id}")
    public RestResponse<Person> delete(@PathParam("id") Long id) {
        return !resource.delete(id) ? ResponseBuilder.create(404).build() : ResponseBuilder.create(204).build();
    }
}

资源自定义

REST Data with Panache 提供了 @ResourceProperties@MethodProperties 注释,可用于自定义资源的某些功能。

它可以在您的资源接口中使用

@ResourceProperties(hal = true, path = "my-people")
public interface PeopleResource extends PanacheEntityResource<Person, Long> {
    @MethodProperties(path = "all")
    List<Person> list(Page page, Sort sort);

    @MethodProperties(exposed = false)
    boolean delete(Long id);
}

可用选项

@ResourceProperties

  • exposed - 资源是否可以公开。一种全局资源属性,可以为每种方法覆盖。默认为 true

  • path - 资源基本路径。默认路径是带连字符的小写资源名称,不带 resourcecontroller 的后缀。

  • rolesAllowed - 允许访问资源的安全角色的列表。它需要一个 Quarkus Security 扩展才能存在,否则它将被忽略。默认为空。

  • paged - 集合响应是否应该分页。如果存在,则在响应标头中包含第一页、最后一页、上一页和下一页 URI。请求页面索引和大小取自 pagesize 查询参数,它们分别默认为 020。默认为 true

  • hal - 除了标准的 application/json 响应之外,还生成可以返回 application/hal+json 响应的其他方法(如果通过 Accept 标头请求)。默认为 false

  • halCollectionName - 生成 hal 集合响应时应使用的名称。默认名称是带连字符的小写资源名称,不带 resourcecontroller 的后缀。

@MethodProperties

  • exposed - 设置为 false 时,不会公开特定的 HTTP 动词。默认为 true

  • path - 操作路径(这附加到资源基本路径)。默认为空字符串。

  • rolesAllowed - 允许访问此操作的安全角色的列表。它需要一个 Quarkus Security 扩展才能存在,否则它将被忽略。默认为空。

向生成的资源添加其他方法

您可以通过将这些方法添加到资源接口,通过 REST Data with Panache 扩展向生成的资源添加其他方法,例如

@ResourceProperties
public interface PeopleResource extends PanacheEntityResource<Person, Long> {
    @GET
    @Path("/name/{name}")
    @Produces("application/json")
    default List<Person> findByName(@PathParam("name") String name) {
        return Person.find("name = :name", Collections.singletonMap("name", name)).list();
    }
}

并且此方法将与使用 https://:8080/people/name/Johan 生成的方法一起公开。

保护端点

REST Data with Panache 将使用包 jakarta.annotation.security 中的 Security 注释,这些注释在您的资源接口上定义

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

@DenyAll
@ResourceProperties
public interface PeopleResource extends PanacheEntityResource<Person, Long> {
    @RolesAllowed("superuser")
    boolean delete(Long id);
}

此外,如果您只对指定允许使用资源的角色感兴趣,则 @ResourceProperties@MethodProperties 注释具有字段 rolesAllowed 来列出允许访问资源或操作的安全角色。

查询参数以列出实体

REST Data with Panache 支持以下查询参数来获取实体列表

  • page - 列表操作应返回的页码。它仅适用于分页资源,并且是一个从 0 开始的数字。默认为 0。

  • size - 列表操作应返回的页面大小。它仅适用于分页资源,并且是一个从 1 开始的数字。默认为 20。

  • sort - 一个逗号分隔的字段列表,应使用这些字段来对列表操作的结果进行排序。字段按升序排序,除非它们以 - 为前缀。例如,?sort=name,-age 将按名称升序,按年龄降序对结果进行排序。

  • namedQuery - 应使用注释 @NamedQuery 在实体级别配置的命名查询。

例如,如果您想在第一页中获取两个 People 实体,您应该调用 https://:8080/people?page=0&size=2,并且响应应该如下所示

[
  {
    "id": 1,
    "name": "John Johnson",
    "birth": "1988-01-10"
  },
  {
    "id": 2,
    "name": "Peter Peterson",
    "birth": "1986-11-20"
  }
]

此外,您还可以通过添加具有字段名称和值的查询参数来按实体字段进行过滤,例如,调用 https://:8080/people?name=Peter Peterson 将返回

[
  {
    "id": 2,
    "name": "Peter Peterson",
    "birth": "1986-11-20"
  }
]
仅对 String、Boolean、Character、Double、Float、Integer、Long、Short、Byte 和原始类型支持按字段过滤。

使用 @NamedQuery 进行复杂过滤以列出实体

您可以指定一个命名查询来在列出实体时进行过滤。例如,在您的实体中具有以下命名查询

@Entity
@NamedQuery(name = "Person.containsInName", query = "from Person where name like CONCAT('%', CONCAT(:name, '%'))")
public class Person extends PanacheEntity {
  String name;
}

在此示例中,我们添加了一个命名查询来列出包含 name 字段中某些文本的所有人员。

接下来,我们可以设置一个查询参数 namedQuery,在使用生成的资源列出实体时,使用我们要使用的命名查询的名称,例如,调用 https://:8080/people?namedQuery=Person.containsInName&name=ter 将返回名称包含文本“ter”的所有人员。

有关命名查询如何工作的更多信息,请转到Hibernate ORM指南或Hibernate Reactive指南。

资源方法 Before/After 监听器

REST Data with Panache 支持订阅以下资源方法挂钩

  • 在添加资源之前/之后

  • 在更新资源之前/之后

  • 在删除资源之前/之后

要注册您的资源方法监听器,您需要提供一个实现接口 RestDataResourceMethodListener 的 bean,例如

@ApplicationScoped
public class PeopleRestDataResourceMethodListener implements RestDataResourceMethodListener<Person> {
    @Override
    public void onBeforeAdd(Person person) {
        System.out.println("Before Save Person: " + person.name);
    }
}

响应正文示例

如上所述,REST Data with Panache 支持 application/jsonapplication/hal+json 响应内容类型。以下是一些假设数据库中有五个 Person 记录的 getlist 操作的响应正文示例。

GET /people/1

Accept: application/json

{
  "id": 1,
  "name": "John Johnson",
  "birth": "1988-01-10"
}

Accept: application/hal+json

{
  "id": 1,
  "name": "John Johnson",
  "birth": "1988-01-10",
  "_links": {
    "self": {
      "href": "http://example.com/people/1"
    },
    "remove": {
      "href": "http://example.com/people/1"
    },
    "update": {
      "href": "http://example.com/people/1"
    },
    "add": {
      "href": "http://example.com/people"
    },
    "list": {
      "href": "http://example.com/people"
    }
  }
}

GET /people?page=0&size=2

Accept: application/json

[
  {
    "id": 1,
    "name": "John Johnson",
    "birth": "1988-01-10"
  },
  {
    "id": 2,
    "name": "Peter Peterson",
    "birth": "1986-11-20"
  }
]

Accept: application/hal+json

{
  "_embedded": [
    {
      "id": 1,
      "name": "John Johnson",
      "birth": "1988-01-10",
      "_links": {
        "self": {
          "href": "http://example.com/people/1"
        },
        "remove": {
          "href": "http://example.com/people/1"
        },
        "update": {
          "href": "http://example.com/people/1"
        },
        "add": {
          "href": "http://example.com/people"
        },
        "list": {
          "href": "http://example.com/people"
        }
      }
    },
    {
      "id": 2,
      "name": "Peter Peterson",
      "birth": "1986-11-20",
      "_links": {
        "self": {
          "href": "http://example.com/people/2"
        },
        "remove": {
          "href": "http://example.com/people/2"
        },
        "update": {
          "href": "http://example.com/people/2"
        },
        "add": {
          "href": "http://example.com/people"
        },
        "list": {
          "href": "http://example.com/people"
        }
      }
    }
  ],
  "_links": {
    "add": {
      "href": "http://example.com/people"
    },
    "list": {
      "href": "http://example.com/people"
    },
    "first": {
      "href": "http://example.com/people?page=0&size=2"
    },
    "last": {
      "href": "http://example.com/people?page=2&size=2"
    },
    "next": {
      "href": "http://example.com/people?page=1&size=2"
    }
  }
}

这两个响应也包含这些标头

不会包含 previous 链接标头(和 hal 链接),因为上一页不存在。

包括/排除 Jakarta REST 类

使用构建时条件

Quarkus 使得可以直接包含或排除 Jakarta REST 资源、提供程序和特性,这归功于与 CDI bean 相同的构建时条件。因此,可以使用配置文件条件 (@io.quarkus.arc.profile.IfBuildProfile@io.quarkus.arc.profile.UnlessBuildProfile) 和/或属性条件 (io.quarkus.arc.properties.IfBuildPropertyio.quarkus.arc.properties.UnlessBuildProperty) 注释 REST Data with Panache 接口,以在构建时指示 Quarkus 应在哪些条件下包含生成的 Jakarta REST 类。

在以下示例中,当且仅当已启用构建配置文件 app1 时,Quarkus 才会包含从 PeopleResource 接口生成的资源。

@IfBuildProfile("app1")
public interface PeopleResource extends PanacheEntityResource<Person, Long> {
}

使用运行时属性

此选项仅在使用 Quarkus REST Quarkus 扩展时可用。

Quarkus 还可以使用 @io.quarkus.resteasy.reactive.server.EndpointDisabled 注释有条件地禁用基于运行时属性值的生成的 Jakarta REST 资源。

在以下示例中,如果在应用程序中将 some.property 配置为 "disable",Quarkus 将在运行时排除从 PeopleResource 接口生成的资源。

@EndpointDisabled(name = "some.property", stringValue = "disable")
public interface PeopleResource extends PanacheEntityResource<Person, Long> {
}

相关内容