The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.
このページを編集

Simplified Hibernate with Quarkus Data Hibernate

experimental

Quarkus Data Hibernate is the perfect solution if you want to get started using Hibernate in Quarkus.

この技術は、experimentalと考えられています。

experimental モードでは、アイデアを成熟させるために早期のフィードバックが求められます。ソリューションが成熟するまでの間、プラットフォームの安定性や長期的な存在を保証するものではありません。フィードバックは メーリングリストGitHubの課題管理 で受け付けています。

とりうるステータスの完全なリストについては、 FAQの項目 を参照してください。

このエクステンションは現在実験段階にあるため、API が変更される可能性があります。特に、クラス名やパッケージ名、さらにはモジュール名も変更される可能性があります。現在、パブリックプレビュー化を進めており、皆様からのフィードバックを求めています。フィードバックは Zulip または GitHub issues までお寄せください。

ウォークスルー

Let’s take a progressive approach to learning how to use Quarkus Data Hibernate, and start with a simple entity.

このガイドでは、 Hibernate またはその基盤となる Jakarta Persistence 仕様 のどちらかの具体的な使用法については詳しく説明しません。なぜなら、どちらもすでに優れたドキュメントがあり、より深い知識を得るために活用できるからです。

エクステンションのインポートと設定

pom.xml
<!-- Import the Quarkus Data Hibernate extension -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-data-hibernate</artifactId>
</dependency>
<!-- Pick your database driver -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
build.gradle
// Import the Quarkus Data Hibernate extension
implementation("io.quarkus:quarkus-data-hibernate")

// Pick your database driver
implementation("io.quarkus:quarkus-jdbc-postgresql")

また、必須の Hibernate プロセッサーを設定する必要があります。

pom.xml
<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <!-- This setting is required for the annotation processor dependencies to be managed by Quarkus.
             More information is available in Maven compiler plugin documentation:
             https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#annotationProcessorPathsUseDepMgmt -->
        <annotationProcessorPathsUseDepMgmt>true</annotationProcessorPathsUseDepMgmt>
        <annotationProcessorPaths>
            <path>
                <groupId>org.hibernate.orm</groupId>
                <artifactId>hibernate-processor</artifactId>
                <!-- Note, no artifact version is required, it's managed by Quarkus.  -->
            </path>
            <!-- other processors that may be required by your app -->
        </annotationProcessorPaths>
        <!-- Other compiler plugin configuration options -->
    </configuration>
</plugin>
build.gradle
// Enforce the version management of your annotation processor dependencies,
// so that there's no need to define an explicit version of the hibernate-processor
annotationProcessor enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")
annotationProcessor 'org.hibernate.orm:hibernate-processor'
サポートされている JDBC ドライバー のいずれかを使用できます。

Add the relevant configuration properties in application.properties.

application.properties
quarkus.datasource.db-kind = postgresql (1)

%prod.quarkus.datasource.username = hibernate
%prod.quarkus.datasource.password = hibernate
%prod.quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:5432/hibernate_db
%prod.quarkus.hibernate-orm.schema-management.strategy=create (2)
1 Configure the datasource for production, relying on Dev Services for connection information in tests / dev mode.
2 Configure Hibernate ORM to create the schema on startup in production, which is useful for experimentation, but rely on convenient defaults in tests / dev mode.

最初のエンティティ

What this guide will focus on is the basics of how to use Quarkus Data Hibernate, so let’s start with how to create an entity:

import io.quarkus.hibernate.panache.PanacheEntity;
import jakarta.persistence.Entity;

import java.time.LocalDate;

@Entity
public class Cat extends PanacheEntity {
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }
}

Java クラスをデータベーステーブルにマッピングするには、次の手順を実行します。

  • クラスを作成する

  • @Entity でアノテーションを付けます。

  • PanacheEntity を継承させる

  • フィールドを public にする

これで完了です。エンティティのインスタンス作成、データベースへの永続化を開始できます。その後は、フィールドへのすべての変更が自動的にデータベースに送信され (明示的な update 命令は不要)、データベースから削除することもできます。

import jakarta.transaction.Transactional;

import java.time.LocalDate;
import java.util.List;

public class Code {

    @Transactional (1)
    public void method() {
        Cat cat = new Cat();
        cat.name = "Lucky";
        cat.birth = LocalDate.of(2015, 01, 12);
        cat.breed = Cat.Breed.CUTE;

        // Persist the cat
        cat.persist();

        // Make a change, no need to update it
        cat.name = "Luckynou";

        // Delete our cat
        cat.delete();
    }
}
1 これは、トランザクション内で操作を実行するために、データベースと対話するすべてのメソッドで必要です。

最初のリポジトリ

エンティティの作成、更新、削除の方法がわかったので、データベースをクエリしてエンティティを検索する方法や、削除クエリを実行する方法を見てみましょう。

