Search code examples
javajpacriteria-apitype-safety

JPA Criteria API and type safety


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?


Solution

  • Type safety is restricted to the upper bound of the generic type of the Expression<T> interface, not the exact type defined in the metamodel. So, because CriteriaBuilder.equal(Expression<?> x, java.lang.Object y) takes an argument of type Expression<?> it allows to pass any object for comparison.

    The other CriteriaBuiler methods are more type safe, for example CriteriaBuilder.ge(Expression<? extends java.lang.Number> x, Expression<? extends java.lang.Number> y) allows only numbers. But allows to compare an integer field with a float for example.

    You can't do better than this. The methods should have been something like CriteriaBuilder.equal(Expression<T> x, T y) with T being the field type from the metamodel.

    Of course this is a hole in the type safety of the criteria API. I don't know why the JPA API creators choosed the wildcard version of these methods.