在Kotlin中开始使用Spring Data JPA
Spring Data JPA是一套标准,定义了Java对象在数据库中的表现方式。JPA提供了一组注解和接口,使得配置和映射Java对象到关系数据库表成为可能。Java对象之间的关系是通过注释提供的(一对一、一对多、多对一、多对多)。
JPA规范的实现是由Hibernate等对象关系映射工具(ORM)提供的。JPA使得在不重构代码的情况下从一个ORM工具切换到另一个ORM工具更加容易,因为它抽象了各种ORM工具所涉及的复杂问题。
JPA位于ORM和应用层之间。在本教程中,我们将使用Spring Data和JPA对一个Recipe
应用程序进行建模。我们的应用程序的实体关系图如下所示。
前提条件
在我们开始之前,我们将需要以下条件。
- 在你的计算机上安装JDK。
- 最喜欢的IDE。
- 对[Java]和[Spring Boot]有一定了解。
创建应用程序
我们将使用[Spring initializr]来创建我们的应用程序。
- 在浏览器中打开[Spring initializr]。
- 选择Kotlin语言。
- 添加
Spring Web
,Spring Data JPA
, 和H2 Database
依赖项。 - 将其他配置保留为默认,然后点击生成项目。
- 解压下载的项目,在你喜欢的IDE中打开。
- 将该项目与maven同步,以下载所有的依赖项。
领域
领域包是我们定义模型的地方。
- 在存在
DemoApplication.kt
文件的根包中,创建一个新包,名称为domain
。 - 在你上面创建的
domain
包中,创建两个Kotlin文件,名称为Recipe.kt
和Ingredient.kt
。
JPA映射
有两种类型的JPA映射。
- 单向映射- 这是指JPA映射只在关系的一侧进行。如果实体A与实体B有一对多的关系,那么实体A上只有一对多的关系注解。
- 双向映射--这是在相关的两个实体上声明JPA映射的情况。如果实体A与实体B有一对多的关系,那么在实体A上使用一对多的注解,在实体B上使用多对一的注解。这种类型的映射被推荐,因为它使得在两个方向上浏览对象图成为可能。
JPA CASCADE类型
JPA CASCADE类型控制状态变化如何从父对象级联到子对象。
- PERSIST- 保存操作被级联到相关实体。
- MERGE- 如果拥有的实体被合并,相关的实体会被合并。
- REFRESH- 当拥有的实体被刷新时,相关实体被刷新。
- REMOVE- 当拥有的实体被删除时,删除所有的相关实体。
- DETACH- 如果发生手动分离,将分离所有相关的实体。
- ALL- 应用以上所有的级联选项。
JPA关系
- OneToMany关系在这种类型的JPA关系中,父实体中的一条记录被另一个实体中的许多子记录所引用。从我们上面的实体关系图中,我们可以看到
Recipe
实体与配料实体有一个OneToMany的关系,这意味着一份食谱能够有多个配料。
在我们之前创建的Recipe.kt
文件中,添加下面的代码片段。
import javax.persistence.*
@Entity
data class Recipe(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) //Uses underlying persistence framework to generate an Id
var id: Long?,
var description: String?,
var prepTime: String?,
var cookTime: String?,
var servings: String?,
var url: String?,
var directions: String?,
@OneToMany(cascade = [CascadeType.ALL], mappedBy = "recipe")
var ingredient: Set<Ingredient>?
)
@Entity
注解将 数据类标记为JPA实体,可以持久化到数据库中。Recipe
@Id
注释将 字段标记为将从 实体生成的数据库表的主表。id
Recipe
@GeneratedValue(strategy = GenerationType.IDENTITY)
注释将 字段设置为自动生成,并将 字段标记为唯一。id
GenerationType.IDENTITY
@OneToMany
annotation在 实体和 实体之间创建了一个OneToMany关系。 表明 实体中的 字段是 实体的外键。Recipe
Ingredient
mappedBy = "recipe"
Ingredient
recipe
Recipe
在Ingredient.kt
文件中,添加下面的片段。
@Entity
data class Ingredient(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long?,
val description: String?,
val amount: BigDecimal?,
@ManyToOne
val recipe: Recipe
)
@ManyToOne
注释创建了一个双向映射,使得在对象图中浏览 和 成为可能。Ingredient
Recipe
- OneToOne关系在这种类型的JPA关系中,一个实体只能属于另一个实体。在我们的
Recipe
实体中添加我们将要创建的Note
类型的notes
变量。
@Entity
data class Recipe(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) //Uses underlying persistence framework to generate an Id
var id: Long?,
var description: String?,
var prepTime: String?,
var cookTime: String?,
var servings: String?,
var url: String?,
var directions: String?,
@OneToMany(cascade = [CascadeType.ALL], mappedBy = "recipe")
var ingredient: Set<Ingredient>?,
@OneToOne(cascade = [CascadeType.ALL])
var notes: Notes?,//Foreign Key
)
@OneToOne
注解表示 实体将与 实体有一对一的关系。Notes
Recipe
在domain
包中,创建一个Kotlin文件,名称为Notes.kt
。在创建的Notes.kt
文件中,添加下面的代码片断。
@Entity
data class Notes(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long?,
@OneToOne
var recipe: Recipe?,
@Lob //Allows for more than 256 characters in the notes field as hibernate always limits the String field to 256 characters.
var notes: String?
)
@OneToOne
注解与 实体创建一个双向的映射关系。Recipe
- ManyToMany关系在这种类型的JPA关系中,一个实体的一条或多条记录与另一个实体的一条或多条记录相关联。
从我们的实体关系图中,我们看到Recipe
实体与Category
实体有一个ManyToMany的关系,这意味着一个菜谱可以属于许多类别,反之亦然。
在domain
包中创建一个Kotlin文件,名称为Category.kt
。在Category.kt
文件中添加下面的代码片段。
@Entity
data class Category(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long,
val name: String,
@ManyToMany(mappedBy = "category")
val recipe: Set<Recipe>
)
在Recipe
实体中,添加category
字段,并使用@ManyToMany
注解对其进行注释。
@Entity
data class Recipe(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) //Uses underlying persistence framework to generate an Id
var id: Long?,
var description: String?,
var prepTime: String?,
var cookTime: String?,
var servings: String?,
var url: String?,
var directions: String?,
@OneToMany(cascade = [CascadeType.ALL], mappedBy = "recipe")
var ingredient: Set<Ingredient>?,
@OneToOne(cascade = [CascadeType.ALL])
var notes: Notes?,//Foreign Key
@ManyToMany
@JoinTable(
name = "recipe_category",
joinColumns = [JoinColumn(name = "recipe_id")],
inverseJoinColumns = [JoinColumn(name = "category_id")]
)
val category: Set<Category>?
)
@JoinTable
注解生成了一个名为recipe_category
的表,该表将存储Recipe
和Category
的主键。生成的表有两列;recipe_id
,引用Recipe
表中的id;category_id
,引用Category
表中的id列。
- Enumerated用于在JPA中存储映射枚举值到数据库的表示。
在domain
包中创建一个Kotlin枚举类,名称为Difficulty
。将下面的代码片段添加到上面创建的枚举类中。
enum class Difficulty {
EASY, MODERATE, HARD
}
在Recipe
实体中添加难度类型的difficulty
字段,如下所示。
@Entity
data class Recipe(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) //Uses underlying persistence framework to generate an Id
var id: Long?,
var description: String?,
var prepTime: String?,
var cookTime: String?,
var servings: String?,
var url: String?,
var directions: String?,
@OneToMany(cascade = [CascadeType.ALL], mappedBy = "recipe")
var ingredient: Set<Ingredient>?,
@OneToOne(cascade = [CascadeType.ALL])
var notes: Notes?,//Foreign Key
@ManyToMany
@JoinTable(
name = "recipe_category",
joinColumns = [JoinColumn(name = "recipe_id")],
inverseJoinColumns = [JoinColumn(name = "category_id")]
)
val category: Set<Category>?,
@Enumerated(value = EnumType.STRING)
val difficulty: Difficulty,
)
@Enumerated(value = EnumType.STRING)
将困难字段设置为枚举。有两种枚举类型;EnumType.STRING
和EnumType.ORDINAL
。EnumType.ORDINAL
枚举值以整数形式存储,即: 为1, 为3,而 以字符串形式存储,即: 为EASY。EASY
HARD
EnumType.STRING
EASY
总结
现在你已经学会了如何使用Spring Data JPA对数据库进行建模,实现JPA存储库,然后为我们的食谱应用程序创建一个REST控制器。