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>io.quarkus</groupId>
                <artifactId>quarkus-data-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 quarkus-data-processor
annotationProcessor enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")
annotationProcessor 'io.quarkus:quarkus-data-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.data.hibernate.ManagedEntity;
import jakarta.persistence.Entity;

import java.time.LocalDate;

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

    public enum Breed {
        CUTE, HAIRLESS;
    }
}

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

  • クラスを作成する

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

  • Make it extend ManagedEntity

  • フィールドを 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.data.hibernate.ManagedEntity;
import io.quarkus.data.hibernate.ManagedRepository;
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 ManagedEntity {
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends ManagedRepository<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:

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

  • Which extends the ManagedRepository interface, with the entity in question as type parameter

  • そして、@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 を使用してリポジトリを検索します。

The ManagedRepository super type

Just like in previous versions of Hibernate ORM and Hibernate Reactive with Panache, the ManagedRepository type comes packed with most of the operations you need to work on your entity, such as, out of the box:

import io.quarkus.data.hibernate.blocking.BlockingDataQuery;
import jakarta.data.Order;
import jakarta.data.Sort;
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();
        List<Cat> sortedCats = repo.findAll().sort(Sort.asc("name")).list();
        // 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);
    }
}

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

With generated finder and query methods, as we’ve previously shown, everything is validated at build-time, but if you want to write non-type-safe queries, you can always use the provided methods of ManagedRepository:

import io.quarkus.data.hibernate.blocking.BlockingDataQuery;
import jakarta.data.Order;
import jakarta.data.Sort;
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");
        // Sort results using PanacheQuery
        List<Cat> sortedByName = repo.findAll().sort(Sort.asc("name")).list();
        List<Cat> sortedMatches = repo.find("breed = ?1", Cat.Breed.CUTE)
                .sort(Sort.desc("name"))
                .list();
    }
}

Sorting is applied on the PanacheQuery returned by find() and findAll(), not on the shortcut list(), listAll(), stream(), or streamAll() methods. For a single criterion, pass a Sort directly — there is no need to wrap it in Order.by(). Use Order only when you need multiple criteria, or when receiving an Order from a REST endpoint:

List<Cat> byName = repo.findAll().sort(Sort.asc("name")).list();
List<Cat> byNameThenBirth = repo.findAll()
        .sort(Order.by(Sort.asc("name"), Sort.desc("birth")))
        .list();

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

In the example above, we extended the ManagedEntity type, and did not define any database identifier, that’s because ManagedEntity comes with a default generated database identifier, so you don’t have to worry about it. It does this by extending the WithId.AutoLong class, which provides a generated database identifier of type Long.

You can choose to extend the WithId.AutoString for a String identifier, or WithId.AutoUUID for a UUID identifier to automatically get an attribute of the form:

@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.

Naturally, you can also provide your own database identifier explicitly and implement the ManagedEntity.CustomId interface in your entity, as well as use the ManagedRepository.CustomId interface for your repository, in order to specify you database identifier type:

