JPA Criteria API和类型安全

时间:2012-08-21 19:51:37

标签: java jpa criteria-api type-safety

我似乎错过了JPA的标准API及其类型安全性。请考虑以下代码:

@Entity
@Access(FIELD)
class User(

  @Id
  Long id;

  @Column(unique=true)
  String email;

  String password;
}

这是元模型:

@StaticMetamodel(User.class)
public static class User_ {
  public static volatile SingularAttribute<User, Long> id;
  public static volatile SingularAttribute<User, String> email;
  public static volatile SingularAttribute<User, String> password;
}

然后使用the Java EE Tutorial中的页面构建一些代码来运用该类:

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> user = cq.from(User.class);
cq.select(user);
cq.where(cb.equal(user.get(User_.email), "john@google.com")); //this line is my problem

TypedQuery<User> q = em.createQuery(cq);
List<User> allUsers = q.getResultList();

assertEquals(1, allUsers.size());

工作正常。但是,如果我更改“where”子句以使用Integer而不是String(“john@google.com”),我希望代码不能编译。但它编译得很好。

我认为标准API应该是类型安全的吗?使用标准JPQL,这几乎不比以下更安全。我的意思是,上面代码中元模型的目的是什么?我从中获得了一切。

User u = em.createQuery("select u from User u where u.email = :email", User.class)
           .setParameter("email", "john@google.com")
       .getSingleResult();

所以问题是:我可以使条件API查询更安全,因此我只能将字符串传递给“from”子句吗?

3 个答案:

答案 0 :(得分:8)

类型安全性仅限于Expression<T>接口的泛型类型的上限,而不是元模型中定义的确切类型。所以,因为 CriteriaBuilder.equal(Expression<?> x, java.lang.Object y)采用Expression<?>类型的参数,它允许传递任何对象进行比较。

其他CriteriaBuiler方法更安全,例如CriteriaBuilder.ge(Expression<? extends java.lang.Number> x, Expression<? extends java.lang.Number> y)只允许数字。但允许比较整数字段和浮点数。

你不能比这更好。方法应该类似于CriteriaBuilder.equal(Expression<T> x, T y),其中T是元模型中的字段类型。

当然,这是标准API的类型安全漏洞。我不知道为什么JPA API创建者选择了这些方法的通配符版本。

答案 1 :(得分:3)

如果您真的想要严格的类型匹配,可以使用in(),您的查询将如下所示:

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> user = cq.from(User.class);
cq.select(user);
cq.where(cb.in(user.get(User_.email)).value("john@google.com"));

TypedQuery<User> q = em.createQuery(cq);
List<User> allUsers = q.getResultList();

assertEquals(1, allUsers.size());

或者你甚至可以写

cq.where(cb.in(user.get(User_.email)).value(cq.literal("john@google.com")));

虽然使用equal()in() - 子句只有一个值之间存在差异,但这会根据您的静态类型模型实现严格的输入。大多数数据库引擎会以相同的方式使用一个值优化=in()

如果您不想为equal()切换in(),您还可以编写这样的函数,以确保编译器提醒您可能存在错误输入

static <T> Predicate equal(CriteriaBuilder cb, Expression<T> left, T right) {
    return cb.equal(left, right);
}

static <T> Predicate equal(CriteriaBuilder cb, Expression<T> left, Expression<T> right) {
    return cb.equal(left, right);
}

最后,您希望使用自己的equal() - 包装器或切换到in(),您可能需要更toLong()的{​​{1}} ...函数作为CriteriaBuilder - 对象不是非常兼容的,通常最好让数据库处理整数宽度的变化。

答案 2 :(得分:0)

cq.where(booelan,String) Java提交“将数字隐式类型转换为字符串”。 直接输入整数来初始化方法String类型参数,因此它是一个合法的字符串。 围绕语言中唯一可能发生问题的地方和方式。您将只需要首先检查地址语法的有效性,并考虑如何处理这样的问题。