使用 Spring Boot 和 Kotlin 进行条件查询和 JPA 元模型

JPA 2定义了一个类型安全的Criteria API,它允许使用所谓的JPA Metamodel构建条件查询。引入此功能是为了避免上述缺点,并提供类型安全和静态的方式来访问实体类的元数据。请注意,元模型生成的任务可以自动化。JBoss、EclipseLink、OpenJPA、DataNucleus只是可用于元模型生成的一些工具。

在本文中,我们将了解如何将这些元模型生成器工具之一集成到 Spring Boot 和 Kotlin 项目中。

规范元模型

元模型是一组描述域模型的对象
这个元模型在两个方面很重要。首先,它允许提供者和框架以通用方式处理应用程序的域模型
其次,从应用程序编写者的角度来看,它允许非常流畅地表达完全类型安全的条件查询。—  Hibernate 社区文档

JPA 2 (JSR 317) 规范中描述了元模型类的结构。如果您有兴趣详细了解元模型类的定义方式,我建议您首先阅读此文档页面。否则,请随意跳到下一章。

配置元模型生成工具

为了生成元模型类,我们将使用JBosshibernate-jpamodelgen提供的元模型生成器工具。

首先,我们需要将hibernate-jpamodelgen依赖项添加到build.gradle.kts文件中,以及kapt

plugins {
   kotlin("kapt") version "1.3.72"
}
dependencies {
    implementation ("org.hibernate:hibernate-jpamodelgen:5.4.12.Final")

    kapt("org.hibernate:hibernate-jpamodelgen:5.4.12.Final")   
}

_kapt_是Kotlin 注解处理工具hibernate-jpamodelgen,在构建时自动生成元模型类需要它。

JPA 元模型类

为了展示元模型类的样子,我们需要一个实体类。由于在 Kotlin 中定义 JPA 实体可能很棘手,因此我建议先阅读本文。

假设我们已经定义了Author实体类,如下所示:

@Entity
open class Author {

    @get:Id
    @get:GeneratedValue
    @get:Column(name = "id")
    open var id: Int? = null

    @get:Column(name = "name")
    open var name: String? = null

    @get:Column(name = "surname")
    open var surname: String? = null

    @get:Column(name = "birth_date")
    @get:Temporal(TemporalType.DATE)
    open var birthDate: Date? = null

    @get:ManyToOne(fetch = FetchType.LAZY)
    @get:JoinColumn(name = "country_id")
    open var country: Country? = null

    @get:ManyToMany(fetch = FetchType.LAZY)
    @get:JoinTable(
            name = "author_book",
            joinColumns = [JoinColumn(name = "author_id")],
            inverseJoinColumns = [JoinColumn(name = "book_id")]
    )
    open var books: MutableSet<Book> = HashSet()
}

默认情况下,对应的元模型类会被放置kaptbuild/generated/source/kapt/与对应的实体类相同的包中。

元模型类将在构建时自动生成,并且与实体同名,并在末尾添加“_”。因此,为该类生成的元模型类AuthorAuthor_如下所示:

@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(Author.class)
public abstract class Author_ {

    public static volatile SingularAttribute<Author, Integer> id;
    public static volatile SingularAttribute<Author, String> name;
    public static volatile SingularAttribute<Author, String> surname;
    public static volatile SingularAttribute<Author, Date> birthDate;
    public static volatile SingularAttribute<Author, Country> country;
    public static volatile SetAttribute<Author, Book> books;

    public static final String ID = "id";
    public static final String NAME = "name";
    public static final String SURNAME = "surname";
    public static final String BIRTH_DATE = "birthDate";
    public static final String COUNTRY = "country";
    public static final String BOOKS = "books";
}

正如您所知,生成的元模型类是一个 Java 类。这不是问题,因为Kotlin 与 Java 完全可互操作

JPA 元模型的实际应用

由于 Criteria API 提供了接受String引用以及Attribute接口实现的重载方法,因此我们可以像使用属性的String引用一样使用生成的元模型类。让我们编写一个条件查询来获取所有名为“John”的作者。

使用的条件查询如下所示Author_

// entityManager设置代码 ...

val criteriaBuilder = entityManager.criteriaBuilder
val criteriaQuery = criteriaBuilder.createQuery(Author::class.java)

// 检索所有名为“John”的作者
val root = criteriaQuery.from(Author::class.java)
criteriaQuery
    .select(root)
    .where(
        criteriaBuilder.equal(root.get(Author_.name), "John")
    )
val query = entityManager.createQuery(criteriaQuery)

val resultList = query.resultList

这就是不使用元模型类的情况:

// entityManager设置代码 ...

val criteriaBuilder = entityManager.criteriaBuilder
val criteriaQuery = criteriaBuilder.createQuery(Author::class.java)

// 检索所有名为“John”的作者
val root = criteriaQuery.from(Author::class.java)
criteriaQuery
    .select(root)
    .where(
        criteriaBuilder.equal(root.get<String>("name"), "John")
    )
val query = entityManager.createQuery(criteriaQuery)

val resultList = query.resultList

主要区别在于如何检索实体属性名称。在第一个片段中,我们使用Author_.name引用而不是传统的列名称。如果该属性发生更改,在第一个示例中我们将收到编译时错误,而在第二个示例中,将引发更危险的运行时错误。使用元模型类可以使代码更干净、更简单、更健壮。

JPA Metamodel 提供了一种类型安全的方式来定义条件查询。这使得未来的重构比通过字符串引用属性要容易得多,从而使代码对更改更加稳健。

许多不同的元模型生成器工具可以与注释处理器一起使用,以在构建时生成元模型类。这意味着实体属性的更改将自动反映在元模型类中,从而避免运行时错误。

© 版权声明
THE END
喜欢就支持一下吧
点赞0打赏 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容