当前位置: 代码迷 >> Java相关 >> Hibernate之criteria查询(2)
  详细解决方案

Hibernate之criteria查询(2)

热度:34   发布时间:2016-04-22 19:51:14.0
Hibernate之criteria查询(二)

  继续昨天说的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类的样例查询,还有一个关联查询的动态关联抓取,回头有时间了再把这三部分补上。暂时就这样了,有不正确的地方欢迎大家指正。

 

  相关解决方案