休眠中的 Criteria Builder 查询不调用 UserType api 方法

UserType api methods are not called from Criteria Builder query in hibernate

我有一个场景,我使用 org.hibernate.usertype.UserType.

在休眠中为实体字段使用自定义用户类型

这是转换为 UTC 的日期

    import org.hibernate.usertype.ParameterizedType;
    import org.hibernate.usertype.UserType;

    public final class UTCTimestampType implements ParameterizedType, UserType
    {..}

我的实体class如下图

@Entity
public class Person extends AbstractPerson
{
    @Column(name = "BIRTH_DT_TM")
    @Type(type = "com.myexample.hibernate.types.UTCTimestampType")
    protected Date birthDtTm;
}

当当前时间大于出生日期时,我有两个查询要从 Db 中检索人

1)jpql中的一个,它实际上按预期调用了UserType#nullSafeSet

2) 但如果我通过条件生成器构建相同的查询,则 class UTCTimestampType 中的 UserType#nullSafeSet 实现永远不会被调用,查询只会执行。

我不确定为什么会这样

这是我对案例 (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();

但是我的条件查询没有通过我的自定义 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();

我希望在执行查询之前将出生日期字段转换为 UTC 时区,这在 jpql 查询中有效,但在标准构建器中,自定义类型永远不会被调用,查询会以传递的时间执行。 我的结尾 sql 声明应该是 Ex:SELECT * FROM Person p where p.birthDtTm = date;

其中日期为 UTC 时区

这里的问题是在休眠版本中直到 "hibernate-core" "version = 5.1.5.Final""hibernate-entitymanager" "version = 5.1.5.Final" ,使用实现的任何用户特定类型 不支持 org.hibernate.usertype.UserType,因为 hibernate 只承认 org.hibernate.type.CompositeCustomType,即使用 [= 实现的用户类型56=], 这可以在 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());
    }
}

我认为这是休眠版本中的错误,这些已从 "hibernate-core" "version = 5.2.0.Final" 和更高版本中修复[注意:这些需要 jdk8]

所以有两种方法可以达到这个效果,

1) 更新 "hibernate-core" "version = 5.2.0.Final" 或以上 [注意:从这个版本开始 hibernate-entitymanager Hibernate 的 JPA 支持已经合并到 hibernate-core 模块中]

如果目前无法移动到 "hibernate-core" "version = 5.2.0.Final",那么下面两个可能会派上用场,

2) java 具有自定义用户类型的日期类型的任何字段都应实现 CompositeUserType

(或)

3) 如果用户类型实现 org.hibernate.usertype.UserType,这是一种绕过的 hacky 方式 [注意:非常不鼓励这样做]

解决这个问题的方法是, 为相应的谓词创建一个 ParameterExpression 然后将其发送到谓词,并仅在查询创建完成时才分配 set 参数。

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

但这是有风险的,因为如果在标准查询的开发过程中遗漏了一个参数,那么为了重新定义会话,将只应用休眠自定义类型。