クエリ操作はエンティティのインスタンスに属さないため、これらの操作は Repository と呼ばれる別の型に配置します。また、それらのクエリは操作対象のエンティティと密接に結びついているため、エンティティ内にネストされたインターフェースに配置することをお勧めします。

import io.quarkus.hibernate.panache.PanacheEntity;
import io.quarkus.hibernate.panache.PanacheRepository;
import jakarta.persistence.Entity;
import org.hibernate.annotations.processing.Find;
import org.hibernate.annotations.processing.HQL;

import java.time.LocalDate;
import java.util.List;

@Entity
public class Cat extends PanacheEntity {
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends PanacheRepository<Cat> {
        @Find
        Cat findByName(String name);

        @HQL("where breed = CUTE")
        List<Cat> findCute();

        @HQL("delete from Cat where name = :name")
        long deleteByName(String name);

        @HQL("delete from Cat where breed = HAIRLESS")
        long deleteHairless();
    }
}

A Quarkus Data Hibernate repository is:

  • エンティティ内にネストされたインターフェース (ただし、好みに応じてトップレベルインターフェースでも構いません)

  • PanacheRepository インターフェースを拡張し、問題のエンティティを型パラメーターとして持つ

  • そして、@Find または @HQL、あるいは @SQL (ネイティブクエリ用) のいずれかでアノテーションが付けられたクエリメソッド、またはクエリの実装を含む default メソッドが含まれる

@Find メソッドを使用して、メソッドのパラメーターでクエリを構築することにより、単一のインスタンスまたはエンティティのコレクションを検索できます。Hibernate ドキュメントには、 これに関する必要なすべての情報 があります。

いくつかの一般的な例
@Find
public List<Cat> findAllCats(); (1)

@Find
public long countAllCats(); (2)

@Find
public Cat findCatByNameAndBreed(String name, Breed breed); (3)
1 ファインダーメソッドはエンティティのコレクションを返す場合があります
2 または COUNT クエリを生成する
3 または単一のエンティティ、およびクエリ対象のエンティティすべてに一致する必要がある任意の数のパラメーター

あるいは、HQL および SQL クエリをサポートする @HQL または @SQL クエリを使用できます。繰り返しになりますが、Hibernate ドキュメントには 必要なすべての情報 があります。

いくつかの一般的な例
@HQL("from Cat")
public List<Cat> findAllCats(); (1)

@HQL("select min(birth) from Cat")
public LocalDate oldestCat(); (2)

@HQL("where name = :name and breed = :breed")
public Cat findCatByNameAndBreed(String name, Breed breed); (3)
1 クエリメソッドはエンティティのコレクションを返す場合があります
2 または単一カラムのプロジェクション、さらに Object[] および List<Object[]> 型を使用した複数カラムのプロジェクションも可能
3 クエリはもちろんメソッドのパラメーターを参照できます。

生成されたファインダーメソッドとクエリメソッドの利点は、ビルド時 に型チェックされることです。これにより、エンティティ名、そのフィールド、HQL/SQL 構文、またはパラメーター名に誤植がないことが検証され、保証されます。

リポジトリの使用

リポジトリを使用するには、使用したい場所にインジェクトするだけです。

import jakarta.inject.Inject;
import jakarta.transaction.Transactional;

import java.time.LocalDate;
import java.util.List;

public class Code {

    @Inject
    Cat.Repo repo;

    @Transactional
    public void method() {
        Cat cat = new Cat();
        cat.name = "Lucky";
        cat.birth = LocalDate.of(2015, 01, 12);
        cat.breed = Cat.Breed.CUTE;

        // Persist the cat
        cat.persist();

        // Make a change, no need to update it
        cat.name = "Luckynou";

        // Find our cat
        cat = repo.findByName("Luckynou");

        // Find cute cats
        List<Cat> cuteCats = repo.findCute();

        // Delete our cat
        cat.delete();

        // Delete queries
        repo.deleteByName("Lucky");
        repo.deleteHairless();
    }
}

さらに、生成されたエンティティの 生成されたメタモデル にある便利なショートカット生成静的メソッドを使用してリポジトリにアクセスすることもできます。これは、操作の発見に非常に役立ち (単に Cat_.repo(). と入力してすべてのメソッドを確認する)、メソッドの外部でインジェクトされたフィールドを追加する回り道を避けるためにも便利です。この起動コードのように、すべての猫を削除する例があります (本番環境では行わないでください!!):

import io.quarkus.runtime.Startup;
import jakarta.transaction.Transactional;

public class OnStart {
    @Startup
    @Transactional
    public void startupMethod() {
        Cat_.repo().deleteAll();
    }
}

