详解JPA 2.0动态查询机制:Criteria API(3)

来源:互联网 发布:数据库 asc 编辑:程序博客网 时间:2024/04/20 03:06

详解JPA 2.0动态查询机制:Criteria API(3)

2009-11-13 09:24 Pinaki Poddar IBMDW 字号:T | T
一键收藏,随时查看,分享好友!

JPA 2.0引入了 Criteria API,这个 API 首次将类型安全查询引入到 Java 应用程序中,并为在运行时动态地构造查询提供一种机制。本文介绍如何使用 Criteria API 和与之密切相关的 Metamodel API 编写动态的类型安全查询。

AD:干货来了,不要等!WOT2015 北京站演讲PPT开放下载!

高级特性

到目前为止,我主要强调了 Criteria API 的强类型,以及它如何帮助减少出现在基于字符串 JPQL 查询中的语义错误。Criteria API 还是以编程的方式构建查询的机制,因此通常被称为动态 查询 API。编程式查询构造 API 的威力是无穷的,但它的利用还取决于用户的创造能力。我将展示 4 个例子:

  • 使用弱类型的 API 构建动态查询
  • 使用数据库支持的函数作为查询表达式来扩展语法
  • 编辑查询实现 “在结果中搜索” 功能
  • 根据例子进行查询 — 数据库社区熟悉的模式

弱类型和动态查询构建

Criteria API 的强类型检查基于开放期间的实例化元模型类的可用性。不过,在某些情况下,选择的实体仅能够在运行时决定。为了支持这种用法,Criteria API 方法提供一个并列版本,其中持久化属性通过它们的名称进行引用(类似于 Java Reflection API),而不是引用实例化静态元模型属性。该 API 的这个并列版本可以通过牺牲编译时类型检查来真正地支持动态查询构造。清单 19 使用弱类型 API 重新编写了 清单 6 中的代码:


清单 19. 弱类型查询

Class< Account> cls =Class.forName("domain.Account");Metamodel model = em.getMetamodel();EntityType< Account> entity = model.entity(cls); CriteriaQuery< Account> c = cb.createQuery(cls);Root< Account> account = c.from(entity);Path< Integer> balance = account.< Integer>get("balance");c.where(cb.and       (cb.greaterThan(balance, 100),         cb.lessThan(balance), 200)));

不过,弱类型 API 不能够返回正确的泛型表达式,因此生成一个编辑器来警告未检查的转换。一种消除这些烦人的警告消息的方法是使用 Java 泛型不常用的工具:参数化方法调用,比如 清单 19 中通过调用 get() 方法获取路径表达式。

可扩展数据库表达式

动态查询构造机制的独特优势是它的语法是可扩展的。例如,您可以在 QueryBuilder 接口中使用 function() 方法创建数据库支持的表达式:

< T> Expression< T> function(String name, Class< T> type, Expression< ?>...args);

function() 方法创建一个带有给定名称和 0 个或多个输入表达式的表达式。function() 表达式的计算结果为给定的类型。这允许应用程序创建一个计算数据库的查询。例如,MySQL 数据库支持 CURRENT_USER() 函数,它为服务器用于验证当前客户机的 MySQL 帐户返回一个由用户名和主机名组成的 UTF-8 字符串。应用程序可以在 CriteriaQuery 中使用未带参数的CURRENT_USER() 函数,如清单 20 所示:


清单 20. 在 CriteriaQuery 中使用特定于数据库的函数

CriteriaQuery< Tuple> q = cb.createTupleQuery();Root< Customer> c = q.from(Customer.class);Expression< String> currentUser =     cb.function("CURRENT_USER", String.class, (Expression< ?>[])null);q.multiselect(currentUser, c.get(Customer_.balanceOwed));

注意,在 JPQL 中不能表达等效的查询,因为它的语法仅支持固定数量的表达式。动态 API 不受固定数量表达式的严格限制。

可编辑查询

可以以编程的方式编辑 CriteriaQuery。可以改变查询的子句,比如它的选择条件、WHERE子句中的选择谓词和 ORDER BY 子句中的排序条件。可以在典型的 “在结果中搜索” 工具中使用这个编辑功能,以添加更多限制在后续步骤中进一步细化查询谓词。

清单 21 中的例子创建了一个根据名称对结果进行排序的查询,然后编辑该查询以根据邮政编码进行查询:


清单 21. 编辑 CriteriaQuery

CriteriaQuery< Person> c = cb.createQuery(Person.class);Root< Person> p = c.from(Person.class);c.orderBy(cb.asc(p.get(Person_.name)));List< Person> result = em.createQuery(c).getResultList();// start editingList< Order> orders = c.getOrderList();List< Order> newOrders = new ArrayList< Order>(orders);newOrders.add(cb.desc(p.get(Person_.zipcode)));c.orderBy(newOrders);List< Person> result2 = em.createQuery(c).getResultList();

 

在 CriteriaQuery 上的 setter 方法 —select()where() 或 orderBy() — 使用新的参数替换先前的值。对应的 getter 方法(比如getOrderList())返回的列表不是活动的,即在返回列表上添加或删除元素不会导致修改CriteriaQuery;另外,一些供应商甚至返回不可变的列表以阻止意外使用。因此,良好的实践是在添加和删除新的表达式之前,将返回列表复制到一个新的列表中。

根据例子进行查询

动态查询 API 中的另一个有用特性就是它能够轻松地支持根据例子进行查询。根据例子进行查询(由 IBM® Research 在 1970 年开发出来)通常被作为早期的软件终端用户可用性例子引用。根据例子进行查询的理念使用模板实例,而不是为查询指定精确的谓词。有了给定的模板实例之后,将创建一个联合谓词,其中每个谓词都是模板实例的非 null 和非默认属性值。执行该查询将计算谓词以查找所有与模板实例匹配的实例。根据例子进行查询曾考虑添加到 JPA 2.0 中,但最终没有添加。OpenJPA 通过它的扩展 OpenJPAQueryBuilder 接口支持这种查询,如清单 22 所示:


清单 22. 使用 OpenJPA 的 CriteriaQuery 根据例子进行查询

CriteriaQuery< Employee> q = cb.createQuery(Employee.class);Employee example = new Employee();example.setSalary(10000);example.setRating(1);q.where(cb.qbe(q.from(Employee.class), example);

如这个例子所示,OpenJPA 的 QueryBuilder 接口扩展支持以下表达式:

public < T> Predicate qbe(From< ?, T> from, T template);

这个表达式根据给定模板实例的属性值生成一个联合谓词。例如,这个查询将查询所有薪水为 10000 评级为 1 的 Employee。要进一步控制比较,可以指定不用于比较的可选属性,以及为值为 String 的属性指定比较方式。

结束语

本文介绍了 JPA 2.0 中的新 Criteria API,它是一个用 Java 语言开发动态、类型安全的查询的机制。CriteriaQuery 在运行时被构建为一个强类型查询表达式树,本文通过一系列例子展示了它的用法。

本文还确立了 Metamodel API 的关键角色,并展示了实例化元模型类如何使编译器能够检查查询的正确性,从而避免语法有误的 JPQL 查询引起的运行时错误。除了保证语法正确之外,JPA 2.0 以编程的方式构造查询的特性还能通过数据库函数实现更多强大的用途,比如通过例子进行查询。我希望本文的读者能够发现这些强大的新 API 的其他新用途。

【责任编辑:杨赛 TEL:(010)68476606】

0 0