如何在Kotlin中开始使用Spring Data JPA

789 阅读6分钟

在Kotlin中开始使用Spring Data JPA

Spring Data JPA是一套标准,定义了Java对象在数据库中的表现方式。JPA提供了一组注解和接口,使得配置和映射Java对象到关系数据库表成为可能。Java对象之间的关系是通过注释提供的(一对一、一对多、多对一、多对多)。

JPA规范的实现是由Hibernate等对象关系映射工具(ORM)提供的。JPA使得在不重构代码的情况下从一个ORM工具切换到另一个ORM工具更加容易,因为它抽象了各种ORM工具所涉及的复杂问题。

JPA位于ORM和应用层之间。在本教程中,我们将使用Spring Data和JPA对一个Recipe 应用程序进行建模。我们的应用程序的实体关系图如下所示。

Entity Diagram

前提条件

在我们开始之前,我们将需要以下条件。

  1. 在你的计算机上安装JDK。
  2. 最喜欢的IDE。
  3. 对[Java]和[Spring Boot]有一定了解。

创建应用程序

我们将使用[Spring initializr]来创建我们的应用程序。

  1. 在浏览器中打开[Spring initializr]。
  2. 选择Kotlin语言。
  3. 添加Spring Web,Spring Data JPA, 和H2 Database 依赖项。
  4. 将其他配置保留为默认,然后点击生成项目。
  5. 解压下载的项目,在你喜欢的IDE中打开。
  6. 将该项目与maven同步,以下载所有的依赖项。

领域

领域包是我们定义模型的地方。

  • 在存在DemoApplication.kt 文件的根包中,创建一个新包,名称为domain
  • 在你上面创建的domain 包中,创建两个Kotlin文件,名称为Recipe.ktIngredient.kt

JPA映射

有两种类型的JPA映射。

  1. 单向映射- 这是指JPA映射只在关系的一侧进行。如果实体A与实体B有一对多的关系,那么实体A上只有一对多的关系注解。
  2. 双向映射--这是在相关的两个实体上声明JPA映射的情况。如果实体A与实体B有一对多的关系,那么在实体A上使用一对多的注解,在实体B上使用多对一的注解。这种类型的映射被推荐,因为它使得在两个方向上浏览对象图成为可能。

JPA CASCADE类型

JPA CASCADE类型控制状态变化如何从父对象级联到子对象。

  1. PERSIST- 保存操作被级联到相关实体。
  2. MERGE- 如果拥有的实体被合并,相关的实体会被合并。
  3. REFRESH- 当拥有的实体被刷新时,相关实体被刷新。
  4. REMOVE- 当拥有的实体被删除时,删除所有的相关实体。
  5. DETACH- 如果发生手动分离,将分离所有相关的实体。
  6. ALL- 应用以上所有的级联选项。

JPA关系

  1. 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
  1. 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
  1. 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 的表,该表将存储RecipeCategory 的主键。生成的表有两列;recipe_id ,引用Recipe 表中的id;category_id ,引用Category 表中的id列。
  1. 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.STRINGEnumType.ORDINAL
  • EnumType.ORDINAL 枚举值以整数形式存储,即: 为1, 为3,而 以字符串形式存储,即: 为EASY。EASY HARD EnumType.STRING EASY

总结

现在你已经学会了如何使用Spring Data JPA对数据库进行建模,实现JPA存储库,然后为我们的食谱应用程序创建一个REST控制器。