エンティティのネストされたインターフェースとして定義するすべてのリポジトリは、生成されたメタモデルクラスの、リポジトリと同じ名前のアクセサーメソッドの下で利用できます。これはリポジトリ型をインジェクトすることと厳密に同等であり、実際には内部で CDI を使用してリポジトリを検索します。

PanacheRepository スーパークラス

Hibernate ORM と Hibernate Reactive with Panache の以前のバージョンと同様に、PanacheRepository 型には、エンティティを操作するために必要なほとんどの操作がすぐに利用できるようになっています。例えば次のとおりです。

import io.quarkus.hibernate.panache.blocking.PanacheBlockingQuery;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import org.hibernate.Session;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

public class Code {

    @Inject
    Cat.Repo repo;

    @Transactional
    public void repositoryOperations(Cat cat) {
        // entity operations
        repo.persist(cat);
        repo.delete(cat);
        boolean isPersistent = repo.isPersistent(cat);
        // operations on all entities
        long count = repo.count();
        long deleted = repo.deleteAll();
        List<Cat> allCats = repo.listAll();
        repo.streamAll().forEach(kitty -> kitty.name = kitty.name.toUpperCase());
        PanacheBlockingQuery<Cat> catQuery = repo.findAll();
        // operations on the Hibernate session
        repo.flush();
        Session session = repo.getSession();
        // ID-related operations
        boolean wasDeleted = repo.deleteById(cat.id);
        Cat foundCat = repo.findById(cat.id);
        Optional<Cat> optionalCat = repo.findByIdOptional(cat.id);
    }
}

タイプセーフでないクエリ

これまでに示したように、生成されたファインダーメソッドとクエリメソッドを使用すると、すべてがビルド時 に検証されますが、タイプセーフでないクエリを作成したい場合は、PanacheRepository が提供するメソッドをいつでも使用できます。

import io.quarkus.hibernate.panache.blocking.PanacheBlockingQuery;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import org.hibernate.Session;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

public class Code {

    @Inject
    Cat.Repo repo;

    @Transactional
    public void nonTypeSafeQueries() {
        // Find a cat by name
        Cat cat = repo.find(Cat_.NAME, "Lucky").singleResult();
        // All cute cats
        List<Cat> cuteCats = repo.list(Cat_.BREED, Cat.Breed.CUTE);
        // Cats with no known birth date
        repo.stream(Cat_.BIRTH+" is null").forEach(kitty -> System.err.println(kitty));
        // Get rid of non-cute cats
        long deleted = repo.delete(Cat_.BREED, Cat.Breed.HAIRLESS);
        // Count ugly cats with no name
        repo.count("breed = ?1 and name is null", Cat.Breed.HAIRLESS);
        // Make every cat cute
        repo.update("breed = CUTE");
    }
}

エンティティ識別子について

上記の例では、PanacheEntity 型を拡張し、データベース識別子を定義しませんでした。これは、PanacheEntity がデフォルトで生成されるデータベース識別子を備えているため、それについて心配する必要がないためです。これは、Long 型の生成されたデータベース識別子を提供する WithId.AutoLong クラスを拡張することで実現されます。

String 識別子には WithId.AutoString を、UUID 識別子には WithId.AutoUUID を拡張することもできますし、WithId<IdType> を拡張して次の形式の属性を自動的に取得することもできます。

@Id
public IdType id;
The identifier will not be automatically generated in this case, so you will have to set its value manually on creation.

もちろん、独自のデータベース識別子を明示的に指定し、エンティティで PanacheEntity.Managed インターフェースを実装し、リポジトリには PanacheRepository.Managed インターフェースを使用して、データベース識別子型を指定することもできます。

import io.quarkus.hibernate.panache.PanacheEntity;
import io.quarkus.hibernate.panache.PanacheRepository;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import org.hibernate.annotations.processing.Find;
import org.hibernate.annotations.processing.HQL;

import java.time.LocalDate;
import java.util.List;

@Entity
public class CatWithId implements PanacheEntity.Managed {
    @Id
    public String id;
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends PanacheRepository.Managed<CatWithId, String> {
        @Find
        CatWithId findByName(String name);

        @HQL("where breed = CUTE")
        List<CatWithId> findCute();

        @HQL("delete from Cat where name = :name")
        long deleteByName(String name);

        @HQL("delete from Cat where breed = HAIRLESS")
        long deleteHairless();
    }
}

使用できるエンティティのスーパークラスとその提供する ID 型のリストを次に示しますが、独自の ID を定義する場合はこれらの型のいずれかを拡張する必要がないことに注意してください。

ID 型 スーパータイプ ショートカットタイプ

Long

WithId.AutoLong

PanacheEntity

UUID

WithId.AutoUUID

String

WithId.String

T

WithId<Id>

ステートレスセッションの使用

