Работа с Kotlin и JPA

Работаем с Kotlin и JPA

1. Вступление

Одна из характеристик Kotlin - это библиотекиinteroperability with Java, и JPA, безусловно, одна из них.

В этом руководстве мы рассмотрим, как использоватьKotlin Data Classes как объекты JPA.

2. зависимости

Для простоты мы будем использовать Hibernate в качестве нашей реализации JPA; нам нужно добавить следующие зависимости в наш проект 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

Конечно, самые последние версииHibernate,H2 иKotlin можно найти в Maven Central.

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. JPA с классами данных Kotlin

После завершения предыдущей настройки мы готовы использовать 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=?

Это показатель того, что все идет хорошо. Важно отметить, что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 имеет два атрибута -id и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)
    })
}

На этот раз мы получим один оператор вставки:

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

И, как мы видим, теперь мы получаем три оператора вставки:

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. Заключение

В этой быстрой статье мы увидели пример того, как интегрировать классы данных Kotlin с JPA, используя jpa-plugin и Hibernate.

Как всегда доступен исходный кодover on GitHub.