シンプルになったHibernate ORM with PanacheとKotlin
Hibernate ORMは、デファクトスタンダードであるJakarta Persistence(旧称JPA)実装であり、Javaエコシステムでよく知られています。Hibernate ORM with Panacheは、このおなじみのフレームワークの上に、新しいレイヤーを提供します。このガイドでは、 Hibernate ORM with Panacheのガイド ですでにカバーされているため、どちらの仕様にも深入りしません。このガイドでは、KotlinベースのQuarkusアプリケーションでHibernate ORM with Panacheを使用するために必要なKotlin固有の変更点を説明します。
KotlinバージョンのHibernate ORMを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だけが必要な場合は、代わりに以下の依存関係をインクルードしてください。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache-kotlin</artifactId> (1)
</dependency>
1 | 最後に -kotlin が追加されていることに注意してください。通常はこのバージョンのみが必要ですが、プロジェクトでJavaとKotlinの両方のコードを使用する場合は、両方のアーティファクト含めても大丈夫です。 |
implementation("io.quarkus:quarkus-hibernate-orm-panache-kotlin") (1)
1 | 最後に -kotlin が追加されていることに注意してください。通常はこのバージョンのみが必要ですが、プロジェクトでJavaとKotlinの両方のコードを使用する場合は、両方のアーティファクト含めても大丈夫です。 |
リポジトリパターンの使用
エンティティの定義
リポジトリパターンを使用する場合、エンティティを通常のJakarta Persistenceエンティティとして定義することができます。
@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
がないことなどが含まれます。