デフォルトでは、PanacheEntity のサブタイプは Hibernate ORM によって管理され、エンティティーに対するすべての変更は、明示的な update 操作を必要とせずに自動的にデータベースに送信されます。

一方で、すべての update 操作を明示的にしたい場合は、Hibernate ORM が ステートレスセッション と呼ぶものを使用する必要があります。

この場合、WithId.AutoLong (またはその他の ID クラス、あるいは独自の ID) を拡張し、PanacheEntity.Stateless および PanacheRepository.Stateless インターフェースを実装する必要があります。

import io.quarkus.hibernate.panache.PanacheEntity;
import io.quarkus.hibernate.panache.PanacheRepository;
import io.quarkus.hibernate.panache.WithId;
import jakarta.persistence.Entity;
import org.hibernate.annotations.processing.Find;
import org.hibernate.annotations.processing.HQL;

import java.time.LocalDate;
import java.util.List;

@Entity
public class Cat extends WithId.AutoLong implements PanacheEntity.Stateless {
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends PanacheRepository.Stateless<Cat, Long> {
        @Find
        Cat findByName(String name);

        @HQL("where breed = CUTE")
        List<Cat> findCute();

        @HQL("delete from Cat where name = :name")
        long deleteByName(String name);

        @HQL("delete from Cat where breed = HAIRLESS")
        long deleteHairless();
    }
}

ご覧のとおり、エンティティー定義は2つのインターフェースを除けばまったく同じです。しかし、これでエンティティーは 管理 されなくなるため、すべての更新操作を明示的に行う必要があります。

import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import org.hibernate.StatelessSession;

import java.time.LocalDate;
import java.util.List;

public class Code {

    @Inject
    Cat.Repo repo;

    @Transactional
    public void method() {
        Cat cat = new Cat();
        cat.name = "Lucky";
        cat.birth = LocalDate.of(2015, 01, 12);
        cat.breed = Cat.Breed.CUTE;

        // Persist the cat
        cat.insert();

        // Make a change, we need to update it
        cat.name = "Luckynou";
        cat.update();

        // Find our cat
        cat = repo.findByName("Luckynou");

        // Find cute cats
        List<Cat> cuteCats = repo.findCute();

        // Delete our cat
        cat.delete();

        // Delete queries
        repo.deleteByName("Lucky");
        repo.deleteHairless();
    }
}

ご覧のとおり、管理対象エンティティーとの唯一の違いは次のとおりです。

  • エンティティーインスタンスのフィールドに対する変更は、エンティティーまたはそのリポジトリーのいずれかで update() を呼び出すことで、明示的にデータベースにプッシュする必要があります。

  • データベースにエンティティーを挿入するには、persist() の代わりに insert() を呼び出す必要があります。

  • セッションの型は Session ではなく StatelessSession になります。

しかし、ほとんどそれだけです。特にクエリーやエンティティーの取得方法については、それ以外のすべては同じままです。

ステートレスセッションを使用している場合、Jakarta Data 型のリポジトリーも使用できます。

リアクティブにしてみよう

リアクティブアプリケーションでエンティティーを使い始めたいですか?まずは pom.xml に Hibernate Reactive とデータベースのデータソースをインポートすることから始めましょう。

pom.xml
<!-- Enable Hibernate Reactive support -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-hibernate-reactive</artifactId>
</dependency>
<!-- Pick your database reactive driver -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>
<!-- FIXME: this will not be required in the future -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-hibernate-reactive-panache-common</artifactId>
</dependency>
build.gradle
// Enable Hibernate Reactive support
implementation("io.quarkus:quarkus-hibernate-reactive")
// Pick your database reactive driver
implementation("io.quarkus:quarkus-reactive-pg-client")
// FIXME: this will not be required in the future
implementation("io.quarkus:quarkus-hibernate-reactive-panache-common")
サポートされているリアクティブドライバー のいずれかを使用できます。

application.properties でリアクティブデータソースを設定してください (ただし、開発モードでは必須ではありません。設定しない場合、dev services が提供されます)。

quarkus.datasource.username = quarkus_test
quarkus.datasource.password = quarkus_test
quarkus.datasource.reactive.url = vertx-reactive:postgresql://localhost/quarkus_test (1)

さて、コードでは、通常の管理対象セッションエンティティーとの唯一の違いは次のとおりです。

  • エンティティーが PanacheEntity.Reactive を実装します。

  • リポジトリーが PanacheRepository.Reactive を拡張します。

  • すべての操作は、T の代わりに Uni<T> を返します。これは標準的な Mutiny リアクティブ型です。

import io.quarkus.hibernate.panache.PanacheEntity;
import io.quarkus.hibernate.panache.PanacheRepository;
import io.quarkus.hibernate.panache.WithId;
import io.smallrye.mutiny.Uni;
import jakarta.persistence.Entity;
import org.hibernate.annotations.processing.Find;
import org.hibernate.annotations.processing.HQL;

