1. 概述

在本教程中,我们将讨论不同类型的JPA查询。此外,我们将专注于比较它们之间的差异,并扩展每个它们的优缺点。

2. 设置

首先,让我们定义将用于本文所有示例的UserEntity类:

@Table(name = "users")
@Entity
public class UserEntity {

    @Id
    private Long id;
    private String name;
    //Standard constructor, getters and setters.

}

JPA 查询有三种基本类型:

  • Query,用 Java 持久性查询语言 (JPQL) 语法编写
  • NativeQuery,用纯SQL语法编写
  • 标准 API 查询,通过不同方法以编程方式构造

让我们探索它们。

3.查询

查询在语法上类似于 SQL,通常用于执行 CRUD 操作:

public UserEntity getUserByIdWithPlainQuery(Long id) {
    Query jpqlQuery = getEntityManager().createQuery("SELECT u FROM UserEntity u WHERE u.id=:id");
    jpqlQuery.setParameter("id", id);
    return (UserEntity) jpqlQuery.getSingleResult();
}

此查询从用户表中检索匹配的记录,并将其映射到用户实体对象。

还有两个额外的查询子类型:

  • 类型化查询
  • 命名查询

让我们看看它们的实际效果。

3.1.类型查询

我们需要注意前面示例中的return语句。JPA 无法推断查询结果类型是什么,因此,我们必须强制转换。

但是,JPA 提供了一个特殊的 Query 子类型,称为TypedQuery。如果我们事先知道查询结果类型,则始终首选此操作。此外,它使我们的代码更可靠,更易于测试。

让我们看一个TypedQuery替代方案,与我们的第一个示例相比:

public UserEntity getUserByIdWithTypedQuery(Long id) {
    TypedQuery<UserEntity> typedQuery
      = getEntityManager().createQuery("SELECT u FROM UserEntity u WHERE u.id=:id", UserEntity.class);
    typedQuery.setParameter("id", id);
    return typedQuery.getSingleResult();
}

这样,我们可以免费获得更强的键入,避免将来可能的转换异常。

3.2.命名查询

虽然我们可以在特定方法上动态定义Query,但它们最终会发展成为难以维护的代码库。如果我们可以将一般用法查询保存在一个集中的、易于阅读的地方会怎样?

JPA还让我们用另一个称为NamedQuery的Query子类型来解决这个问题。

我们可以在orm.xml或属性文件中定义NamedQuery。

此外,我们可以在实体类本身上定义NamedQuery,从而提供一种集中、快速且简单的方法来读取和查找实体的相关查询。

所有命名查询都必须具有唯一的名称。

让我们看看如何将NamedQuery添加到我们的UserEntity类中:

@Table(name = "users")
@Entity
@NamedQuery(name = "UserEntity.findByUserId", query = "SELECT u FROM UserEntity u WHERE u.id=:userId")
public class UserEntity {

    @Id
    private Long id;
    private String name;
    //Standard constructor, getters and setters.

}

如果我们在版本 8 之前使用 Java,@NamedQuery注解必须分组在@NamedQueries注解中。从 Java 8 开始,我们可以简单地在Entity类中重复@NamedQuery注释。

使用NamedQuery非常简单:

public UserEntity getUserByIdWithNamedQuery(Long id) {
    Query namedQuery = getEntityManager().createNamedQuery("UserEntity.findByUserId");
    namedQuery.setParameter("userId", id);
    return (UserEntity) namedQuery.getSingleResult();
}

4.原生查询

NativeQuery只是一个SQL查询。这些使我们能够释放数据库的全部功能,因为我们可以使用 JPQL 限制语法中不可用的专有功能。

这是有代价的。我们使用NativeQuery失去了应用程序的数据库可移植性,因为我们的 JPA 提供程序无法再从数据库实现或供应商中抽象出特定细节。

让我们看看如何使用产生与前面示例相同的结果的NativeQuery:

