criteria.setProjection(Projections.rowCount());
就可以了,但如果是distinct的查询,记录条数和你所需查询的列有关,那么可以这样
Projections.countDistinct(propertyName);
问题来了,好像只能有一列啊。没错在标准sql中的distinct count也只能是一列的,但当有多列查询的时候怎么办呢?criteria有个接口可以设定结果值为distinct,就这样
criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
但是这样做的问题在于,他是先执行,后在程序中distinct,这样会导致每页的记录可能不同,而且性能低下。
换个思路在写sql的时候可以这样写
select count(*) from (select distinct xxxxxx)
也就是说在外层套一个count 而真实的查询语句在from子句中,成为一个子查询。那看看如何变成Hibernate的形式吧。我在网站上搜索了好久,终于得出一个结论(经过Hibernate文档证明了)Hibernate不支持from子句中存在子查询,他们认为from应该是一个映射了的对象,子查询和他们的思想不符。
好吧,那再换个思路,Hibernate本身只是一个映射的过程,那么实际执行还是需要JDBC实现。如果是JDBC就需要sql语句,我们把criteria中蕴含的sql语句提取出来,然后加上我们外层的计数部分,然后再像Hibernate一样绑定参数就可以了,因为外层没有参数占位符,所以也不会影响Hibernate按位置绑定参数,因为没有接口,那只能读源码了。果然,hibernate最后通过sql(就是log中看到的那个)生成一个PreparedStatement,并通过方法绑定参数。废话少说,直接来个破冰之旅。
/** * 因为需要修改sql,就要传入你新的sql,criteria必须是绑定session的 * 因为我们用到了session * @param sql * @param criteria * @return */ public static ListwrapAndExecute(String sql, Criteria criteria) { ResultSet rs = null;//最后的结果集 PreparedStatement ps = null;//我们自己的PreparedStatement Connection connection = null;//session中获取的连接 try { //先转型 CriteriaImpl criteriaImpl = (CriteriaImpl) criteria; // 获取SessionImplementor类型的session SessionImplementor session = criteriaImpl.getSession(); // 获取factory SessionFactoryImplementor factory = session.getFactory(); // 获取CriteriaQueryTranslator对象,按照Hibernate源码中的方式生成 CriteriaQueryTranslator translator = new CriteriaQueryTranslator(factory, criteriaImpl, criteriaImpl .getEntityOrClassName(), CriteriaQueryTranslator.ROOT_SQL_ALIAS); // 从CriteriaQueryTranslator对象中获取保存参数的QueryParameters对象 QueryParameters queryParameters = translator.getQueryParameters(); // 从factory中获取implementors,一般只用一个 String[] implementors = factory.getImplementors(criteriaImpl.getEntityOrClassName()); // 因为只用一个,所以只需要一个loader,按照Hibernate源码中的方式新建 // 注意有些地方由于非public所以需要用反射,反射部分代码就不贴了,注意invokeMethod要从子类找到父类,直到object // 因为有些方法是写在父类中的 Loader loader = new CriteriaLoader((OuterJoinLoadable) ReflectUtil.invokeMethod(session, "getOuterJoinLoadable", new Class[] { String.class }, new Object[] { implementors[0] }), factory, criteriaImpl, implementors[0], ((SessionImpl) session).getLoadQueryInfluencers()); // 按照Hibernate源码方法获取walker CriteriaJoinWalker walker = new CriteriaJoinWalker((OuterJoinLoadable) factory .getEntityPersister(implementors[0]), translator, factory, criteriaImpl, criteriaImpl .getEntityOrClassName(), session.getLoadQueryInfluencers()); // 获取了criteria的sql String criteriaSql = walker.getSQLString(); // 用传入的sql代替,注意PLACEHOLDER代表criteriaSql的占位符 sql = sql.replace(PLACEHOLDER, criteriaSql); // 获取session中的连接 connection = session.getJDBCContext().getConnectionManager().getConnection(); // 构建新的PreparedStatement ps = connection.prepareStatement(sql); // 注意一定要过滤过参数才能绑定 queryParameters.processFilters(criteriaSql, session); // 通过反射绑定参数 ReflectUtil.invokeMethod(loader, "bindParameterValues", new Class[] { PreparedStatement.class, QueryParameters.class, int.class, SessionImplementor.class }, new Object[] { ps, queryParameters, 1, session }); // 获取结果集 rs = session.getBatcher().getResultSet(ps); ......
呼,好了,终于抠出了criteria的东西了。
最后值得注意的是,由于ResultSet和PreparedStatement是自己创建的,所以需要自己维护,就是要注意关闭咯。至于connection由于是session中获取的,可以不用管,否则这个session如果还有其他操作connection关了就要抛错了。