import java.time.LocalDate;
import java.util.List;

@Entity
public class Cat extends WithId.AutoLong implements PanacheEntity.Reactive {
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends PanacheRepository.Reactive<Cat, Long> {
        @Find
        Uni<Cat> findByName(String name);

        @HQL("where breed = CUTE")
        Uni<List<Cat>> findCute();

        @HQL("delete from Cat where name = :name")
        Uni<Integer> deleteByName(String name);

        @HQL("delete from Cat where breed = HAIRLESS")
        Uni<Integer> deleteHairless();
    }
}

そして、エンティティーまたはそのリポジトリーを使用したい場合、@Transactional の代わりに @WithTransaction を使用する以外は、Mutiny を使用するリアクティブコードで通常行うように操作を構成する必要がありますが、それ以外は操作はまったく同じです。

import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

public class Code {

    @Inject
    Cat.Repo repo;

    @WithTransaction
    public Uni<Void> method() {
        Cat cat = new Cat();
        cat.name = "Lucky";
        cat.birth = LocalDate.of(2015, 01, 12);
        cat.breed = Cat.Breed.CUTE;

        // Persist the cat
        return cat.persist()
                // Make a change, no need to update it
                .invoke(() -> cat.name = "Luckynou")
                // Find our cat
                .chain(() -> repo.findByName("Luckynou"))
                // Find cute cats
                .chain((Cat foundCat) -> repo.findCute())
                // Delete our cat
                .chain((List<Cat> cuteCats) -> cat.delete())
                // Delete queries
                .chain(v -> repo.deleteByName("Lucky"))
                .chain((Integer deletedCount) -> repo.deleteHairless())
                .replaceWithVoid();
    }
}

リアクティブかつステートレス

エンティティーの変更を手動で管理したい場合は、エンティティーに PanacheEntity.Reactive.Stateless を、リポジトリーに PanacheRepository.Reactive.Stateless を使用することで、ステートレスセッションを使用できます。

import io.quarkus.hibernate.panache.PanacheEntity;
import io.quarkus.hibernate.panache.PanacheRepository;
import io.quarkus.hibernate.panache.WithId;
import io.smallrye.mutiny.Uni;
import jakarta.persistence.Entity;
import org.hibernate.annotations.processing.Find;
import org.hibernate.annotations.processing.HQL;

import java.time.LocalDate;
import java.util.List;

@Entity
public class Cat extends WithId.AutoLong implements PanacheEntity.Reactive.Stateless {
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends PanacheRepository.Reactive.Stateless<Cat, Long> {
        @Find
        Uni<Cat> findByName(String name);

        @HQL("where breed = CUTE")
        Uni<List<Cat>> findCute();

        @HQL("delete from Cat where name = :name")
        Uni<Integer> deleteByName(String name);

        @HQL("delete from Cat where breed = HAIRLESS")
        Uni<Integer> deleteHairless();
    }
}

そして、使用するコードについて言えば、唯一の変更点は、update() を手動で呼び出す標準的な使用方法と、persist() の代わりに insert() を使用することです。

import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;

import java.time.LocalDate;
import java.util.List;

public class Code {

    @Inject
    Cat.Repo repo;

    @WithTransaction
    public Uni<Void> method() {
        Cat cat = new Cat();
        cat.name = "Lucky";
        cat.birth = LocalDate.of(2015, 01, 12);
        cat.breed = Cat.Breed.CUTE;

        // Persist the cat
        return cat.insert()
                // Make a change, we need to update it
                .chain(() -> {
                    cat.name = "Luckynou";
                    return cat.update();
                })
                // Find our cat
                .chain(() -> repo.findByName("Luckynou"))
                // Find cute cats
                .chain((Cat foundCat) -> repo.findCute())
                // Delete our cat
                .chain((List<Cat> cuteCats) -> cat.delete())
                // Delete queries
                .chain(v -> repo.deleteByName("Lucky"))
                .chain((Integer deletedCount) -> repo.deleteHairless())
                .replaceWithVoid();
    }
}
ステートレスセッションを使用している場合、Jakarta Data 型のリポジトリーも使用できます。

ブロッキング、リアクティブ、管理、ステートレスなコードの組み合わせ

エンティティーをブロッキング管理セッションコードと、リアクティブステートレスコードの両方で使用したいとしましょう。これは、エンティティーのどのスーパータイプを選択しても可能です。ほとんどのユースケースを表すスーパータイプを選択すべきですが、どれを選んだとしても、エンティティーで .statelessReactive() メソッドを使用することで、常に代替操作を取得できます。

import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
import io.smallrye.mutiny.Uni;
import jakarta.transaction.Transactional;

import java.time.LocalDate;

