Simplified Hibernate ORM with Panache and Kotlin

Hibernate ORM 是事实上的标准 Jakarta Persistence(以前称为 JPA)实现,并且在 Java 生态系统中广为人知。带有 Panache 的 Hibernate ORM 在这个熟悉的框架上提供了一个新的层。本指南不会深入探讨这两个框架的具体细节,因为这些细节已经在 Hibernate ORM with Panache guide中介绍过了。在本指南中,我们将介绍在基于 Kotlin 的 Quarkus 应用程序中使用 Panache 与 Hibernate ORM 所需的特定于 Kotlin 的变更。

在使用带有 Panache 的 Hibernate ORM 的 Kotlin 版本时,请注意,PanacheEntityPanacheQuery`和 `PanacheRepository`在一个不同的程序包中:`io.quarkus.hibernate.orm.panache.kotlin

First: an example

正如我们在带有 Panache 的 Hibernate 指南中所看到的,它允许我们用一些自动提供的功能扩展实体和存储库(也称为 DAO)中的功能。在使用 Kotlin 时,该方法与 Java 版本中的方法非常相似,只有一两个小改动。如需启用 Panache 的实体,您可以将其定义为类似于以下内容:

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

如您所见,我们的实体仍然简单。但是,与 Java 版本相比,有一个细微的差别。Kotlin 语言不支持与 Java 完全相同方式的静态方法。相反,我们必须使用 a companion object

@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 从数据库加载数据会将这些字段正确分配。

这些类型与那些教程中提到的 Java 类型不同。对于 Kotlin 支持,所有 Panache 类型都将在 `io.quarkus.hibernate.orm.panache.kotlin`包中找到。此子包允许区分 Java 和 Kotlin 变体,并允许在单个项目中明确使用这两个变体。

在 Kotlin 版本中,我们只是将大部分 active record pattern功能移到了 companion object。除了这一细微改动,我们可以在世界 Java 端轻松映射的方式中使用我们的类型。

Solution

我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。

克隆 Git 存储库: git clone $${quickstarts-base-url}.git,或下载 $${quickstarts-base-url}/archive/main.zip[存档]。

解决方案位于 hibernate-orm-panache-kotlin-quickstart directory

Setting up and configuring Hibernate ORM with Panache and Kotlin

若要开始使用带有 Panache 和 Kotlin 的 Hibernate ORM,您通常可以按照 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 代码,您可以安全地包含这两个制品。

Using the repository pattern

Defining your entity

在使用存储库模式时,您可以将您的实体定义为常规 Jakarta Persistence 实体。

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

Defining your repository

在使用存储库时,您可以通过让它们实现 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()

Most useful operations

在编写完存储库后,以下是您将能够执行的最常见操作:

// 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 version 了解完整详细信息。两个 API 都是相同的,并且以相同的方式工作,但对 Kotlin 特定的调整除外,这些调整可让事情对 Kotlin 开发者来说更自然。这些调整包括使用 NULL 值更好的方法以及 API 方法中缺少 Optional