hibernate的hql查询语句总结
这篇随笔将会记录hql的常用的查询语句,为日后查看提供便利。
在这里通过定义了三个类,Special、Classroom、Student来做测试,Special与Classroom是一对多,Classroom与Student是一对多的关系,这里仅仅贴出这三个bean的属性代码:
Special类:
public class Special { private int id; private String name; private String type; private Set<Classroom> rooms; .......... }
Classroom类:
public class Classroom { private int id; private String name; private Special special; private Set<Student> students; ............ }
Student类:
public class Student { private int id; private String name; private String sex; private Classroom room; .......... }
1.最简单的查询
List<Special> specials = (List<Special>)session.createQuery("select spe from Special spe").list();
这是hql最基本的查询语句了,作用就是查出所有的Special对象放到一个List当中
2.基于 ? 的参数化形式
/** * 查询中使用?,通过setParameter的方式可以防止sql注入 * jdbc的setParameter的下标从1开始,hql的下标从0开始 */ List<Student> students = (List<Student>)session.createQuery("select stu from Student stu where name like ?") .setParameter(0, "%刘%") .list();
在hql中同样支持基于 ? 的参数化形式查询,注意:在jdbc中,setParameter的下标是从1开始的,而hibernate的setParameter的下标是从0开始的。
3.基于 :xx 的别名的方式设置参数
/** * 在hql中可以使用别名的方式来查询,格式是 :xxx 通过setParameter来设置别名 */ List<Student> students = (List<Student>)session.createQuery("select stu from Student stu where name like :name and sex like :sex") .setParameter("name", "%王%").setParameter("sex", "%男%") .list();
4.如果返回的值只有一个,可以使用uniqueResult方法
/** * 如果得到的值只有一个,则可以使用uniqueResult方法 */ Long stu = (Long)session.createQuery("select count(*) from Student stu where name like :name and sex like :sex") .setParameter("name", "%王%").setParameter("sex", "%男%") .uniqueResult(); /** * 如果得到的值只有一个,则可以使用uniqueResult方法 */ Student stu = (Student)session.createQuery("select stu from Student stu where id = ?") .setParameter(0, 1) .uniqueResult();
5.基于投影的查询
/** * 基于投影的查询,如果返回多个值,这些值都是保存在一个object[]数组当中 */ List<Object[]> stus = (List<Object[]>)session.createQuery("select stu.name, stu.sex from Student stu where name like
:name and sex like :sex") .setParameter("name", "%张%").setParameter("sex", "%男%") .list();
6.基于导航对象的查询
/** * 如果对象中有导航对象,可以直接通过对象导航查询 */ List<Student> stus = (List<Student>)session.createQuery("select stu from Student stu where stu.room.name like :room and sex like :sex") .setParameter("room", "%计算机应用%").setParameter("sex", "%女%") .list();
注意:若直接通过导航对象来查询时,其实际是使用cross join(笛卡儿积)来进行连接查询,这样做性能很差,不建议使用
7.使用 in 进行列表查询
/** * 可以使用in设置基于列表的查询,使用in查询时需要使用别名来进行参数设置, * 通过setParameterList方法即可设置,在使用别名和?的hql语句查询时,?形式的查询必须放在别名前面 */ // List<Student> stus = (List<Student>)session.createQuery("select stu from Student stu where sex like ? and stu.room.id in (:room)") // .setParameter(0, "%女%").setParameterList("room", new Integer[]{1, 2}) // .list(); List<Student> stus = (List<Student>)session.createQuery("select stu from Student stu where stu.room.id in (:room) and stu.sex like :sex") .setParameterList("room", new Integer[]{1, 2}).setParameter("sex", "%女%") .list();
在使用 in 进行列表查询时,这个时候要通过 setParameterList() 方法来设置我们的参数,注意:如果一个参数通过别名来传入,一个是通过 ? 的方式来传入的话,那么通过别名的hql语句以及参数设置语句要放在 ? 的后面,不然hibernate会报错。如果都是使用 别名 来设置参数,则无先后顺序
8.分页查询
/** * 通过setFirstResult(0).setMaxResults(10)可以设置分页查询,相当于offset和pagesize */ List<Student> stus = (List<Student>)session.createQuery("select stu from Student stu where stu.room.name like :room and sex like :sex") .setParameter("room", "%计算机应用%").setParameter("sex", "%女%").setFirstResult(0).setMaxResults(10) .list();
9.内连接查询
/** * 使用对象的导航查询可以完成连接查询,但是使用的是Cross Join(笛卡儿积),效率不高,所以建议使用join来查询 */ List<Student> stus = (List<Student>)session.createQuery("select stu from Student stu join stu.room room where room.id=2") .list();
在hql中使用连接查询的语句与我们的sql进行连接查询的语句是有区别的:
hql: select stu from Student stu join stu.room room sql: select t.* from Student t join Classroom c on t.cid=c.id
10.左外连和右外连查询
/** * 左外连和右外连其实是相对的,left join 就是以左边的表为基准, right join 就是以右边的表为基准 */ List<Object[]> stus = (List<Object[]>)session.createQuery("select room.name, count(stu.room.id) from Student stu right join stu.room room group by room.id") .list();
11.创建DTO类,将查询出来的多个字段可以存放到DTO对象中去
/** * 当如果我们查询出多个字段的话,通常会创建一个DTO对象,用来存储我们查询出来的数据,通过 new XXX() 这样的方式 * 前提是XXX这个类里面必须要有接受这些字段的构造方法才行,而且必须要使用类的全名 */ // List<Object[]> stus = (List<Object[]>)session.createQuery("select stu.id,stu.name,stu.sex,room.name,special.name from Student stu left join stu.room room left join room.special special") // .list(); // for(Object[] obj : stus) // { // System.out.println(obj[0] + ", " + obj[1] + ", " + obj[2] + ", " + obj[3] + ", " + obj[4]); // }
List<StudentDTO> stus = (List<StudentDTO>)session.createQuery("select new com.xiaoluo.bean.StudentDTO(stu.id, stu.name, stu.sex, room.name, special.name) from Student stu left join stu.room room left join room.special special")
.list();
12.group having字句
/** * 在hql中不能通过给查询出来的字段设置别名,别名只能设置在from 后面 */ List<Object[]> stus = (List<Object[]>)session.createQuery("select special.name, count(stu.room.special.id) from Student stu right join stu.room.special special group by special.id having count(stu.room.special.id)>150") .list(); // 查询出人数大于150个人的专业
// 查询出每个专业中男生与女生的个数
List<Object[]> stus = (List<Object[]>)session.createQuery("select special.name, stu.sex, count(stu.room.special.id) from Student stu right join stu.room.special special group by special.id,stu.sex") .list();
Hibernate的HQL查询
4.3 使用HQL查询
Hibernate提供了异常强大的查询体系,使用Hibernate有多种查询方式。可以选择使用Hibernate的HQL查询,或者使用条件查询,甚至可以使用原生的SQL查询语句,此外还提供了一种数据过滤功能,这些都可用于筛选目标数据。
下面分别介绍Hibernate的4种数据筛选方法:
4.3.1 HQL查询
HQL是Hibernate Query Language的缩写,HQL的语法很像SQL的语法,但HQL是一种面向对象的查询语言。因此,SQL的操作对象是数据表和列等数据对象,而HQL的操作对象是类、实例、属性等。
HQL是完全面向对象的查询语言,因此可以支持继承和多态等特征。
HQL查询依赖于Query类,每个Query实例对应一个查询对象。使用HQL查询可按如下步骤进行:
(1)获取Hibernate Session对象;
(2)编写HQL语句;
(3)以HQL语句作为参数,调用Session的createQuery方法创建查询对象;
(4)如果HQL语句包含参数,调用Query的setXxx方法为参数赋值;
(5)调用Query对象的list等方法遍历查询结果。
看下面的查询示例:
public class HqlQuery
{
public static void main(String[] args)throws Exception
{
HqlQuery mgr = new HqlQuery();
//调用查询方法
mgr.findPersons();
//调用第二个查询方法
mgr.findPersonsByHappenDate();
HibernateUtil.sessionFactory.close();
}
//第一个查询方法
private void findPersons()
{
//获得Hibernate Session
Session sess = HibernateUtil.currentSession();
//开始事务
Transaction tx = sess.beginTransaction();
//以HQL语句创建Query对象.
//执行setString方法为HQL语句的参数赋值
//Query调用list方法访问查询的全部实例
List pl = sess.createQuery(“from Person p where p.myEvents.title
= :eventTitle”)
.setString(“eventTitle”,”很普通事情”)
.list();
//遍历查询的全部结果
for (Iterator pit = pl.iterator() ; pit.hasNext(); )
{
Person p = ( Person )pit.next();
System.out.println(p.getName());
}
//提交事务
tx.commit();
HibernateUtil.closeSession();
}
//第二个查询方法
private void findPersonsByHappenDate()throws Exception
{
//获得Hibernate Session对象
Session sess = HibernateUtil.currentSession();
Transaction tx = sess.beginTransaction();
//解析出Date对象
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd”);
Date start = sdf.parse(“2005-01-01”);
System.out.println(“系统开始通过日期查找人” + start);
//通过Session的createQuery方法创建Query对象
//设置参数
//返回结果集
List pl = sess.createQuery(
“from Person p where p.myEvents.happenDate between :firstDate
and :endDate”)
.setDate(“firstDate”,start)
.setDate(“endDate”,new Date())
.list();
//遍历结果集
for (Iterator pit = pl.iterator() ; pit.hasNext(); )
{
Person p = ( Person )pit.next();
System.out.println(p.getName());
}
tx.commit();
HibernateUtil.closeSession();
}
}
通过上面的示例程序,可看出查询步骤基本相似。Query对象可以连续多次设置参数,这得益于Hibernate Query的设计。
通常,setXxx方法的返回值都是void,但Hibernate Query的setXxx方法返回值是Query本身。因此,程序通过Session创建Query后,直接多次调用setXxx方法为HQL语句的参数赋值,再直接调用list方法返回查询到的全部结果即可。
Query还包含两个方法:
● setFirstResult(int firstResult),设置返回的结果集从第几条记录开始。
● setMaxResults(int maxResults),设置本次查询返回的结果数。
这两个方法用于实现Hibernate分页。
下面简单介绍HQL语句的语法。
HQL语句本身是不区分大小写的。也就是说,HQL语句的关键字和函数都是不区分大小写的。但HQL语句中所使用的包名、类名、实例名和属性名都区分大小写。
4.3.2 HQL查询的from子句
from子句是最简单的HQL语句,也是最基本的HQL语句。from关键字后紧跟持久化类的类名。例如:
from Person
表明从Person持久化类中选出全部的实例。
大部分时候,推荐为该Person的每个实例起别名。例如:
from Person as p
在上面的HQL语句中,Person持久化类中的实例的别名为p,既然 p是实例名,因此也应该遵守Java的命名规则:第一个单词的首字母小写,后面每个单词的首字母大写。
命名别名时,as关键字是可选的,但为了增加可读性,建议保留。
from后还可同时出现多个持久化类,此时将产生一个笛卡儿积或跨表的连接。
4.3.3 HQL查询的select子句
select子句用于确定选择出的属性,当然select选择的属性必须是from后持久化类包含的属性。例如:
select p.name from Person as p
select可以选择任意属性,不仅可以选择持久化类的直接属性,还可以选择组件属性包含的属性,例如:
select p.name.firstName from Person as p
select也支持将选择出的属性存入一个List对象中,例如:
select new list(p.name , p.address) from Person as p
甚至可以将选择出的属性直接封装成对象,例如:
select new ClassTest(p.name , p.address) from Person as p
前提是ClassTest支持p.name和p.address的构造器,假如p.name的数据类型是 String,p.address的数据类型是String,则ClassTest必须有如下的构造器:
ClassTest(String s1, String s2)
select还支持给选中的表达式命名别名,例如:
select p.name as personName from Person as p
这种用法与new map结合使用更普遍。如:
select new map(p.name as personName) from Person as p
在这种情形下,选择出的是Map结构,以personName为key,实际选出的值作为value。
4.3.4 HQL查询的聚集函数
HQL也支持在选出的属性上,使用聚集函数。HQL支持的聚集函数与SQL完全相同,有如下5个:
● avg,计算属性平均值。
● count,统计选择对象的数量。
● max,统计属性值的最大值
● min,统计属性值的最小值。
● sum,计算属性值的总和。
例如:
select count(*) from Person
select max(p.age) from Person as p
select子句还支持字符串连接符、算术运算符以及SQL函数。如:
select p.name || “” || p.address from Person as p
select子句也支持使用distinct和all关键字,此时的效果与SQL中的效果完全相同。
4.3.5 多态查询
HQL语句被设计成能理解多态查询,from后跟的持久化类名,不仅会查询出该持久化类的全部实例,还会查询出该类的子类的全部实例。
如下面的查询语句:
from Person as p
该查询语句不仅会查询出Person的全部实例,还会查询出Person的子类,如Teacher的全部实例,前提是Person和Teacher完成了正确的继承映射。
HQL支持在from子句中指定任何Java类或接口,查询会返回继承了该类的持久化子类的实例或返回实现该接口的持久化类的实例。下面的查询语句返回所有被持久化的对象:
from java.lang.Object o
如果Named接口有多个持久化类,下面的语句将返回这些持久化类的全部实例:
from Named as n
注意:后面的两个查询将需要多个SQL SELECT语句,因此无法使用order by子句对结果集进行排序,从而,不允许对这些查询结果使用Query.scroll()方法。
4.3.6 HQL查询的where子句
where子句用于筛选选中的结果,缩小选择的范围。如果没有为持久化实例命名别名,可以直接使用属性名引用属性。
如下面的HQL语句:
from Person where name like \’tom%\’
上面HQL语句与下面的语句效果相同:
from Person as p where p.name like “tom%”
在后面的HQL语句中,如果为持久化实例命名了别名,则应该使用完整的属性名。两个HQL语句都可返回name属性以tom开头的实例。
复合属性表达式加强了where子句的功能,例如如下HQL语句:
from Cat cat where cat.mate.name like “kit%”
该查询将被翻译成为一个含有内连接的SQL查询,翻译后的SQL语句如下:
select * from cat_table as table1 cat_table as table2 where table1.mate =
table2.id and table1.name like “kit%”
再看下面的HQL查询语句:
from Foo foo where foo.bar.baz.customer.address.city like”guangzhou%”
翻译成SQL查询语句,将变成一个四表连接的查询。
=运算符不仅可以被用来比较属性的值,也可以用来比较实例:
from Cat cat, Cat rival where cat.mate = rival.mate
select cat, mate
from Cat cat, Cat mate
where cat.mate = mate
特殊属性(小写)id可以用来表示一个对象的标识符。(也可以使用该对象的属性名。)
from Cat as cat where cat.id = 123
from Cat as cat where cat.mate.id = 69
第二个查询是一个内连接查询,但在HQL查询语句下,无须体会多表连接,而完全使用面向对象方式的查询。
id也可代表引用标识符。例如,Person类有一个引用标识符,它由country属性 与medicareNumber两个属性组成。
下面的HQL语句有效:
from Person as person
where person.id.country = \’AU\’
and person.id.medicareNumber = 123456
from Account as account
where account.owner.id.country = \’AU\’
and account.owner.id.medicareNumber = 123456
第二个查询跨越两个表Person和Account。是一个多表连接查询,但此处感受不到多表连接查询的效果。
在进行多态持久化的情况下,class关键字用来存取一个实例的鉴别值(discriminator value)。嵌入where子句中的Java类名,将被作为该类的鉴别值。例如:
from Cat cat where cat.class = DomesticCat
where子句中的属性表达式必须以基本类型或java.lang.String结尾,不要使用组件类型属性结尾,例如Account有Person属性,而Person有Name属性,Name有firstName属性。
看下面的情形:
from Account as a where a.person.name.firstName like “dd%” //正确
from Account as a where a.person.name like “dd%” //错误
4.3.7 表达式
HQL的功能非常丰富,where子句后支持的运算符异常丰富,不仅包括SQL的运算符,还包括EJB-QL的运算符等。
where子句中允许使用大部分SQL支持的表达式:
● 数学运算符+、–、*、/ 等。
● 二进制比较运算符=、>=、<=、<>、!=、like等。
● 逻辑运算符and、or、not等。
● in、not in、between、is null、is not null、is empty、is not empty、member of和not member of等。
● 简单的case、case … when … then … else … end和case、case when … then … else … end等。
● 字符串连接符value1 || value2或使用字符串连接函数concat(value1 , value2)。
● 时间操作函数current_date()、current_time()、current_timestamp()、second()、minute()、hour()、day()、month()、year()等。
● HQL还支持EJB-QL 3.0所支持的函数或操作substring()、trim()、lower()、upper()、length()、locate()、abs()、sqrt()、bit_length()、coalesce()和nullif()等。
● 还支持数据库的类型转换函数,如cast(… as …),第二个参数是Hibernate的类型名,或者extract(… from …),前提是底层数据库支持ANSI cast() 和extract()。
● 如果底层数据库支持如下单行函数sign()、trunc()、rtrim()、sin()。则HQL语句也完全可以支持。
● HQL语句支持使用?作为参数占位符,这与JDBC的参数占位符一致,也可使用命名参数占位符号,方法是在参数名前加冒号 :,例如 :start_date和:x1等。
● 当然,也可在where子句中使用SQL常量,例如\’foo\’、69、\’1970-01-01 10:00: 01.0\’等。
● 还可以在HQL语句中使用java public static final 类型的常量,例如eg.Color.TABBY。
除此之外,where子句还支持如下的特殊关键字用法。
● in与between…and可按如下方法使用:
from DomesticCat cat where cat.name between \’A\’ and \’B\’
from DomesticCat cat where cat.name in ( \’Foo\’,\’Bar\’,\’Baz\’)
● 当然,也支持not in和not between…and的使用,例如:
from DomesticCat cat where cat.name not between \’A\’ and \’B\’
from DomesticCat cat where cat.name not in ( \’Foo\’,\’Bar\’,\’Baz\’ )
● 子句is null与is not null可以被用来测试空值,例如:
from DomesticCat cat where cat.name is null;
from Person as p where p.address is not null;
如果在Hibernate配置文件中进行如下声明:
<property name=”hibernate.query.substitutions”>true 1, false 0</property>
上面的声明表明,HQL转换SQL语句时,将使用字符1和0来取代关键字true和false。然后将可以在表达式中使用布尔表达式,例如:
from Cat cat where cat.alive = true
● size关键字用于返回一个集合的大小,例如:
from Cat cat where cat.kittens.size > 0
from Cat cat where size(cat.kittens) > 0
● 对于有序集合,还可使用minindex与maxindex函数代表最小与最大的索引序数。同理,可以使用minelement与maxelement函数代表集合中最小与最大的元素。 例如:
from Calendar cal where maxelement(cal.holidays) > current date
from Order order where maxindex(order.items) > 100
from Order order where minelement(order.items) > 10000
● 可以使用SQL函数any、some、all、exists、in操作集合里的元素,例如:
//操作集合元素
select mother from Cat as mother, Cat as kit
where kit in elements(foo.kittens)
//p的name属性等于集合中某个元素的name属性
select p from NameList list, Person p
where p.name = some elements(list.names)
//操作集合元素
from Cat cat where exists elements(cat.kittens)
from Player p where 3 > all elements(p.scores)
from Show show where \’fizard\’ in indices(show.acts)
注意这些结构变量size、elements、indices、minindex、maxindex、minelement、maxelement 等,只能在where子句中使用。
● where子句中,有序集合的元素(arrays, lists, maps)可以通过[ ]运算符访问。例如:
//items是有序集合属性,items[0]代表第一个元素
from Order order where order.items[0].id = 1234
//holidays是map集合属性,holidays[national day]代表其中一个元素
select person from Person person, Calendar calendar
where calendar.holidays[\’national day\’] = person.birthDay
and person.nationality.calendar = calendar
//下面同时使用list 集合和map集合属性
select item from Item item, Order order
where order.items[ order.deliveredItemIndices[0] ] = item and order.id = 11
select item from Item item, Order order
where order.items[ maxindex(order.items) ] = item and order.id = 11
在[]中的表达式甚至可以是一个算术表达式,例如:
select item from Item item, Order order
where order.items[ size(order.items) – 1 ] = item
借助于HQL,可以大大简化选择语句的书写,提高查询语句的可读性,看下面的HQL语句:
select cust
from Product prod,
Store store
inner join store.customers cust
where prod.name = \’widget\’
and store.location.name in ( \’Melbourne\’, \’Sydney\’ )
and prod = all elements(cust.currentOrder.lineItems)
如果翻译成SQL语句,将变成如下形式:
SELECT cust.name, cust.address, cust.phone, cust.id, cust.current_order
FROM customers cust,
stores store,
locations loc,
store_customers sc,
product prod
WHERE prod.name = \’widget\’
AND store.loc_id = loc.id
AND loc.name IN ( \’Melbourne\’, \’Sydney\’ )
AND sc.store_id = store.id
AND sc.cust_id = cust.id
AND prod.id = ALL(
SELECT item.prod_id
FROM line_items item, orders o
WHERE item.order_id = o.id
AND cust.current_order = o.id
)
4.3.8 order by子句
查询返回的列表(list)可以根据类或组件属性的任何属性进行排序,例如:
from Person as p
order by p.name, p.age
还可使用asc或desc关键字指定升序或降序的排序规则,例如:
from Person as p
order by p.name asc , p.age desc
如果没有指定排序规则,默认采用升序规则。即是否使用asc关键字是没有区别的,加asc是升序排序,不加asc也是升序排序。
4.3.9 group by子句
返回聚集值的查询可以对持久化类或组件属性的属性进行分组,分组所使用的group by子句。看下面的HQL查询语句:
select cat.color, sum(cat.weight), count(cat)
from Cat cat
group by cat.color
类似于SQL的规则,出现在select后的属性,要么出现在聚集函数中,要么出现在group by的属性列表中。看下面示例:
//select后出现的id出现在group by之后,而name属性则出现在聚集函数中
select foo.id, avg(name), max(name)
from Foo foo join foo.names name
group by foo.id
having子句用于对分组进行过滤,如下:
select cat.color, sum(cat.weight), count(cat)
from Cat cat
group by cat.color
having cat.color in (eg.Color.TABBY, eg.Color.BLACK)
注意:having子句用于对分组进行过滤,因此having子句只能在有group by子句时才可以使用,没有group by子句,不能使用having子句。
Hibernate的HQL语句会直接翻译成数据库SQL语句。因此,如果底层数据库支持的having子句和group by子句中出现一般函数或聚集函数,HQL语句的having子句和order by 子句中也可以出现一般函数和聚集函数。
例如:
select cat
from Cat cat
join cat.kittens kitten
group by cat
having avg(kitten.weight) > 100
order by count(kitten) asc, sum(kitten.weight) desc
注意:group by子句与 order by子句中都不能包含算术表达式。
4.3.10 子查询
如果底层数据库支持子查询,则可以在HQL语句中使用子查询。与SQL中子查询相似的是,HQL中的子查询也需要使用()括起来。如:
from Cat as fatcat
where fatcat.weight > ( select avg(cat.weight) from DomesticCat cat )
如果select中包含多个属性,则应该使用元组构造符:
from Cat as cat
where not ( cat.name, cat.color ) in (
select cat.name, cat.color from DomesticCat cat
)
4.3.11 fetch关键字
对于集合属性,Hibernate默认采用延迟加载策略。例如,对于持久化类Person,有集合属性scores。加载Person实例时,默认不加载scores属性。如果Session被关闭,Person实例将无法访问关联的scores属性。
为了解决该问题,可以在Hibernate映射文件中取消延迟加载或使用fetch join,例如:
from Person as p join p.scores
上面的fetch语句将会初始化person的scores集合属性。
如果使用了属性级别的延迟获取,可以使用fetch all properties来强制Hibernate立即抓取那些原本需要延迟加载的属性,例如:
from Document fetch all properties order by name
from Document doc fetch all properties where lower(doc.name) like \’%cats%\’
4.3.12 命名查询
HQL查询还支持将查询所用的HQL语句放入配置文件中,而不是代码中。通过这种方式,可以大大提供程序的解耦。
使用query元素定义命名查询,下面是定义命名查询的配置文件片段:
<!– 定义命名查询 –>
<query name=”myNamedQuery”>
<!– 此处确定命名查询的HQL语句 –>
from Person as p where p.age > ?
</query>
该命名的HQL查询可以直接通过Session访问,调用命名查询的示例代码如下:
private void findByNamedQuery()throws Exception
{
//获得Hibernate Session对象
Session sess = HibernateUtil.currentSession();
//开始事务
Transaction tx = sess.beginTransaction();
System.out.println(“执行命名查询”);
//调用命名查询
List pl = sess.getNamedQuery(“myNamedQuery”)
//为参数赋值
.setInteger(0 , 20)
//返回全部结果
.list();
//遍历结果集
for (Iterator pit = pl.iterator() ; pit.hasNext(); )
{
Person p = ( Person )pit.next();
System.out.println(p.getName());
}
//提交事务
tx.commit();
HibernateUtil.closeSession();
}
Hibernate的批量处理
Hibernate的批量处理
Hibernate完全以面向对象的方式来操作数据库,当程序里以面向对象的方式操作持久化对象时,将被自动转换为对数据库的操作。例如调用Session的delete()方法来删除持久化对象,Hibernate将负责删除对应的数据记录;当执行持久化对象的set方法时,Hibernate将自动转换为对应的update方法,修改数据库的对应记录。
问题是如果需要同时更新100 000条记录,是不是要逐一加载100 000条记录,然后依次调用set方法——这样不仅繁琐,数据访问的性能也十分糟糕。对这种批量处理的场景,Hibernate提供了批量处理的解决方案,下面分别从批量插入、批量更新和批量删除3个方面介绍如何面对这种批量处理的情形。
1) 批量插入
如果需要将100 000条记录插入数据库,通常Hibernate可能会采用如下做法:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
User u = new User (…..);
session.save(customer);
}
tx.commit();
session.close();
但随着这个程序的运行,总会在某个时候运行失败,并且抛出OutOfMemoryException(内存溢出异常)。这是因为Hibernate的Session持有一个必选的一级缓存,所有的User实例都将在Session级别的缓存区进行了缓存的缘故。
为了解决这个问题,有个非常简单的思路:定时将Session缓存的数据刷新入数据库,而不是一直在Session级别缓存。可以考虑设计一个累加器,每保存一个User实例,累加器增加1。根据累加器的值决定是否需要将Session缓存中的数据刷入数据库。
下面是增加100 000个User实例的代码片段:
private void testUser()throws Exception
{
//打开Session
Session session = HibernateUtil.currentSession();
//开始事务
Transaction tx = session.beginTransaction();
//循环100 000次,插入100 000条记录
for (int i = 0 ; i < 1000000 ; i++ )
{
//创建User实例
User u1 = new User();
u1.setName(“xxxxx” + i);
u1.setAge(i);
u1.setNationality(“china”);
//在Session级别缓存User实例
session.save(u1);
//每当累加器是20的倍数时,将Session中的数据刷入数据库,并清空Session缓存
if (i % 20 == 0)
{
session.flush();
session.clear();
tx.commit();
tx = session.beginTransaction();
}
}
//提交事务
tx.commit();
//关闭事务
HibernateUtil.closeSession();
}
上面代码中,当i%20 == 0时,手动将Session处的缓存数据写入数据库,并手动提交事务。如果不提交事务,数据将依然缓存在事务处——未进入数据库,也将引起内存溢出的异常。
这是对Session级别缓存的处理,还应该通过如下配置来关闭SessionFactory的二级 缓存。
hibernate.cache.use_second_level_cache false
注意:除了要手动清空Session级别的缓存外,最好关闭SessionFactory级别的二级缓存。否则,即使手动清空Session级别的缓存,但因为在SessionFactory级别还有缓存,也可能引发异常。
2) 批量更新
上面介绍的方法同样适用于批量更新数据,如果需要返回多行数据,可以使用scroll()方法,从而可充分利用服务器端游标所带来的性能优势。下面是进行批量更新的代码片段:
private void testUser()throws Exception
{
//打开Session
Session session = HibernateUtil.currentSession();
//开始事务
Transaction tx = session.beginTransaction();
//查询出User表中的所有记录
ScrollableResults users = session.createQuery(“from User”)
.setCacheMode(CacheMode.IGNORE)
.scroll(ScrollMode.FORWARD_ONLY);
int count=0;
//遍历User表中的全部记录
while ( users.next() )
{
User u = (User) users.get(0);
u.setName(“新用户名” + count);
//当count为20的倍数时,将更新的结果从Session中flush到数据库
if ( ++count % 20 == 0 )
{
session.flush();
session.clear();
}
}
tx.commit();
HibernateUtil.closeSession();
}
通过这种方式,虽然可以执行批量更新,但效果非常不好。执行效率不高,而且需要先执行数据查询,然后再执行数据更新,并且这种更新将是逐行更新,即每更新一行记录,都需要执行一条update语句,性能非常低下。
为了避免这种情况,Hibernate提供了一种类似于SQL的批量更新和批量删除的HQL语法。
3) SQL风格的批量更新/删除
Hibernate提供的HQL语句也支持批量的UPDATE和DELETE语法。
批量UPDATE和DELETE语句的语法格式如下:
UPDATE | DELETE FROM? ClassName [WHERE WHERE_CONDITIONS]
关于上面的语法格式有以下四点值得注意:
● 在FROM子句中,FROM关键字是可选的。即完全可以不写FROM关键字。
● 在FROM子句中只能有一个类名,该类名不能有别名。
● 不能在批量HQL语句中使用连接,显式的或隐式的都不行。但可以在WHERE子句中使用子查询。
● 整个WHERE子句是可选的。
假设,需要批量更改User类实例的name属性,可以采用如下代码片段完成:
private void testUser()throws Exception
{
//打开Session
Session session = HibernateUtil.currentSession();
//开始事务
Transaction tx = session.beginTransaction();
//定义批量更新的HQL语句
String hqlUpdate = “update User set name = :newName”;
//执行更新
int updatedEntities = session.createQuery( hqlUpdate )
.setString( “newName”, “新名字” )
.executeUpdate();
//提交事务
tx.commit();
HibernateUtil.closeSession();
}
从上面代码中可以看出,这种语法非常类似于PreparedStatement的executeUpdate语法。实际上,HQL的这种批量更新就是直接借鉴了SQL语法的UPDATE语句。
注意:使用这种批量更新语法时,通常只需要执行一次SQL的UPDATE语句,就可以完成所有满足条件记录的更新。但也可能需要执行多条UPDATE语句,这是因为有继承映射等特殊情况,例如有一个Person实例,它有Customer的子类实例。当批量更新Person实例时,也需要更新Customer实例。如果采用joined-subclass或union-subclass映射策略,Person和Customer实例保存在不同的表中,因此可能需要多条UPDATE语句。
执行一个HQL DELETE,同样使用 Query.executeUpdate() 方法,下面是一次删除上面全部记录的代码片段:
private void testUser()throws Exception
{
//打开Session实例
Session session = HibernateUtil.currentSession();
//开始事务
Transaction tx = session.beginTransaction();
//定义批量删除的HQL语句
String hqlUpdate = “delete User”;
//执行批量删除
int updatedEntities = session.createQuery( hqlUpdate )
.executeUpdate();
//提交事务
tx.commit();
//关闭Session
HibernateUtil.closeSession();
}
基本上用到的hql查询语句就是这些,以后若再遇到会进行补充。