public UserEntity getUserByIdWithNativeQuery(Long id) {
    Query nativeQuery
      = getEntityManager().createNativeQuery("SELECT * FROM users WHERE id=:userId", UserEntity.class);
    nativeQuery.setParameter("userId", id);
    return (UserEntity) nativeQuery.getSingleResult();
}

我们必须始终考虑NativeQuery是否是唯一的选择。大多数时候,一个好的JPQL查询可以满足我们的需求,最重要的是,从实际的数据库实现中保持一个抽象级别。

使用NativeQuery并不一定意味着将我们锁定在一个特定的数据库供应商上。毕竟,如果我们的查询不使用专有的 SQL 命令并且只使用标准的 SQL 语法,那么切换提供程序应该不是问题。

5.查询、命名查询和本机查询

到目前为止,我们已经了解了Query,NamedQuery和NativeQuery。

现在,让我们快速重新审视它们并总结它们的优缺点。

5.1.查询

我们可以使用entityManager.createQuery(queryString) 创建一个查询。

接下来,让我们探讨一下查询的优缺点:

优点:

  • 当我们使用EntityManager 创建查询时,我们可以构建动态查询字符串
  • 查询是用 JPQL 编写的,因此它们是可移植的

缺点:

  • 对于动态查询,可能会根据查询计划缓存将其多次编译为本机 SQL 语句
  • 查询可能分散到各种 Java 类中,并与 Java 代码混合在一起。因此,如果项目包含许多查询,则可能难以维护

5.2.命名查询

一旦定义了NamedQuery,我们就可以使用EntityManager 引用它:

entityManager.createNamedQuery(queryName);

现在,让我们看看命名查询的优缺点:

优点:

  • 在加载持久性单元时编译和验证命名查询。也就是说,它们只编译一次
  • 我们可以集中NamedQuery以使它们更易于维护 - 例如,在orm.xml、属性文件中或 on@Entity类中

缺点:

  • 命名查询始终是静态的
  • 命名查询可以在 Spring Data JPA 存储库中引用。但是,不支持动态排序

5.3.本机查询

我们可以使用EntityManager 创建一个NativeQuery:

entityManager.createNativeQuery(sqlStmt);

根据结果映射,我们还可以将第二个参数传递给方法,例如Entity类,正如我们在前面的示例中所看到的那样。

NativeQuery也有优点和缺点。让我们快速看一下它们:

优点:

  • 随着我们的查询变得复杂,有时 JPA 生成的 SQL 语句并不是最优化的。在这种情况下,我们可以使用NativeQuery来提高查询效率
  • NativeQuery允许我们使用特定于数据库供应商的功能。有时,这些功能可以为我们的查询提供更好的性能

缺点:

  • 特定于供应商的功能可以带来便利和更好的性能,但我们通过失去从一个数据库到另一个数据库的可移植性来支付这种好处。

6.标准接口查询

条件API查询是以编程方式构建的类型安全查询,在语法上有点类似于 JPQL 查询:

public UserEntity getUserByIdWithCriteriaQuery(Long id) {
    CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
    CriteriaQuery<UserEntity> criteriaQuery = criteriaBuilder.createQuery(UserEntity.class);
    Root<UserEntity> userRoot = criteriaQuery.from(UserEntity.class);
    UserEntity queryResult = getEntityManager().createQuery(criteriaQuery.select(userRoot)
      .where(criteriaBuilder.equal(userRoot.get("id"), id)))
      .getSingleResult();
    return queryResult;
}

直接使用标准API 查询可能令人生畏,但当我们需要添加动态查询元素或与JPA元模型结合使用时,它们可能是一个很好的选择。

7. 结论

在这篇快速文章中,我们了解了什么是 JPA 查询及其用法。

JPA 查询是从数据访问层抽象业务逻辑的好方法,因为我们可以依赖 JPQL 语法,并让我们选择的 JPA 提供程序处理查询转换。

本文中介绍的所有代码都可以在 GitHub 上找到。