import io.quarkus.data.hibernate.ManagedEntity;
import io.quarkus.data.hibernate.ManagedRepository;
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 ManagedEntity.CustomId {
    @Id
    public String id;
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends ManagedRepository.CustomId<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

ManagedEntity

UUID

WithId.AutoUUID

String

WithId.String

T

WithId<Id>

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

Out of the box, your subtype of ManagedEntity will be managed by Hibernate ORM, and every change to the entity will be automatically sent to the database without requiring any explicit update operation.

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

In this case, you need to extend the RecordEntity class and extend the RecordRepository interface:

import io.quarkus.data.hibernate.RecordEntity;
import io.quarkus.data.hibernate.RecordRepository;
import io.quarkus.data.hibernate.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 RecordEntity {
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends RecordRepository<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)

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

  • Your entity extends ManagedEntity.Reactive

  • Your repository extends ManagedRepository.Reactive

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

import io.quarkus.data.hibernate.ManagedEntity;
import io.quarkus.data.hibernate.ManagedRepository;
import io.quarkus.data.hibernate.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 ManagedEntity.Reactive {
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends ManagedRepository.Reactive<Cat> {
        @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();
    }
}

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

If you want to manage manually your entity changes, then you can use a stateless session by switching to the RecordEntity.Reactive for your entity, and RecordRepository.Reactive:

import io.quarkus.data.hibernate.RecordEntity;
import io.quarkus.data.hibernate.RecordRepository;
import io.quarkus.data.hibernate.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 RecordEntity.Reactive {
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends RecordRepository.Reactive<Cat> {
        @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()

ManagedEntity

StatelessSession

.statelessBlocking()

RecordEntity

Mutiny.Session

.managedReactive()

ManagedEntity.Reactive

Mutiny.StatelessSession

.statelessReactive()

RecordEntity.Reactive

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

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

Session

Cat_.managedBlocking()

ManagedRepository<Cat>

StatelessSession

Cat_.statelessBlocking()

RecordRepository<Cat>

Mutiny.Session

Cat_.managedReactive()

ManagedRepository.Reactive<Cat>

Mutiny.StatelessSession

Cat_.statelessReactive()

RecordRepository.Reactive<Cat>

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

import io.quarkus.data.hibernate.ManagedEntity;
import io.quarkus.data.hibernate.ManagedRepository;
import io.quarkus.data.hibernate.RecordRepository;
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 ManagedEntity {
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

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

    public interface TheOtherRepo extends RecordRepository<Cat> {
        @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

ManagedEntity

ManagedRepository<Entity>

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

Id

WithId<Id>

ManagedEntity.CustomId

ManagedRepository.CustomId<Entity, Id>

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

Long

RecordEntity

RecordRepository<Entity>

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

Id

WithId<Id>

RecordEntity.CustomId

RecordRepository.CustomId<Entity, Id>

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

Long

ManagedEntity.Reactive

ManagedRepository.Reactive<Entity>

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

Id

WithId<Id>

ManagedEntity.Reactive.CustomId

ManagedRepository.Reactive.CustomId<Entity, Id>

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

Long

RecordEntity.Reactive

RecordRepository.Reactive<Entity>

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

Id

WithId<Id>

RecordEntity.Reactive.CustomId

RecordRepository.Reactive.CustomId<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'

And then you can write your entity as described before, extending a variant of ManagedEntity or RecordEntity or not, as you wish.

As for the repositories, given that Jakarta Data 1.0 only supports the stateless variants (managed entities are being added to the upcoming 1.1 version), we recommend that you stick to the ManagedRepository or RecordRepository variants in order to support stateless or managed entities. Both blocking and reactive variants are supported, though.

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

import io.quarkus.data.hibernate.RecordRepository;

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 RecordRepository<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 削除メソッドは常にクエリメソッドとして明示的に記述できます。

But otherwise you can still make your entity extend ManagedEntity or RecordEntity or any of its variants, and you still get your repository injectable, or via the Cat_.repo() accessor.

これらの機能の詳細については、対応する 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<Cat>

sort

null

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

Order<Cat>

sort (repeating)

empty Order.by()

?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.

When binding sort parameters from REST endpoints to PanacheQuery, prefer Order<Cat> over Sort<Cat>. A missing sort query parameter yields null for Sort<Cat> but an empty Order.by() for Order<Cat>. If you use Sort<Cat> and the parameter is null, you can safely call .sort(null) and it is a no-op; Order is still preferred when multiple columns may be requested.

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. Paging and sorting are configured on the PanacheQuery returned by findAll():

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

import java.util.List;

@Path("cats")
public class CatResource {

    @Inject
    Cat.Repo repo;

    @GET
    public List<Cat> list(PageRequest pageRequest, Order<Cat> order) { (1)
        return repo.findAll().pages().request(pageRequest).sort(order).list();
    }
}
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 アノテーションによってサポートされます。

  • The Panache Query Language, which is a superset of the Hibernate Query Language adding very few shortcuts, which is supported in all the ManagedRepository and RecordRepository queries.

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 ManagedEntity.AutoLong {
    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 ManagedRepository.AutoLong<Cat> {

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

}

In the example above, only methods directly declared on the SecuredCatRepo interface are secured. Methods inherited from ManagedRepository, 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 ManagedEntity {
    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 ManagedEntity {
    public String name;

    public interface Repo {

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

    }
}

関連コンテンツ