继续昨天说的criteria查询,上回书说到criteria 排序、统计、分组等功能,排序和分页的查询已经说完了,今天继续。
3.criteria 排序、统计、分组等功能
3.3 直接使用SQL
criteria有个比较好玩的地方就是可以直接使用sql,请允许我YY一下,有人说我就是sql不好才想到使用criteria查询的,现在倒好,你又给我说回sql去了,这个。。。人家有这个功能我们就试试嘛,说不定哪天就用到了,废话不多说,惯例先上代码后说话:
3.3.1 第一种情况
@Test public void test() { Criteria criteria = session.createCriteria(User1.class); criteria.add(Restrictions.sqlRestriction("{alias}.username like (?)","张%",Hibernate.STRING)); List<User1> list = criteria.list(); for(User1 u: list){ System.out.println(u.toString()); } }
控制台发出的sql语句:
Hibernate: select this_.uid as uid0_0_, this_.username as username0_0_, this_.pwd as pwd0_0_, this_.create_date as create4_0_0_, this_.age as age0_0_ from test.t_user1 this_ where this_.username like (?)
昨天说查姓张的,最后查的是名字里面带有张字的,今天咱实实在在的查个姓张的,直接使用sql进行查询的格式就是上面的代码中红色的部分,但是要注意几点:
第一,{alias}不能变,至于为什么不能变我也不是很清楚,但是经过测试,如果这个词写错了,或者写成别的了,那么发出的sql语句就变了,是查不出来结果的,比如我把alias写成xxx,那么控制台打出来的sql语句就变成了下面这个样子:
Hibernate: select this_.uid as uid0_0_, this_.username as username0_0_, this_.pwd as pwd0_0_, this_.create_date as create4_0_0_, this_.age as age0_0_ from test.t_user1 this_ where {xxx}.username like (?)
请大家注意sql语句中绿色的部分,明显的就出问题了。
第二,{alias}.username 中的username,这个是要和你表中的字段对应,而不是和写的实体中的字段对应,当然,一般情况下我们都是把表的字段和实体的字段写一样的,但是也有不一样的,具体是什么还是要知道的;
第三,(?)这个外面是圆括号,{alias}这个外面是花括号,不能写错了,不然是会报错的。
3.3.2 第二种情况
上面是只传一个参数的情况,那么如果要传递多个参数怎么搞呢?请看代码:
@Test public void test() { Criteria criteria = session.createCriteria(User1.class); Integer [] age = {new Integer(20), new Integer(40)}; Type [] types = {Hibernate.INTEGER, Hibernate.INTEGER}; criteria.add(Restrictions.sqlRestriction(" {alias}.age BETWEEN (?) AND (?)",age , types)); criteria.add(Restrictions.sqlRestriction("{alias}.username like (?)","张%",Hibernate.STRING)); List<User1> list = criteria.list(); for(User1 u: list){ System.out.println(u.toString()); } }
sql语句:
Hibernate: select this_.uid as uid0_0_, this_.username as username0_0_, this_.pwd as pwd0_0_, this_.create_date as create4_0_0_, this_.age as age0_0_ from test.t_user1 this_ where this_.username like (?) and this_.age BETWEEN (?) AND (?)
昨天说过两个criteria.add()方法表示and的关系,这个不是主要的,主要的是上面代码中蓝色的部分,什么意思呢,意思就是在Restrictions.sqlRestriction()方法中的sql语句如果有两个相同类型的参数,那么是可以使用数组的方式传入参数来查询结果的。但是如果一个是string,一个是int,那还是分开写两个criteria.add()吧,写在一起不好使。
3.3.3 第三种情况
@Test public void test() { Criteria criteria = session.createCriteria(User1.class); String sql = ""; criteria.add(Restrictions.sqlRestriction(sql)); List<User1> list = criteria.list(); for(User1 u: list){ System.out.println(u.toString()); } }
刚看到之后有人肯定会说,这什么玩意儿,就传一个空字符串进去就能查询了?别急,看了控制台的sql语句你就知道了:
Hibernate: select this_.uid as uid0_0_, this_.username as username0_0_, this_.pwd as pwd0_0_, this_.create_date as create4_0_0_, this_.age as age0_0_ from test.t_user1 this_
这种形式查的竟然是表中的全部数据,我刚开始只是看到了Restrictions.sqlRestriction(sql)这个方法,但是并不知道怎么用,就随便传了一个空字符串进去,竟然可以,更多的我也没试,大家有兴趣的可以去试试。
3.4聚合统计
查询的时候很多时候会用到聚合统计,什么意思呢?比如说查询姓张的用户有多少个人,或者说某些用户的平均年龄,说白了就是对查询结果的统计,这个使用Criteria怎么搞呢?下面来个例子:
public void test() { Criteria criteria = session.createCriteria(User1.class); List result = null; /*result = criteria.setProjection(Projections.projectionList().add(Projections.rowCount())).list();*/ result = criteria.setProjection(Projections.avg("age")).list(); for(int i=0;i<result.size();i++){ System.out.println(result.get(i)); } }
控制台sql语句:
Hibernate: select avg(this_.age) as y0_ from test.t_user1 this_
从上面sql语句可以看到,上面的代码是对age求平均值,需要注意的有以下几点:
第一,此时的查询不再是criteria.add(),而是criteria.setProjection(),表示应用投影到一个查询。
第二,查询结果不在是List<User>,而是不确定的一个泛型,什么意思呢?对年龄求和的结果是一个int类型,求平均值是double类型的,所以还是不写了吧,省得搞错了还麻烦。
第三,统计函数都是Projections的静态函数,直接打点调用即可。
第四,上述代码中注释的那一行表示查询表中的全部数据的记录数,但是不能单独使用Projections.projectionList()。
第五,age是实体类中的属性名称,不能写成数据库表中的字段名称,不然查询的时候会报找不到这个字段的错误。
public void test() { Criteria criteria = session.createCriteria(User1.class); List result = null; /*result = criteria.setProjection( Projections.projectionList() .add(Projections.rowCount()) .add(Projections.max("ages")) .add(Projections.min("ages")) .add(Projections.avg("password")) ).list();*/ ProjectionList projectionList = Projections.projectionList(); projectionList.add(Projections.avg("ages")); projectionList.add(Projections.max("password")); criteria.setProjection(projectionList); result = criteria.list(); for(int i=0;i<result.size();i++){ System.out.println(result.get(i)); } }
上述的代码是表示查询多个结果时的方式,第一种直接在setProjection方法内部使用add()方法。第二种是先定义一个ProjectionList ,然后将要查询的内容放进去,其实这两种方式本质上是一样的,只不过写法不一样罢了,选择自己喜欢的方式写就行。
3.5 统计
在sql查询时,如果要分组查询会用到group by,在criteria查询中也是一样的,只不过形式不一样。国际惯例,上代码:
result = criteria.setProjection( Projections.projectionList() .add(Projections.rowCount()) .add(Projections.groupProperty("ages"))).list();
为了方便我就只贴出来中间这一段了,下面看sql语句:
Hibernate: select count(*) as y0_, this_.age as y1_ from test.t_user1 this_ group by this_.age
Projections.groupProperty("ages")表示根据年龄进行分组查询,查询的结果仍然是一个list,我debug跟踪了一下,给大家一个debug的result截图,方便理解:
数据库数据截图
这样理解起来就更清楚一些。
3.5.1 别名
result = criteria.setProjection( Projections.projectionList() .add( Projections.alias(Projections.rowCount(), "countByAge") ) .add( Projections.avg("ages").as("avgAge") ) .add(Projections.max("ages"),"maxAge") .add(Projections.groupProperty("ages"),"AGE") ) .addOrder(Order.asc("countByAge")) .addOrder(Order.desc("AGE")) .list();
在写sql语句时,可以给字段起别名,在criteria查询中也是可以的,我查文档给的解释太麻烦而且不是很好懂,我就不说了,其实就是可以起一个别名,而且这个别名可以再另一个投影查询中使用,上面的代码中有三种方式,大家随便找一个都可以。
3.5.2 property.forName()
result = criteria.setProjection( Projections.projectionList() .add( Projections.alias(Projections.rowCount(), "countByAge") ) .add( Property.forName("ages").avg().as("avgAge") ) .add(Property.forName("ages").max(),"maxAge") .add(Property.forName("ages").group(),"AGE") ) .addOrder(Order.asc("countByAge")) .addOrder(Order.desc("AGE")) .list();
控制台的sql语句:
Hibernate: select count(*) as y0_, avg(this_.age) as y1_, max(this_.age) as y2_, this_.age as y3_ from test.t_user1 this_ group by this_.age order by y0_ asc, y3_ desc
重点看红色的部分,其实实现的功能和上面的一段代码是一样的,主要是写法的不一样,上面使用的是Projections.avg("ages")但是这里使用的是 Property.forName("ages").avg(),功能一样,写法不同而已。
上面说了投影查询,下面说说需要注意的几点:
注意:
写HQL的时候如果HQL语句是"from User",那么返回的就是user表中的所有字段,并且这些字段会被封装到User对象中,但是如果你写的"select name,age from User",返回的结果就不会封装到User对象,而是根据投影的实际类型返回,这是投影对结果封装策略的影响。
在criteria查询中同样存在这个问题,如下代码:
result = criteria.setProjection( Projections.projectionList() .add(Projections.property("name").as("name")) .add(Projections.property("password").as("pwd")) ).list();
sql语句:
Hibernate: select this_.username as y0_, this_.pwd as y1_ from test.t_user1 this_
查询结果截图:
可以看到,查询到的结果中只有name和pwd属性,而且并没有封装到实体类中,那么怎么解决这个问题呢?
解决办法:在criteria接口中提供了一个setResultTransformer(ResultTransformer resultTransformer),这个ResultTransformer就是结果集转换策略接口,在criteria的父接口CriteriaSpecification中定义了几个ResultTransformer的常用实现。
ALIAS_TO_ENTITY_MAP 将结果集封装到Map对象;
ROOT_ENTITY 将结果集封装到根实体对象;
DISTINCT_ROOT_ENTITY 将结果集封装到不重复的根实体对象;
PROJECTION 根据投影的结果类型自动封装;
当进行投影查询时,结果的封装策略由ROOT_ENTITY 变为了PROJECTION, 所以是根据查询数据进行封装,而不是封装到根对象。
知道了上面的原理之后,即使只是查询name和pwd,也是可以封装成list的,那就是使用AliasToBeanResultTransformer 修改结果封装策略,AliasToBeanResultTransformer 会根据返回列自动匹配类中属性名,完成封装。
下面上代码:
public void test() { Criteria criteria = session.createCriteria(User1.class); criteria.setProjection( Projections.projectionList() .add(Property.forName("name").as("name")) //.add(Projections.property("name").as("name")) .add(Property.forName("ages").as("ages")) ); criteria.setResultTransformer(new AliasToBeanResultTransformer(User1.class)); List<User1> result = criteria.list(); for(int i=0;i<result.size();i++){ System.out.println(result.get(i)); } }
查询sql和结果:
Hibernate: select this_.username as y0_, this_.age as y1_ from test.t_user1 this_User1 [name=李四, password=null, createDate=null, ages=10]User1 [name=张三, password=null, createDate=null, ages=20]User1 [name=王五, password=null, createDate=null, ages=30]User1 [name=赵六, password=null, createDate=null, ages=20]User1 [name=王麻子, password=null, createDate=null, ages=30]
可以看到查询结果仍然是一个实体,只不过没有查询的字段是没有值的,注意:add(Property.forName("name").as("name"))等效于.add(Projections.property("name").as("name")),并且两个属性名称都要写上且要和实体中的属性名称一致,如果后面的as不写的话查询的所有结果全部是null.
3.6 关联查询
hibernate的关联查询有时候是很烦人的,但是又不得不用关联查询,毕竟他本身就是个ORM查询,不管关联查询用什么,下面来看看criteria的关联查询。
3.6.1 准备工作
两个类,关系大家都很清楚,我就不多说了
public class Student { private int id; private String name; private Classes classes;/*****************************/public class Classes { private int id; private String name; private Set students;
3.6.2 查询
国际惯例,先上代码:
@Test public void test() { Criteria criteria = session.createCriteria(Student.class); Criteria criteria2 = criteria.createCriteria("classes"); criteria2.add(Restrictions.eq("name", "302")); List<Student> list = criteria.list(); for(Student stu:list){ System.out.println(stu); } }
sql语句:
Hibernate: select this_.id as id1_1_, this_.name as name1_1_, this_.classesid as classesid1_1_, classes1_.id as id0_0_, classes1_.name as name0_0_ from t_stu this_ inner join t_class classes1_ on this_.classesid=classes1_.id where classes1_.name=?
大家注意我代码中标红的地方。
criteria.createCriteria()用于两表关联查询,有两种形式,
createCriteria(String associationPath) 采用内连接关联(返回新的Criteria对象)。
createCriteria(String associationPath, int joinType) 可以通过joinType指定关联类型(返回新的Criteria对象 )。
根据自己的需求选择连接类型就可以了,不清楚连接类型的自行百度即可;
第二种查询方式如下:
@Test public void test() { Criteria criteria = session.createCriteria(Student.class); //Criteria criteria2 = criteria.createCriteria("classes","left"); criteria.createAlias("classes", "cla",0); criteria.add(Restrictions.eq("cla.name", "302")); List<Student> list = criteria.list(); for(Student stu:list){ System.out.println(stu); } }
使用criteria.createAlias()方法进行关联查询,如果没有第三个参数,表示内连接查询,如果加上第三个参数的话,第三个参数只能取0,1,2,0表示内连接,1表示left outer ,2表示right outer.
criteria查询暂时就写这么多了,其实还有两三个内容,一个离线(detached)查询和子查询 ,一个org.hibernate.criterion.Example类的样例查询,还有一个关联查询的动态关联抓取,回头有时间了再把这三部分补上。暂时就这样了,有不正确的地方欢迎大家指正。