Working with Kotlin and JPA


1.简介

Kotlin的一个特点是与Java库的互操作性,而JPA肯定是其中之一。

在本教程中,我们将探索如何将Kotlin数据类用作JPA实体。

2.依赖性

为了简单起见,我们将使用Hibernate作为JPA实现; 我们需要将以下依赖项添加到Maven项目中:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.2.15.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-testing</artifactId>
    <version>5.2.15.Final</version>
    <scope>test</scope>
</dependency>

我们还将使用H2嵌入式数据库来运行我们的测试:

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.196</version>
    <scope>test</scope>
</dependency>

对于Kotlin,我们将使用以下内容:

<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib-jdk8</artifactId>
    <version>1.2.30</version>
</dependency>

当然,最新版本的Hibernate,H2和Kotlin可以在Maven Central中找到。

3.编译器插件(jpa-plugin)

要使用JPA,实体类需要不带参数的构造函数。

默认情况下,Kotlin数据类没有它,为了生成它们,我们需要使用jpa-plugin:

<plugin>
    <artifactId>kotlin-maven-plugin</artifactId>
    <groupId>org.jetbrains.kotlin</groupId>
    <version>1.2.30</version>
    <configuration>
        <compilerPlugins>
        <plugin>jpa</plugin>
        </compilerPlugins>
        <jvmTarget>1.8</jvmTarget>
    </configuration>
    <dependencies>
        <dependency>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-maven-noarg</artifactId>
        <version>1.2.30</version>
        </dependency>
    </dependencies>
    <!--...-->
</plugin>

4.带有Kotlin数据类的JPA

完成上一次设置后,我们已准备好将JPA与数据类一起使用。

让我们开始创建一个具有两个属性的Person数据类--id和name,如下所示:

@Entity
data class Person(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Int,

    @Column(nullable = false)
    val name: String
)

正如我们所看到的,我们可以自由使用注释从JPA像@Entity ,@Column和@Id。

要查看我们的实体,我们将创建以下测试:

@Test
fun givenPerson_whenSaved_thenFound() {
    doInHibernate(({ this.sessionFactory() }), { session ->
        val personToSave = Person(0, "John")
        session.persist(personToSave)
        val personFound = session.find(Person::class.java, personToSave.id)
        session.refresh(personFound)

        assertTrue(personToSave == personFound)
    })
}

在启用日志记录的情况下运行测试后,我们可以看到以下结果:

Hibernate: insert into Person (id, name) values (null, ?)
Hibernate: select person0_.id as id1_0_0_, person0_.name as name2_0_0_ from Person person0_ where person0_.id=?

这表明一切进展顺利。 重要的是要注意,如果我们不在运行时使用jpa-plugin,我们将得到一个InstantiationException,因为缺少默认构造函数:

javax.persistence.PersistenceException: org.hibernate.InstantiationException: No default constructor for entity: : com.baeldung.entity.Person

现在,我们将再次使用null值进行测试。为此,让我们使用新的属性email和@OneToMany关系扩展我的Person实体:

//...
@Column(nullable = true)
val email: String? = null,

@Column(nullable = true)
@OneToMany(cascade = [CascadeType.ALL])
val phoneNumbers: List<PhoneNumber>? = null

我们还可以看到email和phoneNumbers字段可以为空,因此用问号声明。

实体有两个属性-id and number::

@Entity
data class PhoneNumber(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Int,

    @Column(nullable = false)
    val number: String
)

让我们通过测试来验证这一点:

@Test
fun givenPersonWithNullFields_whenSaved_thenFound() {
    doInHibernate(({ this.sessionFactory() }), { session ->
        val personToSave = Person(0, "John", null, null)
        session.persist(personToSave)
        val personFound = session.find(Person::class.java, personToSave.id)
        session.refresh(personFound)

        assertTrue(personToSave == personFound)
    })
}

这次,我们将获得一个insert语句:

Hibernate: insert into Person (id, email, name) values (null, ?, ?)
Hibernate: select person0_.id as id1_0_1_, person0_.email as email2_0_1_, person0_.name as name3_0_1_, phonenumbe1_.Person_id as Person_i1_1_3_, phonenumbe2_.id as phoneNum2_1_3_, phonenumbe2_.id as id1_2_0_, phonenumbe2_.number as number2_2_0_ from Person person0_ left outer join Person_PhoneNumber phonenumbe1_ on person0_.id=phonenumbe1_.Person_id left outer join PhoneNumber phonenumbe2_ on phonenumbe1_.phoneNumbers_id=phonenumbe2_.id where person0_.id=?

让我们再测试一次但没有空数据来验证输出:

@Test
fun givenPersonWithFullData_whenSaved_thenFound() {
    doInHibernate(({ this.sessionFactory() }), { session ->
        val personToSave = Person(
          0,
          "John",
          "jhon@test.com",
          Arrays.asList(PhoneNumber(0, "202-555-0171"), PhoneNumber(0, "202-555-0102")))
        session.persist(personToSave)
        val personFound = session.find(Person::class.java, personToSave.id)
        session.refresh(personFound)

        assertTrue(personToSave == personFound)
    })
}

而且,正如我们所看到的,现在我们得到三个插入语句:

Hibernate: insert into Person (id, email, name) values (null, ?, ?)
Hibernate: insert into PhoneNumber (id, number) values (null, ?)
Hibernate: insert into PhoneNumber (id, number) values (null, ?)