基于Kotlin的 Spring Boot JPA应用
1. 简介
本文主要讲解如何在Kotlin中使用JPA。为了便于讲解,本文使用了一个Spring Boot JPA应用,其特征如下 :
- 基于
Spring Data JPA - 没有使用
Web层 - 所有类通过
Kotlin定义 - 使用
JUnit 5 - 使用内嵌数据库
H2用于演示
2. pom.xml
2.1 依赖
首先,我们要在pom.xml中引入依赖,声明使用Spring Boot 和JPA :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- jpa support -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
然后,我们使用H2内嵌式数据库支持数据保存 :
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
接下来,是跟Kotlin相关的依赖 , 这里kotlin.version在pom.xml属性定义区域定义 :
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
单元测试有关的依赖 :
<!-- Spring Boot Test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
2.2. JPA编译插件
要使用JPA,实体类需要一个无参构造函数。
缺省情况下,Kotlin数据类是没有无参构造函数的,为了产生无参构造函数,我们需要如下JPA插件:
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<configuration>
<compilerPlugins>
<!--注意插件 jpa 不能缺少,非则会提示Entity缺少缺省构造函数-->
<plugin>jpa</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-noarg</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
2.3 完整的 pom.xml 文件内容
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<groupId>andy.kotlin.jpa</groupId>
<artifactId>zero</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<kotlin.version>1.3.50</kotlin.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
</parent>
<dependencies>
<!-- 使用 Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 使用 Spring Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Spring Boot Test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<!-- 排除 JUnit 4 -->
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 使用 JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<configuration>
<compilerPlugins>
<!--注意插件 jpa 不能缺少,非则会提示Entity缺少缺省构造函数-->
<plugin>jpa</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-noarg</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
3. 使用Kotlin数据类定义JPA实体
以上步骤完成后,现在可以通过Kotlin数据类定义JPA实体。在本文的例子中,我们定义了两个JPA实体:员工Employee和电话号码PhoneNumber , 如下所示 :
3.1 实体类
员工实体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等等在这里可以随意使用。
3.2. 存储库类
有了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> {
}
4. 应用程序入口
有了上面的实体类和相应的存储库,我们定义一个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, "张三", "zhang.san@test.com", null))
repository.save(Employee(0, "李四", "li.si@test.com", null))
repository.save(Employee(0, "王五", "wang.wu@test.com", 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=[])
5. 单元测试
这里我们制作一个基于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", "tom@test.com", Arrays.asList(PhoneNumber(0, "110"), PhoneNumber(0, "120")));
employeeRepository.save(tom);
val jerry = Employee(0, "Jerry", "jerry@test.com", Arrays.asList(PhoneNumber(0, "119"), PhoneNumber(0, "911")));
employeeRepository.save(jerry);
val andy = Employee(0, "Andy", "andy@test.com", 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
6. 总结
本文通过一个例子应用讲解了如何使用Kotlin制作一个Spring Boot JPA应用,演示了如下要点 :
- 依赖的引入
- 插件的引入
JPA实体的定义JPA存储库组件的定义SpringApplication入口应用程序的定义- 基于
JUnit 5的单元测试 - 基于构造函数的依赖注入
- 基于属性的依赖注入
@Autowired JPA实体对象的创建- 通过
JPA存储库插入或者查询实体对象

本文通过实例介绍如何使用Kotlin与SpringBoot JPA进行应用开发,涵盖依赖引入、实体类定义、存储库使用及JUnit5测试。
2220

被折叠的 条评论
为什么被折叠?



