一.条件查询
条件查询是根据面向对象特色的数据查询方式,条件查询通过如下三个类完成:
1>Criteria:代表一次查询
2>Criterion:代表一个查询条件
3>Restrictions:产生查询条件的工具类
执行条件查询的步骤如下:
1>获得Hibernate的Session对象
2>以Session对象创建Criteria对象
3>使用Restrictions的静态方法创建Criterion查询条件
4>向Criteria查询中添加Criterion查询条件
5>执行Criteria的list()或uniqueResult()方法返回结果集
程序示例如下:
方法1:
public void Query(){
Session sess = HibernateSessionFactory.getSession();
Transaction tx = sess.beginTransaction();
//使用createCriteria开始条件查询
List list = sess.createCriteria(Student.class)
.add(Restrictions.gt("name", "cname")).list();
for(Object obj : list){
Student s = (Student)obj;
System.out.println(s.getName());
}
tx.commit();
HibernateSessionFactory.closeSession();
}
方法2:
public void Query(){
Session sess = HibernateSessionFactory.getSession();
Transaction tx = sess.beginTransaction();
//使用createCriteria开始条件查询
List list = sess.createCriteria(Student.class)
.add(Restrictions.gt("name", "cname")).list();
for(Student s : list){
System.out.println(s.getName());
}
tx.commit();
HibernateSessionFactory.closeSession();
}
注意:方法1的List不带泛型信息,需要进行显示的数据类型转换,方法2的List带泛型信息,可以直接使用;
在SQL语句中,字符串之间可以比较大小;
Criteria包含如下五个方法:
1>Criteria setFirstResult(int firstResult):设置查询返回的第一行记录(索引从0开始);
2>Criteria setMaxResult(int maxResult):设置查询返回的记录数;
这两个方法与Query的两个类似方法的功能一样,都用于完成查询分页;
3>Criteria add(Criterion criterion):增加查询条件
4>Criteria addOrder(Order order):增加排序规则,例如addOrder(Order.desc("type"));
Order实例代表一个排序标准,Order有如下静态方法:
4-1>static Order asc(String propertyName):根据propertyName属性升序排列
4-2>static Order desc(String propertyName):根据propertyName属性降序排列
5>List list():返回结果集
Criterion接口代表一个查询条件,该查询条件由Restrictions负责产生,Restrictions是专门用于产生查询条件的工具类,他的方法大部分都是静态方法,常用的方法如下:
1>static eq | ne | gt | ge | lt | le(String propertyName,Object value)
判断指定属性值是否等于,不等于,大于,大于等于,小于,小于等于指定值
2>static eq | ne | gt | ge | lt | leProperty(String propertyName,String otherPropertyName)
判断第一个属性(由propertyName参数决定)的值是否等于,不等于,大于,大于等于,小于,小于等于第二个属性(由otherPropertyName参数确定)的值
3>static Criterion allEq(Map propertyNameValues)
判断指定属性(由Map参数的key指定)和指定值(由Map参数的value指定)是否完全相等
注意:Map必须是Map
4>static Criterion between(String propertyName,Object lo,Object hi)
判断属性值在某个值范围之内
5>static Criterion ilike(String propertyName,Object value)
判断属性值匹配某个字符串,不区分大小写,条件字符串要不是完整的字符串去匹配,要不采用%通配符进行匹配
6>static Criterion ilike(String propertyName,String value,MatchMode matchMode)
判断属性值匹配某个字段,不区分大小写,模式是以判断以某个字符串开头或者结尾等,这个匹配字符串可以是整个值的字符串,也可以是其中的一部分,也可以使用%通配符
7>static Criterion like(String propertyName,Object value)
判断属性值匹配某个字符串,区分大小写,条件字符串要不是完整的字符串去匹配,要不采用%通配符进行匹配(理论上这个匹配区分大小写,但是实际测试不区分,和ilike一样)
8>static Criterion like(String propertyName,String value,MatchMode matchMode)
判断属性值匹配某个字段,区分大小写,模式是以判断以某个字符串开头或者结尾等,这个匹配字符串可以是整个值的字符串,也可以是其中的一部分,也可以使用%通配符(理论上这个匹配区分大小写,但是实际测试不区分,和ilike一样)
9>static Criterion in(String propertyName,Collection values)
判断属性值在在某个集合内
10>static Criterion in(String propertyName,Object[ ] values)
判断属性值是数组元素的其中之一
11>static Criterion isEmpty(String propertyName)
判断属性值是否为空,这个测试出错,暂时不知道这个方法的应用场景,应该是涉及到关联的集合
12>static Criterion isNotEmpty(String propertyName)
判断属性值是否不为空,这个测试出错,暂时不知道这个方法的应用场景,应该是涉及到关联的集合
13>static Criterion isNull(String propertyName)
判断属性值是否为空
14>static Criterion isNotNull(String propertyName)
判断属性值是否不为空
15>static Criterion not(Criterion expression)
对Criterion求反,两个条件进行嵌套,例如.add(Restrictions.not(Restrictions.eq("name", "cname")))
16>static Criterion sizeEq(String propertyName,int size)
判断某个属性的元素个数是否与size相等,这个测试出错,暂时不知道这个方法的应用场景,应该是涉及到关联的集合
17>static Criterion sqlRestriction(String sql)
直接使用sql语句作为筛选条件,例如:.add(Restrictions.sqlRestriction("type > 15"))
18>static Criterion sqlRestriction(String sql,Object[ ] values,Type[ ] types)
直接使用带参数占位符的SQl语句作为条件,并指定多个参数值,例如.add(Restrictions.sqlRestriction("type > ? and id > ?",new String[]{"17","17"},new Type[]{StringType.INSTANCE,StringType.INSTANCE}))
19>static Criterion sqlRestriction(String sql,Object value,Type type)
直接使用带参数占位符的SQl语句作为条件,并指定参数值,例如.add(Restrictions.sqlRestriction("type > ?","17",StringType.INSTANCE))
===========================================================
二.关联和动态关联
如果需要使用关联实体的属性来增加查询条件,则应该对属性再次使用createCriteria()方法,createCriteria()方法有如下重载版本:
1>Criteria createCriteria(String associationPath):使用默认的连接方式进行关联
2>Criteria createCriteria(String associationPath,JoinType joinType):以JoinType指定的连接方式进行关联;支持INNER_JOIN、LEFT_OUTER_JOIN、RIGHT_OUTER_JOIN、FULL_JOIN等枚举值;
3>Criteria createCriteria(String associationPath,String alias):该方法的功能与第一个方法的功能基本相似,只是该方法允许为关联实体指定别名;
4>Criteria createCriteria(String associationPath,String alias,JoinType joinType):该方法的功能与第二个方法的功能基本相似,只是该方法允许为关联实体指定别名;
5>Criteria createCriteria(String associationPath,String alias,JoinType joinType,Criterion withClause):该方法的功能最强大,该方法既可为关联实体指定别名,也可指定连接类型,还可以通过withClause指定自定义的连接条件---这个可用于实现非等值连接;
对比上面的方法,他们功能基本相似,只是有的能显示指定连接方式;
如下示例根据关联实体的属性过滤数据:
public void Query(){
Session sess = HibernateSessionFactory.getSession();
Transaction tx = sess.beginTransaction();
//使用createCriteria开始条件查询
List list = sess.createCriteria(Student.class)
//此处增加限制条件必须是Student实体存在的属性
.add(Restrictions.gt("studentNumber", 20050231))
//如果要增加对Student的关联类的属性限制
//必须重载createCriteria()
//如果此关联属性是集合,则只要集合里任意一个对象的属性满足下面条件即可
.createCriteria("enrolments")
.add(Restrictions.eq("semester", 4)).list();
for(Object obj:list){
Student s = (Student)obj;
System.out.println(s.getName());
System.out.println(s.getEnrolments().size());
}
tx.commit();
HibernateSessionFactory.closeSession();
}
使用关联类的条件查询,依然是查询原有持久化类的实例,而不是查询被关联类的实例;为了达到上面这种效果,也可以使用createAlias()方法来代替createCriteria()方法,createAlias()方法同样有三个重载的版本:
1>Criteria createAlias(String associationPath,String alias):该方法的功能基本等同于Criteria createCriteria(String associationPath,String alias);
2>Criteria createAlias(String associationPath,String alias,JoinType joinType):该方法的功能基本等同于Criteria createCriteria(String associationPath,String alias,JoinType joinType)
3>Criteria createAlias(String associationPath,String alias,JoinType joinType,Criterion withClause):该方法的功能基本等同于Criteria createCriteria(String associationPath,String alias,JoinType joinType,Criterion withClause);
因此,程序可以将上面的查询替换为如下形式:
public void Query(){
Session sess = HibernateSessionFactory.getSession();
Transaction tx = sess.beginTransaction();
//使用createCriteria开始条件查询
List list = sess.createCriteria(Student.class)
//此处增加限制条件必须是Student实体存在的属性
.add(Restrictions.gt("studentNumber", 20050231))
//如果要增加对Student的关联类的属性限制
//必须重载createCriteria()
//如果此关联属性是集合,则只要集合里任意一个对象的属性满足下面条件即可
.createAlias("enrolments","al")
.add(Restrictions.eq("al.semester", 4)).list();
for(Object obj:list){
Student s = (Student)obj;
System.out.println(s.getName());
System.out.println(s.getEnrolments().size());
}
tx.commit();
HibernateSessionFactory.closeSession();
}
createAlias()方法并不是创建一个新的Criteria实例,他只是给关联实体(包括集合里包含的关联实体)起一个别名,让后面的过滤条件可根据该关联实体进行筛选;
在默认情况下,条件查询将根据持久化注解指定的延迟加载策略来加载关联实体,如果希望在条件查询中改变延迟加载策略(就像HQl查询中使用fetch关键字一样),则可以通过Criteria的setFetchMode()方法来实现,该方法也接受一个FetchMode枚举类型的值,FetchMode支持如下枚举值:
1>DEFAULT:使用配置文件指定延迟加载策略处理;
2>JOIN:使用外连接,预初始化所有的关联实体;
3>SELECT:启用延迟加载,系统将使用单独的select语句来初始化关联实体,只有当真正访问关联实体的时候,才会执行第二条select语句;
如果想让程序在初始化Student对象时,也可以初始化Student关联的Enrolment实体,则可使用如下查询方法
public void Query(){
Session sess = HibernateSessionFactory.getSession();
Transaction tx = sess.beginTransaction();
//使用createCriteria开始条件查询
List list = sess.createCriteria(Student.class)
//此处增加限制条件必须是Student实体存在的属性
.add(Restrictions.gt("studentNumber", 20050231))
.setFetchMode("enrolments", FetchMode.JOIN)
.list();
tx.commit();
HibernateSessionFactory.closeSession();
for(Object obj:list){
Student s = (Student)obj;
System.out.println(s.getName());
System.out.println(s.getEnrolments().size());
}
}
上面程序的setFetchMode()方法将会初始化Student关联的enrolments集合,由于这个方法让程序查询Student实体时同步抓取了Student关联的Enrolemnt实体,虽然程序在HibernateSeeionFactory.closeSession处关闭了session,但程序依然可以在下面通过Student获取关联的Enrolment实体。如果程序将这个方法注释掉,程序会在下面的调用中引发LazyInitializationException异常(延迟初始化异常),导致该异常的典型原因就是,当程序试图获取延迟加载的关联实体或集合属性时,Session的关闭导致无法加载到关联实体或集合属性,程序就会引发LazyInitializationException异常了;===========================================================
三.投影、聚合和分组
投影运算实际上就是一种基于列的运算,通常用于投影到指定列(也就是过滤其他列,类似于select子句的作用),还可以完成sql语句中常用的分组,组筛选等功能;
Hibernate的条件过滤中使用Projection代表投影运算,Projection是一个接口,而Projections作为Projection的工厂,负责生成Projection对象;
一旦产生了Projection对象之后,就可以通过Criteria提供的setProjection(Projection projection)方法来进行投影运算,从该方法上来看,每个Criteria只能接收一个投影运算,似乎无法进行多个投影运算,但实际上,hibernate又提供了一个ProjectionList类,该类是Projection的子类,并可以包含多个投影运算,通过这种方式即可完成多个投影运算;
因此,一个条件查询的投影运算通常有如下程序结构:
List cats = session.createCriteria(Cat.class)
.setProjection(Projections.projectionList()
.add(Projections.rowCount())
.add(Projections.avg("weight"))
.add(Projections.max("weight"))
.add(Projections.min("weight"))
.add(Projections.groupProperty("color")))
.list();
从上面的粗体字代码可以看出,所谓投影运算实际上和sql语句里的聚集函数,分组语句(group by子句)有类似的功能,使用条件查询的投影运算时,不能使用显示的分组子句,但某些投影的类型的实质就是分组投影,这些投影运算将会出现在sql语句的group by子句中,如上面的groupProperty("color")投影;
在Projections工具类提供了如下几个静态方法:
1>AggregateProjection avg(String propertyname):计算特定属性的平均值,类似于avg函数;
2>CountProjection count(String propertyname):统计查询结果在某列上的记录条数,类似于count(column)函数;
3>CountProjection countDistinct(String propertyname):统计查询结果在某列上不重复的记录条数,类似于count(distinct column)函数;
4>PropertyProjection groupProperty(String propertyname):将查询结果按某列上的值进行分组,类似于添加group by子句;
5>AggregateProjection max(String propertyname):统计查询结果在某列上的最大值,类似于max函数;
6>AggregateProjection min(String propertyname):统计查询结果在某列上的最小值,类似于min函数;
7>Projection rowCount():统计查询结果的记录条数,类似于count(*)的功能;
8>AggregateProjection sum(String propertyname):统计查询结果在某列上的总和,类似于sum函数;
下面的程序示范如何通过投影运算来进行分组,使用聚集函数功能:
public void Query(){
Session sess = HibernateSessionFactory.getSession();
Transaction tx = sess.beginTransaction();
//使用createCriteria开始条件查询
List list = sess.createCriteria(Enrolment.class)
.createAlias("student", "s")
.setProjection(Projections.projectionList()
//统计记录条数
.add(Projections.rowCount())
//统计选择该课程里最大的学生姓名
.add(Projections.max("s.name"))
//按照course进行分组
.add(Projections.groupProperty("course")))
.list();
for(Object obj:list){
Object[] objs = (Object[])obj;
Course c = (Course)objs[2];
System.out.println(c.getName());
System.out.println(objs[0]);
System.out.println(objs[1]);
System.out.println("------------");
}
tx.commit();
HibernateSessionFactory.closeSession();
}
上面程序为条件查询增加了分组功能,并通过Projections统计了每组的记录条数,统计Student名字最大的值;
从上面的程序可以看出,对于增加了投影运算后的条件查询,查询返回的结果是数组,数组的前N个元素依次是投影运算的结果,最后一个数组元素才是条件查询得到的实体,由此可见,投影运算的实质和group by子句,聚集函数的功能大致一致;
除此之外,如果希望对分组(投影)后的属性进行排序,那就需要为投影运算指定一个别名,为投影运算指定别名有如下三个方法:
1>使用Projections的alisa()方法为指定投影指定别名;
Projections的alias()方法为指定Projection指定别名,并返回Projection对象,一旦为指定Projection指定了别名,程序就可以根据该Projection别名来进行其他操作了,比如排序,条件查询示例如下
public void Query(){
Session sess = HibernateSessionFactory.getSession();
Transaction tx = sess.beginTransaction();
//使用createCriteria开始条件查询
List list = sess.createCriteria(Enrolment.class)
.createAlias("student", "s")
.setProjection(Projections.projectionList()
//统计记录条数
.add(Projections.rowCount())
//统计选择该课程里最大的学生姓名,如果以这个为别名进行排序,原来的这个投影查询条件运算不能省略
.add(Projections.max("s.name"))
//按照course进行分组
.add(Projections.groupProperty("course"))
.add(Projections.alias(Projections.max("s.name"), "s")))
.addOrder(Order.asc("s"))
.list();
for(Object obj:list){
Object[] objs = (Object[])obj;
Course c = (Course)objs[2];
System.out.println(c.getName());
System.out.println(objs[0]);
System.out.println(objs[1]);
System.out.println("------------");
}
tx.commit();
HibernateSessionFactory.closeSession();
}
上面程序示范了为Projection指定别名的方法;2>使用SimpleProjection的as()方法为自身指定别名;
如果条件查询所使用的投影运算是SimpleProjection及其子类,则可以直接使用该投影对象的as()方法来为自身指定别名,条件查询如下:
public void Query(){
Session sess = HibernateSessionFactory.getSession();
Transaction tx = sess.beginTransaction();
//使用createCriteria开始条件查询
List list = sess.createCriteria(Enrolment.class)
.createAlias("student", "s")
.setProjection(Projections.projectionList()
//统计记录条数
.add(Projections.rowCount())
//统计选择该课程里最大的学生姓名,如果以这个为别名进行排序,原来的这个投影查询条件运算不能省略
.add(Projections.max("s.name").as("s"))
//按照course进行分组
.add(Projections.groupProperty("course")))
.addOrder(Order.asc("s"))
.list();
for(Object obj:list){
Object[] objs = (Object[])obj;
Course c = (Course)objs[2];
System.out.println(c.getName());
System.out.println(objs[0]);
System.out.println(objs[1]);
System.out.println("------------");
}
tx.commit();
HibernateSessionFactory.closeSession();
}
3>使用ProjectionList的add()方法添加投影时指定别名;
ProjectionList的add()方法有两种重载形式:一种是直接添加一个投影,另一种是在添加投影时指定别名,示例如下:
public void Query(){
Session sess = HibernateSessionFactory.getSession();
Transaction tx = sess.beginTransaction();
//使用createCriteria开始条件查询
List list = sess.createCriteria(Enrolment.class)
.createAlias("student", "s")
.setProjection(Projections.projectionList()
//统计记录条数
.add(Projections.rowCount())
//统计选择该课程里最大的学生姓名,如果以这个为别名进行排序,原来的这个投影查询条件运算不能省略
.add(Projections.max("s.name"),"s")
//按照course进行分组
.add(Projections.groupProperty("course")))
.addOrder(Order.asc("s"))
.list();
for(Object obj:list){
Object[] objs = (Object[])obj;
Course c = (Course)objs[2];
System.out.println(c.getName());
System.out.println(objs[0]);
System.out.println(objs[1]);
System.out.println("------------");
}
tx.commit();
HibernateSessionFactory.closeSession();
}
除此之外,hibernate还提供了Property执行投影运算,Property投影的作用类似于sql语句中的select,条件查询的结果只有被Property投影的列才会被选出,示例如下:
如果只选择显示一列:
public void Query(){
Session sess = HibernateSessionFactory.getSession();
Transaction tx = sess.beginTransaction();
//使用createCriteria开始条件查询
List list = sess.createCriteria(Enrolment.class)
.setProjection(Property.forName("semester"))
.list();
for(Object obj:list){
System.out.println(obj);
}
tx.commit();
HibernateSessionFactory.closeSession();
}
上面的条件查询执行的结果不在是对象集合,而是semester属性所组成的集合;如果选择显示多列:
public void Query(){
Session sess = HibernateSessionFactory.getSession();
Transaction tx = sess.beginTransaction();
//使用createCriteria开始条件查询
List list = sess.createCriteria(Enrolment.class)
.setProjection(Projections.projectionList()
.add(Property.forName("semester"))
.add(Property.forName("year")))
.add(Property.forName("name").eq("ss"))
.list();
for(Object obj:list){
}
tx.commit();
HibernateSessionFactory.closeSession();
}
上面的代码最后还能添加过滤条件,比如上面是name属性等于ss的记录===========================================================
四.离线查询和子查询
条件查询的离线查询由DetachedCriteria来代表,DetachedCriteria类允许在一个Session范围之外创建一个查询,并且可以使用任意Session来执行它;
使用DetachedCriteria来执行离线查询,通常使用如下的方法来获得一个离线查询:
//创建指定持久化类的离线查询
DetachedCriteria.forClass(Class entity)
除此之外,DetachedCriteria还可代表子查询,当把DetachedCriteria传入Criteria中作为查询条件时,DetachedCriteria就变成了子查询,条件实例包含子查询通过Subqueries或者Property来获得;
如下程序示范了使用DetachedCriteria进行离线查询和子查询:
离线查询:
public void Query(){
//定义一个离线查询
DetachedCriteria query = DetachedCriteria.forClass(Student.class)
.setProjection(Property.forName("name"));
//打开Session和事务
Session sess = HibernateSessionFactory.getSession();
Transaction tx = sess.beginTransaction();
//执行离线查询
List list = query.getExecutableCriteria(sess).list();
for(Object obj:list){
System.out.println(obj);
}
tx.commit();
HibernateSessionFactory.closeSession();
}
子查询:
public void Query(){
//定义一个离线查询
DetachedCriteria query = DetachedCriteria.forClass(Student.class)
.setProjection(Property.forName("name"));
//打开Session和事务
Session sess = HibernateSessionFactory.getSession();
Transaction tx = sess.beginTransaction();
//执行子查询
List list = sess.createCriteria(Student.class)
//下面两行代码作用相同,都示范了通过子查询添加查询条件
//.add(Property.forName("name").in(query))
.add(Subqueries.propertyIn("name", query))
.list();
for(Student obj:list){
System.out.println(obj.getName());
}
tx.commit();
HibernateSessionFactory.closeSession();
}
从上面的程序来看,当创建一个DetachedCriteria对象之后,该对象到底被作为离线查询使用还是作为子查询使用,都与DetachedCriteria无关;
如果程序使用Session的getExecutableCriteria()方法来执行DetachedCriteria对象,则他被当成离线查询使用,如果程序使用Property(或Subqueries的系列类方法)来操作DetachedCriteria对象,则他被当作子查询使用;
Property类提供了eq()、eqAll、ge()、geAll()、gt()、gtAll()、in等系列方法,这些方法与SQL子查询中的运算符一一对应,除此之外,Subqueries也提供了eq()、eqAll、ge()、geAll()、gt()、gtAll()、in等静态方法,这些方法与Property同名的实例方法的功能基本相似,在这种情况下,DetachedCriteria被当成子查询使用;
-----------------------------------------------------------------
补充1:
public static void query(){
Session sess = HibernateSessionFactory.getSession();
Transaction tx = sess.beginTransaction();
Criteria criteria = sess.createCriteria(User.class,"user");
criteria.createCriteria("institution","institution");
ProjectionList pList = Projections.projectionList();
pList.add(Property.forName("user.id").as("aa"))
.add(Projections.property("user.username").as("bb"))
.add(Projections.property("institution.institutionName").as("cc"));
criteria.setProjection(pList);
criteria.setResultTransformer(Transformers.aliasToBean(Temp.class));
List list = criteria.list();
tx.commit();
HibernateSessionFactory.closeSession();
}
1>查询User类的指定字段;
2>查处的结果使用setResultTransformer方法进行规整,必须有一个Temp的类去接收指定的字段,如下:
package com.anlw.entity;
public class Temp {
private int aa;
private String bb;
private String cc;
public int getAa() {
return aa;
}
public void setAa(int aa) {
this.aa = aa;
}
public String getBb() {
return bb;
}
public void setBb(String bb) {
this.bb = bb;
}
public String getCc() {
return cc;
}
public void setCc(String cc) {
this.cc = cc;
}
}
3>Property.froName()方法与Projections.property()方法的作用一样,都是指定自定义查询的字段;