Hibernate Criteria排序疑难:如何在ORDER BY中应用TO_TIMESTAMP函数?
我之前也遇到过类似的问题——直接在Hibernate Criteria的Order.asc()里写SQL函数肯定不行,因为Hibernate会把参数当成实体的属性名来解析,而不是原生SQL片段。下面给你几个可行的解决方案,按简单程度排序:
方案1:使用Order.sql()直接写入原生排序SQL(最简单)
如果你用的是Hibernate 5及以上版本,可以直接用Order.sql()方法,它允许你传入原生的ORDER BY子句片段,Hibernate会直接把它拼到最终的SQL里,不会做属性解析:
// 添加你的查询条件 criteria.add(Restrictions.ge("initDate", startTimestamp)); criteria.add(Restrictions.le("initDate", endTimestamp)); // 直接写入排序用的原生SQL criteria.addOrder(Order.sql("TO_TIMESTAMP(LIMIT_DATE, 'dd/MM/yyyy') ASC")); criteria.addOrder(Order.sql("TO_TIMESTAMP(INIT_DATE, 'dd/MM/yyyy') ASC"));
不过这里要提一句:既然你的数据库字段和Java实体都是日期类型,其实没必要用TO_TIMESTAMP(这个函数是把字符串转成日期),直接用TRUNC()函数截断时分秒更高效,比如:
criteria.addOrder(Order.sql("TRUNC(LIMIT_DATE) ASC")); criteria.addOrder(Order.sql("TRUNC(INIT_DATE) ASC"));
TRUNC(LIMIT_DATE)会把日期的时分秒部分清零,直接得到日期的“日”级别,排序效果和你想要的完全一致,而且性能更好。
方案2:使用SQL投影(适合需要保留实体对象返回的场景)
如果你的场景需要返回完整的实体对象(而不是Object数组),可以通过定义SQL投影来创建排序用的别名,再用别名排序:
// 1. 定义投影列表,先添加所有实体属性(确保返回完整对象) ProjectionList projections = Projections.projectionList(); // 假设你的实体类是Demande,添加所有需要的属性,比如: projections.add(Projections.property("id")); projections.add(Projections.property("initDate")); projections.add(Projections.property("limitDate")); // ... 其他属性按需添加 // 2. 添加排序用的SQL投影,设置别名 projections.add(Projections.sqlProjection( "TRUNC(LIMIT_DATE) as limitDateTrunc", new String[]{"limitDateTrunc"}, new Type[]{TimestampType.INSTANCE} )); projections.add(Projections.sqlProjection( "TRUNC(INIT_DATE) as initDateTrunc", new String[]{"initDateTrunc"}, new Type[]{TimestampType.INSTANCE} )); // 3. 设置投影并排序 criteria.setProjection(projections); criteria.addOrder(Order.asc("limitDateTrunc")); criteria.addOrder(Order.asc("initDateTrunc")); // 4. 如果需要把结果转成实体对象,可以使用ResultTransformer criteria.setResultTransformer(Transformers.aliasToBean(Demande.class));
这个方法的好处是可以继续用实体对象接收查询结果,不过需要手动添加所有属性到投影列表里,适合属性不多的实体。
方案3:注册SQL函数后使用投影函数(最规范)
如果你的项目经常需要用到这类SQL函数,可以先在Hibernate中注册TRUNC(或TO_TIMESTAMP)函数,然后通过Projections.sqlFunction()来调用:
// 注册TRUNC函数(在SessionFactory初始化时执行一次即可) SessionFactory sessionFactory = ...; ((SessionFactoryImplementor) sessionFactory).getSqlFunctionRegistry() .register("trunc", new StandardSQLFunction("trunc", TimestampType.INSTANCE)); // 之后在Criteria中使用: Criteria criteria = session.createCriteria(Demande.class); // 添加查询条件 criteria.add(Restrictions.ge("initDate", startTimestamp)); criteria.add(Restrictions.le("initDate", endTimestamp)); // 使用注册的函数创建排序条件 criteria.addOrder(Order.asc( Projections.sqlFunction("trunc", TimestampType.INSTANCE, Projections.property("limitDate")) )); criteria.addOrder(Order.asc( Projections.sqlFunction("trunc", TimestampType.INSTANCE, Projections.property("initDate")) ));
这种方法更符合Hibernate的规范,适合需要多次复用该函数的场景。
最后提醒:你之前的错误could not resolve property: TO_TIMESTAMP(limitDate, 'dd/MM/yyyy'),本质是Hibernate把你传入的字符串当成了实体类的属性名去匹配,而不是原生SQL,所以只要避开让Hibernate解析这个片段,直接用原生SQL或者投影的方式就能解决问题。
内容的提问来源于stack exchange,提问作者Yassine Badache

