KotlinとJPAとの連携

KotlinとJPAの使用

1. 前書き

Kotlinの特徴の1つはinteroperability with Javaライブラリであり、JPAは確かにこれらの1つです。

このチュートリアルでは、Kotlin Data ClassesをJPAエンティティとして使用する方法について説明します。

2. 依存関係

物事を単純にするために、JPA実装としてHibernateを使用します。 Mavenプロジェクトに次の依存関係を追加する必要があります。


    org.hibernate
    hibernate-core
    5.2.15.Final


    org.hibernate
    hibernate-testing
    5.2.15.Final
    test

また、H2組み込みデータベースを使用してテストを実行します。


    com.h2database
    h2
    1.4.196
    test

Kotlinの場合、次のものを使用します。


    org.jetbrains.kotlin
    kotlin-stdlib-jdk8
    1.2.30

もちろん、HibernateH2、およびKotlinの最新バージョンはMavenCentralにあります。

3. コンパイラプラグイン(jpa-plugin)

JPAを使用するには、エンティティクラスにパラメータなしのコンストラクタが必要です。

デフォルトでは、Kotlinデータクラスにはそれがありません。それらを生成するには、jpa-pluginを使用する必要があります。


    kotlin-maven-plugin
    org.jetbrains.kotlin
    1.2.30
    
        
        jpa
        
        1.8
    
    
        
        org.jetbrains.kotlin
        kotlin-maven-noarg
        1.2.30
        
    
    

4. Kotlinデータクラスを使用したJPA

前のセットアップが完了したら、データクラスでJPAを使用する準備が整いました。

次のように、idnameの2つの属性を持つPersonデータクラスの作成を開始しましょう。

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

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

ご覧のとおり、@Entity, @Column@IdなどのJPAのアノテーションを自由に使用できます。

エンティティの動作を確認するために、次のテストを作成します。

@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=?

それはすべてが順調であることを示す指標です。 if we don’t use the jpa-plugin in runtime, we are going to get an InstantiationException, due to the lack of default constructor:に注意することが重要です

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

ここで、null値を使用して再度テストします。 これを行うには、Personエンティティを新しい属性email@OneToManyの関係で拡張しましょう。

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

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

また、emailフィールドとphoneNumbersフィールドはNULL可能であるため、疑問符で宣言されていることがわかります。

PhoneNumberエンティティには、idnumberの2つの属性があります。

@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)
    })
}

今回は、1つの挿入ステートメントを取得します。

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

もう一度テストしてみましょう。ただし、nullデータを使用せずに、出力を確認します。

@Test
fun givenPersonWithFullData_whenSaved_thenFound() {
    doInHibernate(({ this.sessionFactory() }), { session ->
        val personToSave = Person(
          0,
          "John",
          "[email protected]",
          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)
    })
}

そして、見てわかるように、3つのinsertステートメントを取得しました。

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, ?)

5. 結論

この簡単な記事では、jpa-pluginとHibernateを使用してKotlinデータクラスをJPAと統合する方法の例を紹介しました。

いつものように、ソースコードは利用可能ですover on GitHub.