在 JPA 群组中,关于我的 类型安全条件查询提议 已经有很多讨论。我 最爱的 Java 语言新特性 是javax.annotation.Processor。Java 6 注解处理器源于 JDK 5 中存在的 APT 工具,但已集成到javac中。实际上,"注解处理器" 这个名字有些误导,因为这个特性与注解只有偶然的关系。这个Processor实际上是一个相当通用的编译器插件。如果你像我一样,以前不是代码生成的粉丝,现在是时候重新考虑了。Java 6Processor可以
- 分析正在编译的 Java 源代码的编译器元模型
- 在源路径中搜索其他元数据,例如 XML
- 生成新类型,这些类型也将被编译,或生成其他文件
最好的是,这个功能不需要特殊的工具或命令行选项javac。你所需要做的只是将包含你的Processor的 jar 文件放入类路径中,编译器会完成其余工作!
在类型安全的查询 API 中,我想使用这个功能来解决 Java 缺乏类字段和方法类型安全的元模型的问题。基本思路是编译器插件将为应用程序中的每个持久化类生成一个 元模型类型
。
假设我们有一个以下持久化类
@Entity public class Order { @Id long id; boolean filled; Date date; @OneToMany Set<Item> items; @ManyToOne Shop shop; //getters and setters... }
那么将生成一个名为Order_的类,其中包含Order的每个持久化属性的静态成员,应用程序可以使用这些成员在查询中引用属性。
经过几次迭代后,我们已确定以下生成类型的格式
import javax.jpa.metamodel.Attribute; import javax.jpa.metamodel.Set; import javax.jpa.metamodel.Metamodel; @Metamodel public abstract class Order_ { public static Attribute<Order, Long> id; public static Attribute<Order, Boolean> filled; public static Attribute<Order, Date> date; public static Set<Order, Item> items; public static Attribute<Order, Shop> shop; }
JPA提供者负责在持久化单元初始化时初始化这些成员的值。
现在,查询条件将类似于以下内容
Root<Item> item = q.addRoot(Item.class); Path<String> shopName = item.get(Item_.order) .get(Order_.shop) .get(Shop_.name); q.select(item) .where( qb.equal(shopName, "amazon.com") );
这相当于
select item from Item item where item.shop.name = 'amazon.com'
或者像这样
Root<Order> order = q.addRoot(Order.class); Join<Item, Product> product = order.join(Order_.items) .join(Item_.product); Path<BigDecimal> price = product.get(Product_.price); Path<Boolean> filled = order.get(Order_.filled); Path<Date> date = order.get(Order_.date); q.select(order, product) .where( qb.and( qb.gt(price, 100.00), qb.not(filled) ) ) .order( qb.ascending(price), qb.descending(date) );
这相当于
select order, product from Order order join order.items item join item.product product where product.price > 100 and not order.filled order by product.price asc, order.date desc
查询几乎完全类型安全。因为属性:
- 我不能传递一个Order到连接或路径它表示一个项目,并且
- 我不能尝试进行比较,比如gt()表示一个路径表示一个布尔属性,或者not()表示一个路径表示一个类型为日期.
专家小组中有一些怀疑者,但我的感觉是,一旦人们习惯了这种想法,即类型生成不再是开发过程中阻碍你的事情,我们将看到更多使用这种方法的框架。我确实认为这个API比之前的提案有了很大的改进。
Root item = q.addRoot(Item.class); Path shopName = item.get("order") .get("shop") .get("name"); q.select(item) .where( qb.equal(shopName, "amazon.com") );
或者
Root order = q.addRoot(Order.class); Join product = order.join("items") .join("product"); Path price = product.get("price"); Path filled = order.get("filled"); Path date = order.get("date"); q.select(order, product) .where( qb.and( qb.gt(price, 100.00), qb.not(filled) ) ) .order( qb.ascending(price), qb.descending(date) );
这两个查询都充满了非类型安全的调用,这些调用在执行查询之前无法进行验证。