I have a scenario where i use a custom user type for a entity field in hibernate using the org.hibernate.usertype.UserType.
It's a date conversion to UTC
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;
public final class UTCTimestampType implements ParameterizedType, UserType
{..}
My entity class is like the below
@Entity
public class Person extends AbstractPerson
{
@Column(name = "BIRTH_DT_TM")
@Type(type = "com.myexample.hibernate.types.UTCTimestampType")
protected Date birthDtTm;
}
I have two queries to retreive person from Db when current time is greater than brith date
1)One in jpql, which actually calls the UserType#nullSafeSet as expected
2) but if i build the same query via criteria builder the UserType#nullSafeSet implementation in the class UTCTimestampType is never get called and the query just executes.
Am not sure why this happens
this i my sample unit test for case (1)
String query = "SELECT pFROM Person p where p.birthDtTm = :now";
Query q = entityManager.createQuery(query);
q.setParameter("now", new Date());
List<Person> persons= q.getResultList();
But my criteria query doesn't go through my custom UserType class
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Person> criteriaQuery = criteriaBuilder.createQuery(Person.class);
Root<Person> root = criteriaQuery.from(Person.class);
Predicate predicate = criteriaBuilder.greaterThanOrEqualTo(
root.get(Person_.birthDtTm), new Date());
criteriaQuery.where(predicate);
criteriaQuery.select(root);
TypedQuery<Person> q = entityManager.createQuery(criteriaQuery2);
List<Person> persons = q.getResultList();
I want the birth date field to be converted to UTC time zone before my query is executed, this worked in jpql query, but in criteria builder the custom type is never called and the query executes with the passed in time. My end sql statment should be Ex:SELECT * FROM Person p where p.birthDtTm = date;
where date is in UTC time zone
The problem here was that in hibernate version till "hibernate-core" "version = 5.1.5.Final" and "hibernate-entitymanager" "version = 5.1.5.Final" , any user specific types implemented using org.hibernate.usertype.UserType is not supported, since hibernate only acknowledges org.hibernate.type.CompositeCustomType i.e. user types implemented using org.hibernate.usertype.CompositeUserType, this can be seen in the class
org.hibernate.jpa.internal.QueryImpl#mightNeedRedefinition
private boolean mightNeedRedefinition(Class javaType, Type expectedType)
{
// only redefine dates/times/timestamps that are not wrapped in a CompositeCustomType
if(expectedType == null)
{
return java.util.Date.class.isAssignableFrom(javaType);
}
else
{
return java.util.Date.class.isAssignableFrom(javaType)
&& !CompositeCustomType.class.isAssignableFrom(expectedType.getClass());
}
}
I believe this to be a bug in the hibernate versions, these have been fixed from "hibernate-core" "version = 5.2.0.Final" and higher version [note: these require jdk8]
So there are two ways to achieve the result,
1) update "hibernate-core" "version = 5.2.0.Final" or above [Note: from this version onwards hibernate-entitymanager Hibernate's JPA support has been merged into the hibernate-core module]
if a move to "hibernate-core" "version = 5.2.0.Final" is not possible at the moment, then the below two might come in handy,
2) any fields of java date types with custom user type should implement the CompositeUserType
(or)
3) a hacky way of getting around if the user type implements org.hibernate.usertype.UserType [note: this is highly discouraged]
The is a hack to workaround this is to, create a ParameterExpression for the corresponding predicate then send that to the predicate and only assign the set parameter only when the query creation is completed.
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Person> criteriaQuery = criteriaBuilder.createQuery(Person.class);
Root<Person> root = criteriaQuery.from(Person.class);
ParameterExpression<Date> p = criteriaBuilder.parameter(Date.class);
Predicate predicate = criteriaBuilder.greaterThanOrEqualTo(root.get(Person_.birthDtTm), p);
criteriaQuery.where(predicate);
criteriaQuery.select(root);
TypedQuery<Person> q = entityManager.createQuery(criteriaQuery2);
query.setParameter(p, new Date();
List<Person> persons = q.getResultList();
but its risky since if a single parameter is missed in the development of the criteria query, then for the reamining of the session only hibernates custom type will be applied.