11

I seem to have missed something in JPA's criteria API and its type safety. Consider the following code:

@Entity
@Access(FIELD)
class User(

  @Id
  Long id;

  @Column(unique=true)
  String email;

  String password;
}

Here the meta model:

@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;
}

Then some code to exercise the class, built using pages from 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());

It works fine. However, if I change the "where" clause to use an Integer instead of a String ("john@google.com"), I expected the code to not compile. Yet it compiles fine.

I thought the criteria API was supposed to be type safe? That is hardly more type safe than the following, using standard JPQL. I mean, what is the purpose of the meta model in the above code?? I have gained nothing from it.

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

So the question is: can I make the criteria API query more type safe, so that I can only pass a String to the "from" clause?

4

3 回答 3

10

类型安全仅限于接口泛型类型的上限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 创建者选择这些方法的通配符版本。

于 2012-08-21T21:59:39.350 回答
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()虽然 using和只有一个值的 -clause之间存在差异,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()-wrapper 或切换到in()您可能需要更频繁的toLong(),... 函数,因为-objects 不是很兼容强制转换,通常最好让数据库处理整数宽度的变化.CriteriaBuilderNumber

于 2014-02-20T23:06:38.100 回答
0

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

于 2012-08-21T20:17:45.817 回答