public class Code {

    @Transactional
    public void blockingManagedMethod() {
        Cat cat = new Cat();
        cat.name = "Lucky";
        cat.birth = LocalDate.of(2015, 01, 12);
        cat.breed = Cat.Breed.CUTE;

        // Persist the cat
        cat.persist();

        // Make a change, no need to update it
        cat.name = "Luckynou";
    }

    @WithTransaction
    public Uni<Void> reactiveStatelessMethod(Long catId) {
        Cat cat = new Cat();
        cat.name = "Lucky";
        cat.birth = LocalDate.of(2015, 01, 12);
        cat.breed = Cat.Breed.CUTE;

        // Insert the cat
        return cat.statelessReactive().insert()
                .chain(() -> {
                    // Make a change, we need to update it
                    cat.name = "Luckynou";
                    return cat.statelessReactive().update();
                });

    }
}

代替のエンティティー操作はすべて、これらのメソッドから利用できます。

セッション型 エンティティーアクセサー 同等のエンティティー型

Session

.managedBlocking()

PanacheEntity.Managed

StatelessSession

.statelessBlocking()

PanacheEntity.Stateless

Mutiny.Session

.managedReactive()

PanacheEntity.Reactive

Mutiny.StatelessSession

.statelessReactive()

PanacheEntity.Reactive.Stateless

同様に、すべてのリポジトリ操作に対して、生成されたメタモデルアクセサーからエンティティの代替リポジトリを取得したり、@Inject でそれらを注入したりできます。

セッション型 メタモデルアクセサー リポジトリの型

Session

Cat_.managedBlocking()

PanacheRepository.Managed<Cat, Long>

StatelessSession

Cat_.statelessBlocking()

PanacheRepository.Stateless<Cat, Long>

Mutiny.Session

Cat_.managedReactive()

PanacheRepository.Reactive<Cat, Long>

Mutiny.StatelessSession

Cat_.statelessReactive()

PanacheRepository.Reactive.Stateless<Cat, Long>

しかし、エンティティ内でカスタム操作のための任意の数のリポジトリを定義することもできます。

import io.quarkus.hibernate.panache.PanacheEntity;
import io.quarkus.hibernate.panache.PanacheRepository;
import io.smallrye.mutiny.Uni;
import jakarta.persistence.Entity;
import org.hibernate.annotations.processing.Find;
import org.hibernate.annotations.processing.HQL;

import java.time.LocalDate;
import java.util.List;

@Entity
public class Cat extends PanacheEntity {
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends PanacheRepository<Cat> {
        @Find
        Cat findByName(String name);
    }

    public interface TheOtherRepo extends PanacheRepository.Reactive.Stateless<Cat, Long> {
        @Find
        Uni<Cat> findByName(String name);
    }
}

そして、両方のタイプのリポジトリを注入することができます。

import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;

public class Code {

    @Inject
    Cat.Repo repo;

    @Inject
    Cat.TheOtherRepo theOtherRepo;

    @Transactional
    public void blockingManagedMethod() {
        Cat cat = repo.findByName("Lucky");
    }

    @WithTransaction
    public Uni<Void> reactiveStatelessMethod() {
        return theOtherRepo.findByName("Lucky")
                .invoke(cat -> System.err.println(cat))
                .replaceWithVoid();
    }
}
これらのリポジトリを注入する代わりに、Cat_.repo()Cat_.theOtherRepo() を使用して、生成されたメタモデルアクセサーを使用することももちろん可能です。

要約

ここに、操作モデルの好みと必要な ID の型に応じて、エンティティおよびリポジトリのスーパータイプとして利用できるすべてのオプションをリストした表があります。独自の ID エンティティフィールドを定義する場合、WithId<Id> を拡張する必要がないことを覚えておいてください。

セッション型 ID 型 エンティティの親クラス エンティティの親インターフェース リポジトリのスーパータイプ

Session (マネージド、ブロッキング)

Long

PanacheEntity

PanacheRepository<Entity>

Session (マネージド、ブロッキング)

Id

WithId<Id>

PanacheEntity.Managed

PanacheRepository.Managed<Entity, Id>

StatelessSession (ステートレス、ブロッキング)

Id

WithId<Id>

PanacheEntity.Stateless

PanacheRepository.Stateless<Entity, Id>

Mutiny.Session (マネージド、リアクティブ)

Id

WithId<Id>

PanacheEntity.Reactive

PanacheRepository.Reactive<Entity, Id>

Mutiny.StatelessSession (ステートレス、リアクティブ)

Id

WithId<Id>

PanacheEntity.Reactive.Stateless

PanacheRepository.Reactive.Stateless<Entity, Id>

起動コード

ブロッキング操作の場合、起動コードの呼び出しは非常に簡単です。

