Generating Jakarta REST resources with Panache
很多 Web 应用程序都是单调的 CRUD 应用程序,包含非常乏味的 REST API。为了精简此任务,具有 Panache 的 REST 数据扩展可以为你的实体和存储库生成基本的 CRUD 终结点。
虽然此扩展仍然处于试验阶段且提供的功能集有限,但我们希望尽早收到反馈。目前,此扩展支持带 Panache 的 Hibernate ORM 和 MongoDB,且可以生成适用于 application/json
和 application/hal+json
内容的 CRUD 资源。
- Setting up REST Data with Panache
- Generating resources
- Resource customisation
- Adding additional methods to the generated resource
- Securing endpoints
- Query parameters to list entities
- Complex filtering to list entities using @NamedQuery
- Resource Method Before/After Listeners
- Response body examples
- Include/Exclude Jakarta REST classes
Setting up REST Data with Panache
Quarkus 提供了以下扩展来设置 REST Data with Panache。请查看下一兼容性表格,以根据你正在使用的技术来使用正确的扩展:
Extension | Hibernate | RESTEasy |
---|---|---|
|
|
|
|
|
|
|
|
Hibernate ORM
-
将所需的依赖项添加到构建文件中
-
具有 Panache 扩展 Hibernate ORM REST Data(
quarkus-hibernate-orm-rest-data-panache
) -
JDBC 驱动程序扩展(
quarkus-jdbc-postgresql
,quarkus-jdbc-h2
,quarkus-jdbc-mariadb
,…) -
一个 RESTEasy JSON 序列化扩展(该扩展同时支持 Quarkus REST(以前为 RESTEasy Reactive)和 RESTEasy Classic)
-
<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>
<!-- Use this if you are using Quarkus REST -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
<!-- Use this if you are going to use RESTEasy Classic -->
<!--
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
-->
implementation("io.quarkus:quarkus-hibernate-orm-rest-data-panache")
implementation("io.quarkus:quarkus-jdbc-postgresql")
// Use this if you are using Quarkus REST
implementation("io.quarkus:quarkus-rest-jackson")
// Use this if you are going to use RESTEasy Classic
// implementation("io.quarkus:quarkus-resteasy-jackson")
-
如 Hibernate ORM with Panache 指南中所述,实现 Panache 实体和/或存储库。
-
如 Generating resources 部分中所述,定义要生成的接口。
若要查看具有 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-jsonb
,quarkus-rest-jackson
,…)
-
<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>
-
如 Hibernate Reactive with Panache 指南中所述,实现 Panache 实体和/或存储库。
-
如 resource generation 部分中所述,定义要生成的接口。
MongoDB
-
将所需的依赖项添加到构建文件中
-
具有 Panache 扩展的 MongoDB REST Data(
quarkus-mongodb-rest-data-panache
) -
一个 RESTEasy JSON 序列化扩展(
quarkus-rest-jackson
或quarkus-rest-jsonb
)
-
<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>
<!-- Use this if you are going to use RESTEasy Classic -->
<!--
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
-->
implementation("io.quarkus:quarkus-mongodb-rest-data-panache")
// Use this if you are using Quarkus REST
implementation("io.quarkus:quarkus-rest-jackson")
// Use this if you are going to use RESTEasy Classic
// implementation("io.quarkus:quarkus-resteasy-jackson")
-
如 MongoDB with Panache 指南中所述,实现 Panache 实体和/或存储库。
-
如 resource generation 部分中所述,定义要生成的接口。
Generating resources
与 Panache 结合使用的 REST Data 会根据应用程序中可用的接口生成 Jakarta REST 资源。对于每一个您要生成的实体和存储库,请提供一个资源接口。_Do not implement these interfaces and don’t provide custom methods because they will be ignored._但是,您可以覆盖扩展接口的方法以对其进行自定义(请参阅结尾部分)。
PanacheEntityResource
如果您的应用程序有扩展 PanacheEntity
或 PanacheEntityBase
类的实体(例如 Person
),您可以指示与 Panache 结合使用的 REST Data 利用以下接口为其生成 Jakarta REST 资源:
public interface PeopleResource extends PanacheEntityResource<Person, Long> {
}
PanacheRepositoryResource
如果您的应用程序有简单实体(例如 Person
)和实现 PanacheRepository
或 PanacheRepositoryBase
接口的存储库(例如 PersonRepository
),则可以指示与 Panache 结合使用的 REST Data 使用以下接口为其生成 Jakarta REST 资源:
public interface PeopleResource extends PanacheRepositoryResource<PersonRepository, Person, Long> {
}
PanacheMongoEntityResource
如果您的应用程序有一个扩展了 PanacheMongoEntity`或 `PanacheMongoEntityBase`类的实体(例如 `Person
),您可以指示 REST Data 与 Panache 使用以下接口生成其 Jakarta REST 资源:
public interface PeopleResource extends PanacheMongoEntityResource<Person, Long> {
}
PanacheMongoRepositoryResource
如果您的应用程序有一个简单的实体(例如 Person
)和一个实现了 PanacheMongoRepository`或 `PanacheMongoRepositoryBase`接口的存储库(例如 `PersonRepository
),您可以指示 REST Data 与 Panache 使用以下接口生成其 Jakarta REST 资源:
public interface PeopleResource extends PanacheMongoRepositoryResource<PersonRepository, Person, Long> {
}
The generated resource
生成的资源对于实体和存储库来说在功能上是等效的。唯一的区别是正在使用的数据访问模式和数据存储。
如果您已定义了上面提到的 `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 Person get(@PathParam("id") Long id){
Person person = resource.get(id);
if (person == null) {
throw new WebApplicationException(404);
}
return person;
}
@GET
@Produces("application/json")
public Response 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 long count() {
return resource.count();
}
@Transactional
@POST
@Consumes("application/json")
@Produces("application/json")
public Response add(Person personToSave) {
Person person = resource.add(person);
// ... build a new location URL and return 201 response with an entity
}
@Transactional
@PUT
@Path("{id}")
@Consumes("application/json")
@Produces("application/json")
public Response update(@PathParam("id") Long id, Person personToSave) {
if (resource.get(id) == null) {
Person person = resource.update(id, personToSave);
return Response.status(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 void delete(@PathParam("id") Long id) {
if (!resource.delete(id)) {
throw new WebApplicationException(404);
}
}
}
Resource customisation
REST Data 与 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);
}
Available options
@ResourceProperties
-
exposed
- 资源是否可以公开。一个全局资源属性,可以针对每种方法进行覆盖。默认值是true
。 -
path
- 资源基本路径。默认路径为小写连字符分隔的资源名称,没有后缀resource`或 `controller
。 -
rolesAllowed
- 允许访问资源的安全角色列表。它需要 Quarkus 安全扩展来呈现,否则它将被忽略。默认值为空。 -
paged
- 集合响应是否应分页。如果存在,则响应头将包含第一页、最后一页、上一页和下一页 URI。请求页面索引和大小取自page`和 `size`查询参数,它们分别默认为 `0`和 `20
。默认值是true
。 -
hal
- 除了标准application/json`响应之外,还会生成其他方法,这些方法可以通过 `Accept`头进行请求时返回 `application/hal+json`响应。默认值是 `false
。 -
halCollectionName
- 在生成 hal 集合响应时应使用的名称。默认名称为小写连字符分隔的资源名称,没有后缀resource`或 `controller
。
@MethodProperties
-
exposed
- 设置为false`时,不公开特定 HTTP 动词。默认值是 `true
。 -
path
- 操作路径(这是附加到资源基本路径的)。默认值为空字符串。 -
rolesAllowed
- 允许访问此操作的安全角色列表。它需要 Quarkus 安全扩展来呈现,否则它将被忽略。默认值为空。
Adding additional methods to the generated resource
您可以通过向资源接口添加这些方法,用 REST Data 和 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();
}
}
该方法将与使用 `http://localhost:8080/people/name/Johan`生成的方法一起公开。
Securing endpoints
REST Data with Panache 将使用 jakarta.annotation.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
,以列出允许访问资源或操作的安全角色。
Query parameters to list entities
REST Data with Panache 支持以下查询参数来获取实体列表:
-
page
- 列表操作应返回的页码。它仅适用于分页资源,并且是一个从 0 开始的数字。默认值为 0。 -
size
- 列表操作应返回的页面大小。它仅适用于分页资源,并且是一个从 1 开始的数字。默认值为 20。 -
sort
- 由逗号分隔的字段列表,该列表应用于对列表操作的结果进行排序。除非字段以-
为前缀,否则字段将按升序排列。例如,?sort=name,-age
将按 name 升序、按 age 降序对结果进行排序。 -
namedQuery
- 应使用注释@NamedQuery
在实体级别配置的命名查询。
例如,如果您想在第一页中获取两个 People
实体,则应调用 http://localhost:8080/people?page=0&size=2
,响应应如下所示:
[
{
"id": 1,
"name": "John Johnson",
"birth": "1988-01-10"
},
{
"id": 2,
"name": "Peter Peterson",
"birth": "1986-11-20"
}
]
此外,您还可以通过添加查询参数(包括字段名称和值)按实体字段进行筛选,例如,调用 http://localhost:8080/people?name=Peter Peterson
将返回:
[
{
"id": 2,
"name": "Peter Peterson",
"birth": "1986-11-20"
}
]
仅支持按 String、Boolean、Character、Double、Float、Integer、Long、Short、Byte 和基本类型按字段进行筛选。
Complex filtering to list entities using @NamedQuery
列出实体时,您可以指定一个命名查询以进行筛选。例如,在实体中具有以下命名查询:
@Entity
@NamedQuery(name = "Person.containsInName", query = "from Person where name like CONCAT('%', CONCAT(:name, '%'))")
public class Person extends PanacheEntity {
String name;
}
在此示例中,我们添加了一个命名查询,以列出 name
字段中包含某些文本的所有人员。
接下来,我们可以在使用生成的资源列出实体时设置一个查询参数 namedQuery
,其中包括我们要使用的命名查询的名称,例如,调用 http://localhost:8080/people?namedQuery=Person.containsInName&name=ter
将返回所有姓名包含文本 "ter" 的人。
有关命名查询的工作原理的更多信息,请参阅 Hibernate ORM 指南或 Hibernate Reactive 指南。
Resource Method Before/After Listeners
REST Data with Panache 支持订阅以下资源方法钩子:
-
Before/After add resource
-
Before/After update resource
-
Before/After delete resource
要注册资源方法侦听器,您需要提供一个实现接口 RestDataResourceMethodListener
的 bean,例如:
@ApplicationScoped
public class PeopleRestDataResourceMethodListener implements RestDataResourceMethodListener<Person> {
@Override
public void onBeforeAdd(Person person) {
System.out.println("Before Save Person: " + person.name);
}
}
Response body examples
如上所述,REST Data with Panache 支持 application/json
和 application/hal+json
响应内容类型。以下是如何为 get
和 list
操作响应主体的示例,假设数据库中有五个 Person
记录。
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"
}
}
}
这两个响应都将包含以下标头:
-
链接:< [role="bare"][role="bare"]http://example.com/people?page=0&size=2 >:: rel="first"
-
链接:< [role="bare"][role="bare"]http://example.com/people?page=2&size=2 >:: rel="last"
-
链接:< [role="bare"][role="bare"]http://example.com/people?page=1&size=2 >:: rel="next"
将不会包含 previous
链接标头(以及 hal 链接),因为不存在前一页。
Include/Exclude Jakarta REST classes
Using Build time conditions
得益于在构建时的条件,Quarkus 可以直接启用或禁用 Jakarta REST 资源、提供商和特性,与 CDI Bean 相同。因此,可以使用配置文件条件 (@io.quarkus.arc.profile.IfBuildProfile
或 @io.quarkus.arc.profile.UnlessBuildProfile
) 和/或属性条件 (io.quarkus.arc.properties.IfBuildProperty
或 io.quarkus.arc.properties.UnlessBuildProperty
) 对 REST 数据和 Panache 接口进行注释,以在构建时向 Quarkus 指示生成的 Jakarta REST 类应在哪些条件下包含。
在以下示例中,Quarkus 将仅当启用了构建配置文件 app1
时才包含从 PeopleResource
接口生成的资源。
@IfBuildProfile("app1")
public interface PeopleResource extends PanacheEntityResource<Person, Long> {
}
Using a runtime property
仅在使用 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> {
}