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

シンプルになったHibernate ORM with PanacheとKotlin

Hibernate ORMはデファクトスタンダードなJPA実装であり、Javaエコシステムではよく知られています。Hibernate ORM with Panacheはこのお馴染みのフレームワークの上に新しいレイヤーを提供します。これらの詳細については Hibernate ORM with Panacheのガイド ですでに説明しているので、このガイドでは触れません。このガイドでは、KotlinベースのQuarkusアプリケーションでHibernate ORM with Panacheを使用するために必要なKotlin固有の変更点について説明します。

kotlin版のHibernate ORM with Panacheを使用する場合、 PanacheEntity, PanacheQuery, PanacheRepository は、 io.quarkus.hibernate.orm.panache.kotlin という別のパッケージになっていることに注意してください。

最初に:例

Hibernate ORM with Panacheガイドで見たように、Hibernate ORM with Panacheを使うと、エンティティーやリポジトリ(DAOとも呼ばれます)の機能を自動で提供される機能で拡張することができます。Kotlin を使用する場合のアプローチは Java 版と非常によく似ていますが、若干の変更点があります。エンティティーを Panache で有効にするには、次のように定義します。

@Entity
class Person: PanacheEntity {
    lateinit var name: String
    lateinit var birth: LocalDate
    lateinit var status: Status
}

ご覧のように、私たちのエンティティーはシンプルなままです。しかし、Java版とは少し違いがあります。Kotlin 言語は Java のように静的メソッドの概念をサポートしていません。その代わり、 コンパニオンオブジェクト を使わなければなりません。

@Entity
class Person : PanacheEntity() {
    companion object: PanacheCompanion<Person> {  (1)
        fun findByName(name: String) = find("name", name).firstResult()
        fun findAlive() = list("status", Status.Alive)
        fun deleteStefs() = delete("name", "Stef")
    }

    lateinit var name: String  (2)
    lateinit var birth: LocalDate
    lateinit var status: Status
}
1 コンパニオンオブジェクトは、特定のインスタンスに関連しないすべてのメソッドを保持し、特定の型にバインドされた一般的な管理とクエリを可能にします。
2 幾つか選択肢がありますが、ここでは lateinit のアプローチを選択しました。これにより、コンストラクタ(表示されていません)やHibernateがデータベースからデータをロードすることで適切に代入されることがわかっているので、これらのフィールドを非 null として宣言することができます。
これらの型は、それらのチュートリアルで言及されている Java のタイプとは異なります。Kotlin のサポートのために、すべての Panache の型は io.quarkus.hibernate.orm.panache.kotlin パッケージにあります。このサブパッケージは、Java と Kotlin の違いを区別し、単一のプロジェクトで両方を明確に使用することを可能にします。

Kotlin版では、単純に active record pattern の機能を companion object に追加しました。このわずかな変更を別にすれば、私たちは、世界のJava側から簡単にマップする方法で私たちの型を使用して作業することができます。

ソリューション

次のセクションの指示に従って、段階的にアプリケーションを作成していくことをお勧めします。ただし、完成した例にすぐにアクセスすることもできます。

Gitリポジトリをクローンする: git clone https://github.com/quarkusio/quarkus-quickstarts.git 、またはhttps://github.com/quarkusio/quarkus-quickstarts/archive/main.zip[archive] をダウンロードする。

このソリューションは hibernate-orm-panache-kotlin-quickstart ディレクトリにあります。

Hibernate ORM with PanacheとKotlinのセットアップと設定

KotlinでHibernate ORM with Panacheを使い始めるには、一般的には、Javaチュートリアルで説明されている手順に従います。プロジェクトを設定する上で最も大きな変更点は、インクルードするQuarkusのアーティファクトです。もちろん、必要であればJavaバージョンのままでも構いませんが、KotlinのAPIだけが必要な場合は、代わりに以下の依存関係をインクルードしてください。

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-hibernate-orm-panache-kotlin</artifactId>  (1)
</dependency>
1 最後に -kotlin が追加されていることに注意してください。通常はこのバージョンのみが必要ですが、プロジェクトでJavaとKotlinの両方のコードを使用する場合は、両方のアーティファクト含めても大丈夫です。
build.gradle
implementation("io.quarkus:quarkus-hibernate-orm-panache-kotlin") (1)
1 最後に -kotlin が追加されていることに注意してください。通常はこのバージョンのみが必要ですが、プロジェクトでJavaとKotlinの両方のコードを使用する場合は、両方のアーティファクト含めても大丈夫です。

リポジトリパターンの使用

エンティティの定義

リポジトリパターンを使用する場合、エンティティーを通常のJPAエンティティーとして定義することができます。

@Entity
class Person {
    @Id
    @GeneratedValue
    var id: Long? = null;
    lateinit var name: String
    lateinit var birth: LocalDate
    lateinit var status: Status
}

リポジトリの定義

リポジトリを使用する場合、PanacheRepository を実装することでアクティブレコードパターンをリポジトリに注入した場合と全く同じ便利なメソッドを得ることができます。

@ApplicationScoped
class PersonRepository: PanacheRepository<Person> {
     fun findByName(name: String) = find("name", name).firstResult()
     fun findAlive() = list("status", Status.Alive)
     fun deleteStefs() = delete("name", "Stef")
}

PanacheEntityBase で定義されている操作はすべてリポジトリ上で利用可能なので、これを使用することはアクティブレコードパターンを使用するのと全く同じですが、それを注入する必要があります。

@Inject
lateinit var personRepository: PersonRepository

@GET
fun count() = personRepository.count()

最も便利な操作

リポジトリを書くことで実行可能な最も一般的な操作は以下の通りです。

// creating a person
var person = Person()
person.name = "Stef"
person.birth = LocalDate.of(1910, Month.FEBRUARY, 1)
person.status = Status.Alive

// persist it
personRepository.persist(person)

// note that once persisted, you don't need to explicitly save your entity: all
// modifications are automatically persisted on transaction commit.

// check if it's persistent
if(personRepository.isPersistent(person)){
    // delete it
    personRepository.delete(person)
}

// getting a list of all Person entities
val allPersons = personRepository.listAll()

// finding a specific person by ID
person = personRepository.findById(personId) ?: throw Exception("No person with that ID")

// finding all living persons
val livingPersons = personRepository.list("status", Status.Alive)

// counting all persons
val countAll = personRepository.count()

// counting all living persons
val countAlive = personRepository.count("status", Status.Alive)

// delete all living persons
personRepository.delete("status", Status.Alive)

// delete all persons
personRepository.deleteAll()

// delete by id
val deleted = personRepository.deleteById(personId)

// set the name of all living persons to 'Mortal'
personRepository.update("name = 'Mortal' where status = ?1", Status.Alive)

すべての list メソッドは、同等の stream バージョンがあります。

val persons = personRepository.streamAll();
val namesButEmmanuels = persons
    .map { it.name.toLowerCase() }
    .filter { it != "emmanuel" }
stream メソッドが動作するにはトランザクションが必要です。

更なるサンプルについては、 Java 版 を参照してください。どちらの API も同じで、Kotlin 開発者がより自然に感じられるように Kotlin 固有の調整が行われている点を除いては同じように動作します。これらの調整には、nullability の使用方法の改善や、API メソッドの Optional がないことなどが含まれます。