import io.quarkus.runtime.Startup;
import jakarta.transaction.Transactional;

public class OnStart {
    @Startup
    @Transactional
    public void startupMethod() {
        Cat_.repo().deleteAll();
    }
}

リアクティブ操作の場合も同様です。

import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
import io.quarkus.runtime.Startup;
import io.smallrye.mutiny.Uni;

public class OnStart {
    @Startup
    @WithTransaction
    Uni<Void> startupMethod(){
        return Cat_.repo().deleteAll().replaceWithVoid();
    }
}

Jakarta Data

当然ながら、Jakarta Data を使用してリポジトリを定義することもできます。そのためには、以下のモジュールをインポートする必要があります。

pom.xml
<dependency>
    <groupId>jakarta.data</groupId>
    <artifactId>jakarta.data-api</artifactId>
</dependency>
build.gradle
implementation 'jakarta.data:jakarta.data-api'

その後、前に説明したように、PanacheEntity のバリアントを拡張するかどうかにかかわらず、必要に応じてエンティティを記述できます。

リポジトリについては、Jakarta Data 1.0 がステートレスなバリアントのみをサポートしているため(マネージドエンティティは次期 1.1 バージョンに追加される予定です)、ステートレスまたはマネージドエンティティをサポートするために PanacheRepository バリアントを使用することをお勧めします。ただし、ブロッキングおよびリアクティブの両方のバリアントがサポートされています。

そうでない場合は、@HQL@Query に置き換え、@Find のパッケージインポートを変更するだけで、@Delete ファインダーメソッドを作成することもできます。

import io.quarkus.hibernate.panache.PanacheRepository;

import jakarta.data.repository.Delete;
import jakarta.data.repository.Find;
import jakarta.data.repository.Query;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

import java.time.LocalDate;
import java.util.List;

@Entity
public class Cat {
    @Id
    @GeneratedValue
    public Long id;
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends PanacheRepository.Stateless<Cat, Long> {
        @Find (1)
        Cat findByName(String name);

        @Query("where breed = CUTE") (2)
        List<Cat> findCute();

        @Delete (3)
        long deleteByName(String name);

        @Query("delete from Cat where breed = HAIRLESS") (4)
        long deleteHairless();
    }
}
1 これは通常のファインダーメソッドです
2 @HQL の代わりに @Query を使用します。これらはすべて同じです。
3 delete ファインダーメソッドを記述できます。これはファインダーメソッドと似ていますが、そのアクションはエンティティを削除することであり、オプションで削除された数を返します。
4 削除メソッドは常にクエリメソッドとして明示的に記述できます。

しかし、それ以外の場合でも、エンティティを PanacheEntity またはそのバリアントのいずれかを拡張させることができ、リポジトリを注入したり、Cat_.repo() アクセサーを介して取得したりすることができます。

これらの機能の詳細については、対応する Hibernate Data Repositories および Jakarta Data ガイドを参照してください。

リポジトリメソッドから Uni 型を返すことでリアクティブなバリアントを使用することもできますし、マネージドエンティティをサポートするために、エンティティに他のタイプのリポジトリを追加することもできます。

REST integration

When your application uses both quarkus-data-hibernate and quarkus-rest-jackson, Quarkus automatically adds REST support for common Jakarta Data parameter types. This lets you declare Jakarta Data types directly as REST endpoint parameters, and they will be populated from HTTP query parameters without any manual parsing.

Supported parameter types

The following Jakarta Data types are automatically mapped from query parameters:

Jakarta Data type クエリーパラメーター デフォルト Example URL

PageRequest

page, size, requestTotal

page=1, size=10, requestTotal=true

?page=2&size=25

Sort<?>

sort

null

?sort=name (asc) or ?sort=-name (desc)

Order<?>

sort (repeating)

null

?sort=name&sort=-salary

Limit

limit, startAt, endAt

null (no limit), startAt=1

?limit=10 or ?limit=10&startAt=51 or ?startAt=51&endAt=60

Direction

direction

null

?direction=ASC or ?direction=desc

For Sort and Order, prefix the property name with - to indicate descending order.

Cursor-based pagination (PageRequest.Mode.CURSOR_NEXT / CURSOR_PREVIOUS) is not currently supported by this REST integration. Only offset-based pagination is available via query parameters.

Page serialization

When a REST endpoint returns a jakarta.data.page.Page, it is serialized as a JSON object with pagination metadata, rather than as a plain array:

{
  "content": [ ... ],
  "hasNext": true,
  "hasPrevious": false,
  "totalElements": 100,
  "totalPages": 34
}

The totalElements and totalPages fields are only included when the PageRequest was created with requestTotal=true.

Example endpoint

Here is an example of a REST endpoint that uses PageRequest and Order to return a paginated, sorted list of cats. PanacheRepository already provides a findAll(PageRequest, Order) method, so you can use the entity and repository we defined earlier directly:

