在 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) );

这两个查询都充满了非类型安全的调用,这些调用在执行查询之前无法进行验证。


回到顶部