The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

PanacheでJakarta RESTリソースを生成する

多くのWebアプリケーションは、REST APIを使った単調なCRUDアプリケーションで、書くのが面倒です。このタスクを合理化するために、REST Data with Panache エクステンションは、エンティティーやリポジトリの基本的なCRUDエンドポイントを生成することができます。

このエクステンションはまだ実験的なものであり、限られた機能セットしか提供していませんが、早期にフィードバックが得られることを期待しています。現在のところ、このエクステンションは Hibernate ORM と MongoDB with Panache をサポートしており、 application/jsonapplication/hal+json のコンテンツで動作する CRUD リソースを生成することができます。

REST Data with Panacheのセットアップ

Quarkusは、REST Data with Panacheをセットアップするために、以下のエクステンションを提供しています。次の互換性表を参照して、使用している技術に応じて適切なものを使用してください:

Table 1. 互換性表
エクステンション Hibernate ORM RESTEasy

quarkus-hibernate-orm-rest-data-panache

ORM

Classic and Reactive

quarkus-hibernate-reactive-rest-data-panache

Reactive

Reactive

quarkus-mongodb-rest-data-panache

ORM

Classic and Reactive

Hibernate ORM

  • 必要な依存関係を pom.xml に追加します

    • Hibernate ORM REST Data with Panache エクステンション ( quarkus-hibernate-orm-rest-data-panache )

    • JDBC ドライバーエクステンション ( quarkus-jdbc-postgresql , quarkus-jdbc-h2 , quarkus-jdbc-mariadb , …​)

    • RESTEasy JSONシリアライゼーションエクステンションの1つ(このエクステンションは、Quarkus REST(旧RESTEasy Reactive)とRESTEasy Classicの両方をサポートしています。)

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>

<!-- 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>
-->
build.gradle
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 エンティティおよび/またはリポジトリを実装します。

  • リソースの生成 のセクションで説明したように、生成のためのインターフェイスを定義します。

Panacheを使用したHibernate ORM REST Dataの動作を確認するには、 hibernate-orm-rest-data-panache-quickstart クイックスタートをご覧ください。

Hibernate Reactive

  • 必要な依存関係を pom.xml に追加します

    • Hibernate ORM REST Data with Panache エクステンション ( quarkus-hibernate-orm-rest-data-panache )

    • Vert.x リアクティブデータベースドライバエクステンション ( quarkus-reactive-pg-client, quarkus-reactive-mysql-client, …​)

    • Quarkus RESTシリアライゼーションエクステンションの1つ ( 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エンティティやリポジトリを実装します。

  • リソース生成 のセクションで説明したように、生成のためのインターフェイスを定義します。

MongoDB

  • 必要な依存関係を pom.xml に追加します

    • MongoDB REST Data with Panache エクステンション ( quarkus-mongodb-rest-data-panache )

    • RESTEasy JSONシリアライゼーションエクステンションの1つ ( quarkus-rest-jackson または quarkus-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>

<!-- Use this if you are going to use RESTEasy Classic -->
<!--
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-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")

// Use this if you are going to use RESTEasy Classic
// implementation("io.quarkus:quarkus-resteasy-jackson")
  • MongoDB with Panache ガイドで説明されているように、Panacheエンティティやリポジトリを実装します。

  • リソース生成 のセクションで説明したように、生成のためのインターフェイスを定義します。

リソースの生成

REST Data with Panacheは、アプリケーションに存在するインターフェースに基づいて、Jakarta RESTリソースを生成します。生成したい各エンティティとリポジトリに対して、リソース・インターフェースを提供します。 これらのインターフェイスは実装せず、カスタムメソッドを提供しないようにしてください。 ただし、カスタマイズするために、拡張インターフェースからメソッドをオーバーライドすることは可能です(末尾のセクションを参照)。

PanacheEntityResource

アプリケーションに PanacheEntity または PanacheEntityBase クラスを拡張したエンティティ (例: Person ) がある場合、REST Data with Panache に指示して、次のインターフェイスで Jakarta REST リソースを生成させることができます:

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

PanacheRepositoryResource

アプリケーションにシンプルなエンティティ(例: Person )と、 PanacheRepository または PanacheRepositoryBase インターフェースを実装するリポジトリ(例: PersonRepository )がある場合、REST Data with Panache に指示して、次のインターフェイスで Jakarta REST リソースを生成させることができます:

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

PanacheMongoEntityResource

アプリケーションに PanacheMongoEntity または PanacheMongoEntityBase クラスを拡張したエンティティ (例: Person ) がある場合、REST Data with Panache に指示して、次のインターフェイスで Jakarta REST リソースを生成させることができます:

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

PanacheMongoRepositoryResource

アプリケーションにシンプルなエンティティ(例: Person )と、 PanacheMongoRepository または PanacheMongoRepositoryBase インターフェースを実装するリポジトリ(例: PersonRepository )がある場合、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 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);
        }
    }
}

リソースのカスタマイズ

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 - リソースのベースパス。デフォルトのパスは、 resource または controller のサフィックスを含まないハイフン付きの小文字のリソース名です。

  • rolesAllowed - リソースへのアクセスが許可されているセキュリティロールのリストです。Quarkus Securityエクステンションが存在する必要があり、存在しない場合は無視されます。デフォルトは空です。

  • paged - コレクションのレスポンスをページングするかどうか。最初、最後、前、次のページの URI が存在する場合は、レスポンスヘッダに含まれます。リクエストページのインデックスとサイズは、 pagesize のクエリパラメーターから取得され、それぞれのデフォルトは 020 です。デフォルトは true です。

  • hal - 標準の application/json レスポンスに加えて、 Accept ヘッダでリクエストされた場合に application/hal+json レスポンスを返す追加のメソッドを生成します。デフォルトは false です。

  • halCollectionName - HAL コレクションレスポンスを生成する際に使用されるべき名前です。デフォルトの名前は resource または controller のサフィックスなしのハイフン付き小文字のリソース名です。

@MethodProperties

  • exposed - false に設定されている場合、特定の HTTP Verb を公開しません。デフォルトは 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();
    }
}

そして、このメソッドは、生成されたメソッドとともに、 http://localhost: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`アノテーションを用いてエンティティレベルで設定されるべき名前付きクエリです。

たとえば、最初のページで2つの 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およびプリミティブ型にのみ対応しています。

@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 ガイドにアクセスしてください。

リソースメソッド Before/Afterリスナー

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);
    }
}

レスポンスボディの例

前述の通り、REST Data with Panache は application/jsonapplication/hal+json のレスポンス コンテンツ タイプをサポートしています。 Person ここでは、データベース内に get レコードが 5 つあると仮定して、 と list 操作を行った場合のレスポンスボディがどのようになるか、いくつかの例を示します。

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では、CDI Beanの場合と同様に、ビルド時の条件によって、Jakarta RESTリソース、プロバイダ、フィーチャーを直接追加または除外できます。 したがって、REST Data with Panacheインターフェイスにプロファイル条件( @io.quarkus.arc.profile.IfBuildProfile または @io.quarkus.arc.profile.UnlessBuildProfile )やプロパティ条件( io.quarkus.arc.properties.IfBuildProperty または io.quarkus.arc.properties.UnlessBuildProperty )をアノテーションすることで、生成されるJakarta RESTクラスをどの条件で含めるかを、ビルド時にQuarkusに指示することができます。

以下の例では、ビルドプロファイル 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> {
}

関連コンテンツ