在本教程中,我们将讨论不同类型的JPA查询。此外,我们将专注于比较它们之间的差异,并扩展每个它们的优缺点。
首先,让我们定义将用于本文所有示例的UserEntity类:
- @Table(name = "users")
- @Entity
- public class UserEntity {
- @Id
- private Long id;
- private String name;
- //Standard constructor, getters and setters.
- }
JPA 查询有三种基本类型:
让我们探索它们。
查询在语法上类似于 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();
- }
此查询从用户表中检索匹配的记录,并将其映射到用户实体对象。
还有两个额外的查询子类型:
让我们看看它们的实际效果。
我们需要注意前面示例中的return语句。JPA 无法推断查询结果类型是什么,因此,我们必须强制转换。
但是,JPA 提供了一个特殊的 Query 子类型,称为TypedQuery。如果我们事先知道查询结果类型,则始终首选此操作。此外,它使我们的代码更可靠,更易于测试。
让我们看一个TypedQuery替代方案,与我们的第一个示例相比:
- public UserEntity getUserByIdWithTypedQuery(Long id) {
- TypedQuery
typedQuery - = getEntityManager().createQuery("SELECT u FROM UserEntity u WHERE u.id=:id", UserEntity.class);
- typedQuery.setParameter("id", id);
- return typedQuery.getSingleResult();
- }
这样,我们可以免费获得更强的键入,避免将来可能的转换异常。
虽然我们可以在特定方法上动态定义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();
- }
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 语法,那么切换提供程序应该不是问题。
到目前为止,我们已经了解了Query,NamedQuery和NativeQuery。
现在,让我们快速重新审视它们并总结它们的优缺点。
我们可以使用entityManager.createQuery(queryString) 创建一个查询。
接下来,让我们探讨一下查询的优缺点:
优点:
缺点:
一旦定义了NamedQuery,我们就可以使用EntityManager 引用它:
entityManager.createNamedQuery(queryName);
现在,让我们看看命名查询的优缺点:
优点:
缺点:
我们可以使用EntityManager 创建一个NativeQuery:
entityManager.createNativeQuery(sqlStmt);
根据结果映射,我们还可以将第二个参数传递给方法,例如Entity类,正如我们在前面的示例中所看到的那样。
NativeQuery也有优点和缺点。让我们快速看一下它们:
优点:
缺点:
条件API查询是以编程方式构建的类型安全查询,在语法上有点类似于 JPQL 查询:
- public UserEntity getUserByIdWithCriteriaQuery(Long id) {
- CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
- CriteriaQuery
criteriaQuery = criteriaBuilder.createQuery(UserEntity.class); - Root
userRoot = criteriaQuery.from(UserEntity.class); - UserEntity queryResult = getEntityManager().createQuery(criteriaQuery.select(userRoot)
- .where(criteriaBuilder.equal(userRoot.get("id"), id)))
- .getSingleResult();
- return queryResult;
- }
直接使用标准API 查询可能令人生畏,但当我们需要添加动态查询元素或与JPA元模型结合使用时,它们可能是一个很好的选择。
在这篇快速文章中,我们了解了什么是 JPA 查询及其用法。
JPA 查询是从数据访问层抽象业务逻辑的好方法,因为我们可以依赖 JPQL 语法,并让我们选择的 JPA 提供程序处理查询转换。
本文中介绍的所有代码都可以在 GitHub 上找到。