import jakarta.data.Order;
import jakarta.data.page.Page;
import jakarta.data.page.PageRequest;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@Path("cats")
public class CatResource {

    @Inject
    Cat.Repo repo;

    @GET
    public Page<Cat> list(PageRequest pageRequest, Order<Cat> order) { (1)
        return repo.findAll(pageRequest, order);
    }
}
1 PageRequest is populated from page, size, and requestTotal query parameters. Order is populated from repeating sort query parameters.

A client can then call:

GET /cats?page=1&size=20&sort=name&sort=-birth

This produces a PageRequest.ofPage(1, 20, true) and Order.by(Sort.asc("name"), Sort.desc("birth")).

HQL, JD-QL, Jakarta-QL, Panache-QL

この API では、異なるクエリ言語が使用されています。

  • Hibernate Query Language (HQL) は、Hibernate で使用されるクエリ言語であり、@HQL アノテーションによってサポートされます。

  • Jakarta Persistence Query Language (JP-QL) は、Hibernate Query Language のサブセットです。これは現在、Jakarta Query と呼ばれる独自の仕様に移行中です。

  • Jakarta Data Query Language (JD-QL) は、Jakarta Persistence Query Language のサブセットであり、@Query アノテーションによってサポートされます。

  • Panache Query Language は、Hibernate Query Language のスーパーセットであり、ごくわずかなショートカットを追加したもので、すべての PanacheRepository クエリでサポートされています。

Panache クエリー言語

通常、ほとんどのHQLクエリは、from EntityName [where …​] [order by …​] という形式で、最後にオプションの要素が続きます。

選択クエリーが fromselect、または with で始まっていない場合は、次の追加形式がサポートされます。

  • order by …​from EntityName order by …​ に展開されます

  • <singleAttribute>` (および単一のパラメーター) は from EntityName where <singleAttribute> = ? に展開されます

  • where <query>from EntityName where <query> に展開されます

  • <query>from EntityName where <query> に展開されます

更新クエリーが update で始まらない場合は、以下の追加の形式をサポートしています:

  • from EntityName …​` は update EntityName …​ に展開されます

  • set? <singleAttribute> (および単一のパラメーター) は update EntityName set <singleAttribute> = ? に展開されます

  • set? <update-query>update EntityName set <update-query> に展開されます

削除クエリーが delete で始まらない場合は、以下の追加の形式をサポートしています:

  • from EntityName …​delete from EntityName …​ に展開されます

  • <singleAttribute> (および単一のパラメーター) は delete from EntityName where <singleAttribute> = ? に展開されます

  • <query>delete from EntityName where <query> に展開されます

Secure Panache repositories

Quarkus Security provides an initial support for securing Panache Repositories with security annotations.

Example inner repository with method-level security annotations
@Entity
public class Cat extends PanacheEntity {
    public String name;

    public interface SecuredRepo {
        @PermissionsAllowed("cat:list")
        @PermissionsAllowed("cat:read")
        @Find
        List<Cat> findByName(String name);
    }
}
Example standalone repository with class-level security annotation
@RolesAllowed("admin")
public interface SecuredCatRepo extends PanacheManagedBlockingRepository<Cat> {

    @Find
    List<Cat> findByName(String name);

}

In the example above, only methods directly declared on the SecuredCatRepo interface are secured. Methods inherited from PanacheRepository, such as findById, persist, or deleteAll, are not secured because type-level annotations only affect the type they annotate.

現在、型変数やワイルドカードを使用する汎用インターフェースメソッドは、標準のセキュリティーアノテーションでは確実には保護できません。したがって、そのようなメソッドを保護しようとするのではなく、以下に説明する 2 つの代替案のいずれかを使用する必要があります。

型変数を使用した汎用メソッドを持つリポジトリーの例
@Entity
public class Cat extends PanacheEntity {
    public String name;

    public interface ParentRepo<T extends Cat> {

        @Find
        List<T> findAll(Order<T> order);

    }

    public interface ChildRepo extends ParentRepo<Cat> {
    }
}

代替案 1: REST レイヤーの呼び出し元メソッドにセキュリティーアノテーションを適用する。

@Path("cat")
public class CatResource {

    @Inject
    Cat.ChildRepo childRepo;

    @PermissionsAllowed("cat:list")
    @GET
    public List<Cat> findAll() {
        return childRepo.findAll(Order.by());
    }

}

代替案 2: リポジトリーインターフェースの型変数 T を具体的な型に置き換える。

@Entity
public class Cat extends PanacheEntity {
    public String name;

    public interface Repo {

        @PermissionsAllowed("cat:list")
        @Find
        List<Cat> findAll(Order<Cat> order);

    }
}

関連コンテンツ