在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注释将 字段标记为将从 实体生成的数据库表的主表。idRecipe@GeneratedValue(strategy = GenerationType.IDENTITY)注释将 字段设置为自动生成,并将 字段标记为唯一。idGenerationType.IDENTITY@OneToManyannotation在 实体和 实体之间创建了一个OneToMany关系。 表明 实体中的 字段是 实体的外键。RecipeIngredientmappedBy = "recipe"IngredientrecipeRecipe
在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注释创建了一个双向映射,使得在对象图中浏览 和 成为可能。IngredientRecipe
- 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注解表示 实体将与 实体有一对一的关系。NotesRecipe
在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。EASYHARDEnumType.STRINGEASY
总结
现在你已经学会了如何使用Spring Data JPA对数据库进行建模,实现JPA存储库,然后为我们的食谱应用程序创建一个REST控制器。