编程

使用 JPA 静态元模型创建类型安全的查询

483 2024-07-25 01:24:00

编写条件查询或创建动态实体图时,需要引用实体类及其属性。最快捷、最简单的方法是将所需的名称作为字符串提供。但这有几个缺点,例如,在编写查询时,必须记住或查找实体属性的所有名称。但是,如果必须重构实体并修改某些属性的名称,那么在项目的后期阶段也会导致更大的问题。在这种情况下,必须使用 IDE 的搜索功能,并尝试查找引用修改后的属性的所有字符串。这是一个乏味且容易出错的活动,很容易占用重构的大部分时间。

因此,我更喜欢使用静态元模型来编写条件查询和动态实体图。这是 JPA 规范定义的一个小特性,它提供了一种类型安全的方式来引用实体及其属性。

实体示例

如下代码所示,我为该示例准备了一个简单的实体。它代表一个有 id、名字和姓氏的作者(Author),以及她/他写过的书的列表(List)。此处我跳过 Book 实体,因为下面的例子不需要它。

@Entity

public class Author implements Serializable {

    @Id

    @GeneratedValue(strategy = GenerationType.AUTO)

    @Column(name = "id", updatable = false, nullable = false)

    private Long id;

     

    @Version

    @Column(name = "version")

    private int version;

    @Column

    private String firstName;

    @Column

    private String lastName;

     

    @ManyToMany(mappedBy="authors")

    private Set<Book> books = new HashSet<Book>();

     

    ...

}

静态元模型类

静态元模型的类看起来与实体相似。基于 JPA 规范,持久化单元中的每个托管类都有一个相应的元模型类。你可以在同一个包中找到它,它与相应的托管类具有相同的名称,并在末尾添加了 “_”。因此,本例中的元模型类位于包  org.thoughts.on.java.model 中,名称为 Author_

@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")

@StaticMetamodel(Author.class)

public abstract class Author_ {

    public static volatile SingularAttribute<Author, String> firstName;

    public static volatile SingularAttribute<Author, String> lastName;

    public static volatile SetAttribute<Author, Book> books;

    public static volatile SingularAttribute<Author, Long> id;

    public static volatile SingularAttribute<Author, Integer> version;

}

正如源代码所示,元模型类 Author_Author 实体的每个属性提供了一个属性。每个元模型属性都提供有关其类型及其所属实体的信息。

使用元模型类

使用元模型类的方式与使用实体和属性的 String 引用的方式相同。标准查询和动态实体图的 API 提供了接受字符串和 Attribute 接口实现的重载方法。

我使用元模型类创建一个条件查询来搜索所有名字以“J”开头的作者(Author)。如你所见,我使用与处理实体属性的 String 引用相同的方法。

CriteriaBuilder cb = this.em.getCriteriaBuilder();

// create the query

CriteriaQuery<Author> q = cb.createQuery(Author.class);

// set the root class

Root<Author> a = q.from(Author.class);

// use metadata class to define the where clause

q.where(cb.like(a.get(Author_.firstName), "J%"));

// perform query

this.em.createQuery(q).getResultList();

如前所述,元模型类可用于创建动态实体图。如下:

// create the entity graph

EntityGraph graph = this.em.createEntityGraph(Author.class);

// use metadata class to define the subgraph

Subgraph<Book> bookSubGraph = graph.addSubgraph(Author_.books);

// perform query

List<Author> authors = this.em

                .createQuery("SELECT DISTINCT a FROM Author a", Author.class)

                .setHint("javax.persistence.fetchgraph", graph).getResultList();

生成元模型类

JPA 规范建议使用注释处理器(processor)来生成元模型类,而不同的实现就是这样做的。不幸的是,每个实现都提供了自己的方法。

我在下面描述 Hibernate 所需的 maven 构建配置。如果使用不同的 JPA 实现(例如 EclipseLinkOpenJPA)或构建工具,请查看相应的文档。

将 Hibernate 静态元模型生成器添加到构建过程中非常简单。只需将它添加到构建类路径中。下面的代码显示了 maven 构建所需的依赖项声明。

...

<dependencies>

    <dependency>

        <groupId>org.hibernate</groupId>

        <artifactId>hibernate-jpamodelgen</artifactId>

    </dependency>

</dependencies>

...

如果使用 maven 生成的元模型保存在 target/generated-classes 目录下。因此你需要在 IDE 中将此目录添加到 classpath 定义。

结论

静态元模型提供了一种类型安全且简单的方法来创建标准查询和动态实体图。这加快了初始实现的速度,并使未来的重构比通过字符串引用属性更容易。

不同的持久化提供者的实现提供注释处理器,以便在构建时生成元模型类。这样可以确保实体上的更改反映在元模型上,并避免运行时出现错误。