本文主要讲解如何在Kotlin
中使用JPA
。为了便于讲解,本文使用了一个Spring Boot JPA
应用,其特征如下 :
Spring Data JPA
Web
层Kotlin
定义JUnit 5
H2
用于演示首先,我们要在pom.xml
中引入依赖,声明使用Spring Boot
和JPA
:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
然后,我们使用H2
内嵌式数据库支持数据保存 :
<dependency>
<groupId>com.h2databasegroupId>
<artifactId>h2artifactId>
dependency>
接下来,是跟Kotlin
相关的依赖 , 这里kotlin.version
在pom.xml
属性定义区域定义 :
<dependency>
<groupId>org.jetbrains.kotlingroupId>
<artifactId>kotlin-reflectartifactId>
<version>${kotlin.version}version>
dependency>
<dependency>
<groupId>org.jetbrains.kotlingroupId>
<artifactId>kotlin-stdlibartifactId>
<version>${kotlin.version}version>
dependency>
单元测试有关的依赖 :
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>junitgroupId>
<artifactId>junitartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiter-engineartifactId>
<scope>testscope>
dependency>
要使用JPA
,实体类需要一个无参构造函数。
缺省情况下,Kotlin
数据类是没有无参构造函数的,为了产生无参构造函数,我们需要如下JPA
插件:
<plugin>
<artifactId>kotlin-maven-pluginartifactId>
<groupId>org.jetbrains.kotlingroupId>
<version>${kotlin.version}version>
<configuration>
<compilerPlugins>
<plugin>jpaplugin>
compilerPlugins>
configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlingroupId>
<artifactId>kotlin-maven-noargartifactId>
<version>${kotlin.version}version>
dependency>
dependencies>
plugin>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>andy.kotlin.jpagroupId>
<artifactId>zeroartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<kotlin.version>1.3.50kotlin.version>
properties>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.8.RELEASEversion>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>junitgroupId>
<artifactId>junitartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiter-engineartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.h2databasegroupId>
<artifactId>h2artifactId>
dependency>
<dependency>
<groupId>org.jetbrains.kotlingroupId>
<artifactId>kotlin-reflectartifactId>
<version>${kotlin.version}version>
dependency>
<dependency>
<groupId>org.jetbrains.kotlingroupId>
<artifactId>kotlin-stdlibartifactId>
<version>${kotlin.version}version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<artifactId>kotlin-maven-pluginartifactId>
<groupId>org.jetbrains.kotlingroupId>
<version>${kotlin.version}version>
<configuration>
<compilerPlugins>
<plugin>jpaplugin>
compilerPlugins>
configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlingroupId>
<artifactId>kotlin-maven-noargartifactId>
<version>${kotlin.version}version>
dependency>
dependencies>
plugin>
plugins>
build>
project>
以上步骤完成后,现在可以通过Kotlin
数据类定义JPA
实体。在本文的例子中,我们定义了两个JPA
实体:员工Employee
和电话号码PhoneNumber
, 如下所示 :
员工实体Employee
定义在文件Employee.kt
中 :
package andy
import javax.persistence.*
@Entity
data class Employee(
/**
* 员工记录 id,自动生成
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Int,
/**
* 员工姓名,不可为空
*/
@Column(nullable = false)
val name: String,
/**
* 员工电子邮箱,可以为空
*/
@Column(nullable = true)
val email: String? = null,
/**
* 员工电话号码,可以有多个,关联存储在另外一张表中
*/
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER)
val phoneNumbers: List<PhoneNumber>? = null
)
电话号码PhoneNumber
实体定义在PhoneNumber.kt
中 :
package andy
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
/**
* 电话号码
*/
@Entity
data class PhoneNumber(
/**
* 电话号码记录 id,自动生成
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Int,
/**
* 电话号码字符串值,不能为空
*/
@Column(nullable = false)
val number: String
)
从实体类定义可以看到,JPA
提供的注解,比如@Entity
,@Column
和@Id
等等在这里可以随意使用。
有了JPA
实体,我们来定义相应的存储库类。
首先是员工Employee
实体对应的存储库类EmployeeRepository
,定义在文件EmployeeRepository.kt
中:
package andy
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.JpaSpecificationExecutor
import org.springframework.stereotype.Repository
@Repository
interface EmployeeRepository : JpaRepository<Employee, Long>, JpaSpecificationExecutor<Employee> {
fun findByNameLike(pattern: String): List<Employee>;
}
然后是电话号码PhoneNumber
实体对应的PhoneNumberRepository
存储库类,定义在文件PhoneNumberRepository.kt
中:
package andy
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.JpaSpecificationExecutor
import org.springframework.stereotype.Repository
@Repository
interface PhoneNumberRepository : JpaRepository<PhoneNumber, Long>, JpaSpecificationExecutor<PhoneNumber> {
}
有了上面的实体类和相应的存储库,我们定义一个Spring Boot
应用在文件Application.kt
中,如下所示 :
package andy
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
@SpringBootApplication
open class Application {
// 另外一种注入 EmployeeRepository bean 的方法
//@Autowired
//lateinit var repository: EmployeeRepository;
/**
* 定义一个 CommandLineRunner bean,
* 它会在应用启动时运行
*/
@Bean
open fun init(repository: EmployeeRepository): CommandLineRunner {
return CommandLineRunner {
// 这段逻辑对应接口 CommandLineRunner 约定的方法 void run(String... args)
// 注意下面定义的 Employee 实例的第三个参数设置为了 null,
// 这里可以设置为 null 是因为 Employee 实体定义中相应 phoneNumber 属性定义中的问号
repository.save(Employee(0, "张三", "[email protected]", null))
repository.save(Employee(0, "李四", "[email protected]", null))
repository.save(Employee(0, "王五", "[email protected]", null))
/**
* 上面的添加了一个Employee 王五,所以下面输出语句肯定能搜索到一条记录
*/
val entity = repository.findByNameLike("王五")[0];
println("查询到的记录应该是 王五 : $entity");
}
}
}
fun main(args: Array<String>) {
runApplication<Application>(*args)
}
在该文件中:
@SpringBootApplication
定义了一个Spring Boot
应用类Application
;Spring Boot CommandLineRunner bean
,接口CommandLineRunner
约定了该bean
的run
方法会在容器启动时被执行;main
启动应用Application
;该文件中重点的部分是CommandLineRunner bean
的定义。它有如下特征 :
Kotlin
函数的形式存在,接受一个参数repository: EmployeeRepository
以接收注入的EmployeeRepository bean
;CommandLineRunner
实例,表明其run
方法会在应用启动时被调用;另外,我们在CommandLineRunner#run
方法逻辑中往数据库插入三条员工记录,但是每个员工的电话号码都未设置,使用了null
值。这里主要是用来演示数据库插入动作和null
值的使用的。运行该程序,你可以看到如下输出 :
查询到的记录应该是 王五 : Employee(id=3, name=王五, email=wang.wu@test.com, phoneNumbers=[])
这里我们制作一个基于JUnit 5
的单元测试来观察一下Kotlin
中使用JPA
:
package andy
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.boot.test.context.SpringBootTest
import java.util.*
@DataJpaTest
@DisplayName("Test JPA in Kotlin using JUnit 5")
open class KotlinJPATest @Autowired constructor(val employeeRepository: EmployeeRepository) {
// 另外一种注入方式,属性注入
@Autowired
lateinit var phoneNumberRepository: PhoneNumberRepository;
@Test
fun testJPAInKotlin() {
/**
* 以下语句添加三个员工,使用六个电话号码
*/
val tom = Employee(0, "Tom", "[email protected]", Arrays.asList(PhoneNumber(0, "110"), PhoneNumber(0, "120")));
employeeRepository.save(tom);
val jerry = Employee(0, "Jerry", "[email protected]", Arrays.asList(PhoneNumber(0, "119"), PhoneNumber(0, "911")));
employeeRepository.save(jerry);
val andy = Employee(0, "Andy", "[email protected]", Arrays.asList(PhoneNumber(0, "114"), PhoneNumber(0, "10080")));
employeeRepository.save(andy);
/**
* 下面查询语句能匹配到一条记录,是关于 Jerry 的
*/
val employees: List<Employee> = employeeRepository.findByNameLike("%rr%");
println("查询得到的员工记录是 : $employees ");
assertEquals(1,employees.size,"查询得到的员工记录数量应该是1")
assertEquals("Jerry",employees[0].name,"查询得到的员工记录应该是Jerry")
/**
* 下面输出电话号码的数量,应该是 6,也就是上面创建员工记录时所使用的电话号码信息
*/
val countPhoneNumbers = phoneNumberRepository.count();
println("电话号码记录数量 : $countPhoneNumbers");
assertEquals(6,countPhoneNumbers,"电话号码数量 [应该是 6]")
}
}
该单元测试我们主要演示以下几个要点 :
@Autowired
注入组件;Kotlin
中像在Java
中一样操作JPA
实体和存储库组件;运行该测试,在控制台上,你应该可以看到如下输出 :
Hibernate: insert into employee (id, email, name) values (null, ?, ?)
Hibernate: insert into phone_number (id, number) values (null, ?)
Hibernate: insert into phone_number (id, number) values (null, ?)
Hibernate: insert into employee (id, email, name) values (null, ?, ?)
Hibernate: insert into phone_number (id, number) values (null, ?)
Hibernate: insert into phone_number (id, number) values (null, ?)
Hibernate: insert into employee (id, email, name) values (null, ?, ?)
Hibernate: insert into phone_number (id, number) values (null, ?)
Hibernate: insert into phone_number (id, number) values (null, ?)
Hibernate: select employee0_.id as id1_0_, employee0_.email as email2_0_, employee0_.name as name3_0_ from employee employee0_ where employee0_.name like ? escape ?
查询得到的员工记录是 : [Employee(id=5, name=Jerry, email=jerry@test.com, phoneNumbers=[PhoneNumber(id=3, number=119), PhoneNumber(id=4, number=911)])]
Hibernate: select count(*) as col_0_0_ from phone_number phonenumbe0_
电话号码记录数量 : 6
本文通过一个例子应用讲解了如何使用Kotlin
制作一个Spring Boot JPA
应用,演示了如下要点 :
JPA
实体的定义JPA
存储库组件的定义SpringApplication
入口应用程序的定义JUnit 5
的单元测试@Autowired
JPA
实体对象的创建JPA
存储库插入或者查询实体对象