4.1 继 承 映 射对于面向对象的程序设计语言而言,继承和多态是两个最基本的概念。Hibernate的继承映射可以理解为两个持久化类之间的继承关系,例如老师和人之间的关系,老师继承了人,可以认为老师是一个特殊的人,如果对人进行查询,老师实例也将被得到——而无须关注人的实例、老师的实例底层数据库的存储。 Hibernate支持的几种继承映射策略,不管哪种继承映射策略,Hibernate的多态查询都可以良好运行。看如下的几个持久化类。 Person类,是本示例应用中继承树结构的父类,其代码如下: public class Person { //标识属性 private long id; //两个普通属性 private String name; private char gender; //Address的组件属性 private Address address; //标识属性的getter方法 public long getId() { return id; } //标识属性的setter方法 public void setId(long id) { this.id = id; } //此处省略了两个普通属性的getter和setter方法 ... //组件属性address的getter方法 public Address getAddress() { return address; } //组件属性address的setter方法 public void setAddress(Address address) { this.address = address; } } Person类中有一个组件属性address,它的类型为Address,Address也是一个用户自定义类,这个自定义类的代码如下: public class Address { //定义address属性 private String address; //定义zip属性 private String zip; //定义country属性 private String country; //无参数的构造器 public Address() { } //带3个参数的构造器 public Address(String address,String zip,String country) { this.address = address; this.zip = zip; this.country = country; } //下面省略了3个属性的setter和getter方法 ... } 上面的Address类非常简单,它是一个包含3个字符串属性的自定义类。映射Person类的Address属性时,使用基本的组件属性映射语法即可。除此之外,Person类还有两个子类Customer和Employee,而Employee又有一个Manager的子类,而且它们之间还存在关联关系。注意:Person、Customer、Employee和Manager 4个类之间不仅存在关联关系,也存在继承关系,还有组件属性映射,是比较复杂的映射。为读者介绍如此复杂的映射,也是希望带给读者更实际的映射案例。 Customer类的代码如下: //Customer类继承Person类 public class Customer extends Person { //Customer类在Person类的基础上增加了一个comments属性 private String comments; //Customer与Employee之间存在1:N的双向关联 private Employee employee; //comments属性的getter方法 public String getComments() { return comments; } //关联持久化类Employee属性的getter方法 public Employee getEmployee() { return employee; } //comments属性的setter方法 public void setComments(String comments) { this.comments = comments; } //关联持久化类Employee属性的setter方法 public void setEmployee(Employee employee) { this.employee = employee; } } Customer类是Person类的子类,在此基础上增加了comments属性。下面是Employee类的代码: public class Employee extends Person { //员工的title属性 private String title; //员工的salary属性 private double salary; //员工关联的系列Customer private Set<Customer> customers = new HashSet<Customer>(); //Employee与其子类Manager之间存在N:1的双向关联 private Manager manager; //此处省略title和salary的setter和getter方法 ... //Manager属性的getter方法 public Manager getManager() { return manager; } //Manager属性的setter方法 public void setManager(Manager m) { this.manager = m; } //关联属性Customer的getter方法 public Set<Customer> getCustomers() { return customers; } //关联属性Customer的setter方法 public void setCustomers(Set<Customer> customers) { this.customers = customers; } } Employee还存在一个子类Manager。该子类的代码如下: public class Manager extends Employee { //经理管理的部门 private String department; //与经理关联的系列员工 private Set<Employee> employees = new HashSet<Employee>(); //department属性的getter方法 public String getDepartment() { return department; } //department属性的setter方法 public void setDepartment(String department) { this.department = department; } //employees属性的setter方法 public void setEmployees(Set<Employee> s) { this.employees = s; } //employees属性的getter方法 public Set<Employee> getEmployees() { return this.employees; } } 图4.1是如上几个类之间的类图。 图4.1 继承映射中各类的类图下面,采用Hibernate支持的3种继承映射策略来完成如上映射。 4.1.1 采用subclass元素的继承映射在这种映射策略下,整个继承树的所有实例都将保存在同一个表内,即以上的Person、Employee、Customer和Manager实例都将保存在同一个表内。因为将父类和子类的实例全部保存在同一个表内,因此,需要在该表内额外增加一列,使用该列来区分每行记录到底是哪个类的实例——这个列被称为辨别者列(discriminator)。在这种映射策略下,使用subclass来映射子元素,使用discriminator元素来映射辨别者列。此外,每个类映射中都需要指定辨别者列的值。上面整个继承树的映射文件代码如下: <?xml version="1.0" encoding="gb2312"?> <!-- Hibernate的映射文件 --> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="lee"> <!-- 映射Person类 --> <class name="Person" discriminator-value="普通人"> <!-- 映射标识属性 --> <id name="id" column="person_id"> <!-- 使用identity的主键生成器策略 --> <generator class="identity"/> </id> <!-- 映射辨别者列 --> <discriminator column="wawa" type="string"/> <!-- 以下映射两个基本属性 --> <property name="name" length="80"/> <property name="gender"/> <!-- 下面映射了一个组件属性 --> <component name="address"> <!-- 映射组件属性的3个基本属性 --> <property name="address"/> <property name="zip"/> <property name="country"/> </component> <!-- 使用subclass元素映射Person类的子类Employee --> <subclass name="Employee" discriminator-value="雇员"> <!-- 映射两个基本属性 --> <property name="title" /> <property name="salary" /> <!-- 映射N-1的关联映射 --> <many-to-one name="manager" column="manager_id"/> <!-- 映射与Customer类之间的1-N关联 --> <set name="customers" inverse="true"> <key column="empoyee_id"/> <one-to-many class="Customer"/> </set> <!-- 使用subclass元素映射Employee类的子类Manager --> <subclass name="Manager" discriminator-value="经理"> <!-- 映射Manager类的基本属性department --> <property name="department"/> <!-- 映射Manager类的关联属性Employee属性 --> <set name="employees" inverse="true"> <key column="manager_id"/> <one-to-many class="Employee"/> </set> </subclass> </subclass> <!-- 使用subclass元素映射Person类的Customer子类 --> <subclass name="Customer" discriminator-value="顾客"> <!-- 映射Customer类的comments属性 --> <property name="comments"/> <!-- 映射Customer类的N-1关联映射 --> <many-to-one name="employee" column="empoyee_id"/> </subclass> </class> </hibernate-mapping> 在上面的映射文件中,指定了一个辨别者列,该列的列名为wawa,该列的值没有实际意义,仅用于区分该列的数据是哪个类的实例。其中Person类实例的wawa列的值为普通人,而Employee类实例的wawa列的值为雇员,而Manager类实例的wawa列的值为经理,Customer类实例的wawa列的值为顾客。使用一个主程序保存一系列的记录,分别保存普通人、员工、顾客和经理等角色,数据表的结构如图4.2所示。 图4.2 subclass继承映射策略的表结构如图4.2所示,辨别者列wawa用于区分该条记录是哪个类的实例。图4.2中有很多NULL值,这正是这种映射策略的劣势所在:所有子类定义的字段,不能有非空约束。因为如果为这些字段增加非空约束,那么父类的实例在这些列根本没有值,这肯定引起数据完整性冲突,导致父类的实例无法保存到数据库。注意:使用subclass继承映射策略时,其子类中属性映射的字段都不可有非空约束。 4.1.2 采用joined-subclass元素的继承映射采用这种映射策略时,父类实例保存在父类表里,而子类实例则由父类表和子类表共同存储。因为子类实例也是一个特殊的父类实例,因此必然也包含了父类实例的属性,于是将子类与父类共有的属性保存在父类表中,而子类增加的属性,则保存在子类表中。在这种映射策略下,无须使用辨别者列,但需要为每个子类使用key元素映射共有主键,该主键的列表必须与父类标识属性的列名相同。但如果继承树的深度很深,可能查询一个子类实例时,需要跨越多个表,因为子类的数据依次保存在其多个父类中。注意:使用joined-subclass继承映射策略时,必须使用key元素映射父子类的共有主键,且这些共有主键列的列名必须相同。使用joined-subclass映射策略的映射文件如下: <?xml version="1.0" encoding="gb2312"?> <!-- Hibernate的映射文件 --> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="lee"> <!-- 映射Person类 --> <class name="Person"> <!-- 映射标识属性 --> <id name="id" column="person_id"> <!-- 使用identity的主键生成器策略 --> <generator class="identity"/> </id> <!-- 以下映射两个基本属性 --> <property name="name" length="80"/> <property name="gender"/> <!-- 下面映射了一个组件属性 --> <component name="address"> <!-- 映射组件属性的3个基本属性 --> <property name="address"/> <property name="zip"/> <property name="country"/> </component> <!-- 使用joined-subclass元素映射Person类的Employee子类 --> <joined-subclass name="Employee"> <!-- 必须使用key元素映射父子类的共有主键 --> <key column="person_id"/> <!-- 映射Employee类的两个普通属性 --> <property name="title" not-null="true"/> <property name="salary" not-null="true"/> <!-- 映射Employee类与Manager类之间的N-1关联 --> <many-to-one name="manager" column="manager_id"/> <!-- 映射Employee类与Customer类之间的1-N关联 --> <set name="customers" inverse="true"> <key column="employee_id"/> <one-to-many class="Customer"/> </set> <!-- 使用joined-subclass元素映射Employee类的Manager子类 --> <joined-subclass name="Manager"> <!-- 必须使用key元素映射父子类的共有主键 --> <key column="person_id"/> <!-- 映射Manager类的department属性 --> <property name="department"/> <!-- 映射Employee类与Manager类之间的1-N关联--> <set name="employees" inverse="true"> <key column="manager_id"/> <one-to-many class="Employee"/> </set> </joined-subclass> </joined-subclass> <!-- 使用joined-subclass元素映射Person类的Customer子类 --> <joined-subclass name="Customer"> <!-- 必须使用key元素映射父子类的共有主键 --> <key column="person_id"/> <property name="comments" not-null="true"/> <!-- 映射Employee类与Customer类之间的1-N关联 --> <many-to-one name="employee" column="employee_id" not-null="true"/> </joined-subclass> </class> </hibernate-mapping> 以上的配置文件中,子类增加的属性已经可以增加非空约束了。因为子类的属性和父类没有保存在同一个表中,所有子类的属性都可以增加非空约束。注意:对于Employee类和Manager类之间的关联,其外键列依然没有增加非空约 束——这不可能,因为Manager类是Employee类的子类,他们之间的关联实际是一种自关联。所有自关联中的外键列都不可能有非空约束。使用主程序保存一系列记录后,可看到Person表的内容如图4.3所示。 图4.3 joined-subclass映射策略中父类表的内容由图4.3可见,不仅Person的实例保存在Person表中,Employee、Manager和Customer的实例也保存在Person表中,但仅仅保存它们作为Person实例的属性。而作为子类的属性则保存在各自的表中。图4.4是Employee表的内容。 图4.4 joined-subclass继承映射策略中子类表的内容 Customer表的内容如图4.5所示。 图4.5 joined-subclass继承映射策略中子类表的内容如图4.3、4.4和4.5所示,三个表中都有person_id列,这就是它们作为父子类的共有主键,Hibernate正是通过相同的主键值来查询一个子类实例的数据的。例如,需要查询一个id为5的顾客,Hibernate将从Person表中查询出id为5的数据,并查询出Customer表中id为5的记录。将两条记录拼接成一个实例,当然这种拼接是通过一个SQL语句完成的。注意:使用joined-subclass继承映射策略时,查询子类实例的内容时,需要跨越多个表进行查询。到底需要跨越多少个表,取决于该子类有多少层父类。采用joined-subclass映射策略时,无须使用辨别者列,子类增加的属性也可以拥有非空约束,是一种比较理想的映射策略。只是在查询子类实例的数据时,可能需要跨越多个表来查询——不过这些底层的实现无须程序开发者关心。 4.1.3 采用union-subclass元素的继承映射还有一种映射策略,与刚才介绍的joined-subclass映射策略非常相似,子类增加的属性也可以有非空约束——即父类实例的数据保存在父表中,而子类实例的数据则保存在子表中,与采用joined-subclass映射策略不同的是,子类实例的数据仅保存在子类表中,而在父类表中没有任何记录。在这种映射策略下,子类表的字段会比父类表字段要多,因为子类表的字段等于父类表字段加子类增加属性的总和。这种映射策略下,既不需要使用辨别者列,也无须使用key元素来映射共有主键。如果单从数据库来看,几乎难以看出它们之间存在继承关系。下面是采用union-subclass继承映射策略的映射文件代码: <?xml version="1.0" encoding="gb2312"?> <!-- Hibernate的映射文件 --> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="lee"> <!-- 映射Person类 --> <class name="Person"> <!-- 映射标识属性 --> <id name="id" column="person_id"> <!-- 使用identity的主键生成器策略 --> <generator class="identity"/> </id> <!-- 以下映射两个基本属性 --> <property name="name" length="80"/> <property name="gender"/> <!-- 下面映射了一个组件属性 --> <component name="address"> <!-- 映射组件属性的3个基本属性 --> <property name="address"/> <property name="zip"/> <property name="country"/> </component> <!-- 使用union-subclass元素映射Person类的Employee子类 --> <union-subclass name="Employee"> <!-- 映射Employee类的两个普通属性 --> <property name="title" not-null="true"/> <property name="salary" not-null="true"/> <!-- 映射Employee类与Manager类之间的N-1关联 --> <many-to-one name="manager" column="manager_id"/> <!-- 映射Employee类与Customer类之间的1-N关联 --> <set name="customers" inverse="true"> <key column="employee_id"/> <one-to-many class="Customer"/> </set> <!-- 使用union -subclass元素映射Employee类的Manager子类 --> <union-subclass name="Manager"> <!-- 映射Manager类的department属性 --> <property name="department"/> <!-- 映射Employee类与Manager类之间的1-N关联 --> <set name="employees" inverse="true"> <key column="manager_id"/> <one-to-many class="Employee"/> </set> </union-subclass> </union-subclass> <!-- 使用union -subclass元素映射Person类的Customer子类 --> <union-subclass name="Customer"> <!-- 映射Customer的普通属性 --> <property name="comments" not-null="true"/> <!-- 映射Employee类与Customer类之间的1-N关联 --> <many-to-one name="employee" column="employee_id" not-null= "true"/> </union-subclass> </class> </hibernate-mapping> 由上面的映射文件可见,使用union-subclass映射策略时,非常简洁,既不需要使用辨别者列,也不需使用key元素映射共有主键列。只要使用union-subclass映射子类即可,这种策略非常方便实用。注意:使用union-subclass映射策略时,既不需要使用辨别者列,也不需要使用key元素映射共有主键列。在这种映射策略下,不同持久化类实例保存在不同的表中,不会出现加载一个实例内容时需要跨越多个表取数据的情况。例如,上面的范例中Person类的实例就是保存在Person表中,而Person子类Customer实例就是保存在Customer表中,不会保存在Person表中。即使在这种映射策略下,执行多态查询时,也需要跨越多个表进行查询。例如,查询满足某个条件的Person实例,Hibernate将会从Person表中查询,也会从Person的所有子类对应的表中查询。在这种映射策略下,插入与上面示例相同的数据。图4.6是Person表中的内容。 图4.6 使用union-subclass映射策略时父类实例对应的表正如前面介绍的,Person表中仅仅保存Person类实例的数据,而Person子类实例的数据则保存在对应的表中。因为子类在Person类的基础上增加了额外的属性,因此,其子类对应表的数据列将更多。图4.7显示了Employee类对应表的内容。 图4.7 使用union-subclass映射策略时Employee实例对应的表以此类推,Manager类对应的表则应该有更多的数据列,图4.8是Manager类实例保存的数据表。 图4.8 使用union-subclass映射策略时Manager实例对应的表采用这种映射策略时,底层数据库的数据看起来更符合正常情况下的数据库设计,不同实体的数据保存在不同数据表中,因此更容易理解。注意:采用union-subclass映射策略时,几乎难以看出子类表和父类表之间的联系,除了子类表会包含父类表的所有数据列之外,如果没有删除数据,整个继承树里所有实例的主键加起来是连续的。 4.2 Hibernate的批量处理 Hibernate完全以面向对象的方式来操作数据库,当程序里以面向对象的方式操作持久化对象时,将被自动转换为对数据库的操作。例如调用Session的delete()方法来删除持久化对象,Hibernate将负责删除对应的数据记录;当执行持久化对象的set方法时,Hibernate将自动转换为对应的update方法,修改数据库的对应记录。问题是如果需要同时更新100 000条记录,是不是要逐一加载100 000条记录,然后依次调用set方法——这样不仅繁琐,数据访问的性能也十分糟糕。对这种批量处理的场景,Hibernate提供了批量处理的解决方案,下面分别从批量插入、批量更新和批量删除3个方面介绍如何面对这种批量处理的情形。 4.2.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级别还有缓存,也可能引发异常。 4.2.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语法。 4.2.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(); } 由Query.executeUpdate()方法返回一个整型值,该值是受此操作影响的记录数量。实际上,Hibernate的底层操作是通过JDBC完成的。因此,如果有批量的UPDATE或DELETE操作被转换成多条UPDATE或DELETE语句,该方法返回的是最后一条SQL语句影响的记录行数。 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(); } 4.4 条 件 查 询条件查询是更具面向对象特色的数据查询方式。条件查询可通过如下3个类完成: ● Criteria,代表一次查询。 ● Criterion,代表一个查询条件。 ● Restrictions,产生查询条件的工具类。执行条件查询的步骤如下:(1)获得Hibernate的Session对象。(2)以Session对象创建Criteria对象。(3)增加Criterion查询条件。(4)执行Criteria的list等方法返回结果集。看下面的条件查询示例: private void test() { //获取Hibernate Session对象 Session session = HibernateUtil.currentSession(); //开始事务 Transaction tx = session.beginTransaction(); //创建Criteria和添加查询条件同步完成 //最后调用list方法,返回查询到的结果集 List l = session.createCriteria(Student.class) //此处增加限制条件必须是Student已经存在的属性 .add( Restrictions.gt("studentNumber" , new Long(20050231) ) ) //如果要增加对Student的关联类的属性的限制则必须重新createCriteria() /如果此关联属性是集合,则只要集合里任意一个对象的属性满足下面条件 .createCriteria("enrolments")即可 .add( Restrictions.gt("semester" , new Short("2") ) ) .list(); Iterator it = l.iterator(); //遍历查询到的记录 while (it.hasNext()) { Student s = (Student)it.next(); System.out.println(s.getName()); Set enrolments = s.getEnrolments(); Iterator iter = enrolments.iterator(); while(iter.hasNext()) { Enrolment e = (Enrolment)iter.next(); System.out.println(e.getCourse().getName()); } } tx.commit(); ibernateUtil.closeSession(); } 在条件查询中,Criteria接口代表一次查询,该查询本身不具备任何的数据筛选功能,Session调用createCriteria(Class clazz)方法对某个持久化类创建条件查询实例。 Criteria包含如下两个方法: ● Criteria setFirstResult(int firstResult),设置查询返回的第一行记录。 ● Criteria setMaxResults(int maxResults),设置查询返回的记录数。这两个方法与Query的这两个方法用法相似,都用于完成查询分页。而Criteria还包含如下常用方法: ● Criteria add(Criterion criterion),增加查询条件。 ● Criteria addOrder(Order order),增加排序规则。 ● List list(),返回结果集。 Criterion接口代表一个查询条件,该查询条件由Restrictions负责产生,Restrictions是专门用于产生查询条件的工具类,它的方法大部分都是静态方法,常用的方法如下: ● static Criterion allEq(Map propertyNameValues),判断指定属性(由Map参数的key指定)和指定值(由Map参数的value指定)是否完全相等。 ● static Criterion between(String propertyName,Object lo, Object hi),判断属性值在某个值范围之内。 ● static Criterion ilike(String propertyName, Object value),判断属性值匹配某个字符串。 ● static Criterion ilike(String propertyName, String value,MatchMode matchMode),判断属性值匹配某个字符串,并确定匹配模式。 ● static Criterion in(String propertyName,Collection values),判断属性值在某个集合内。 ● static Criterion in(String propertyName,Object[] values),判断属性值是数组元素的其中之一。 ● static Criterion isEmpty(String propertyName),判断属性值是否为空。 ● static Criterion isNotEmpty(String propertyName),判断属性值是否不为空。 ● static Criterion isNotNull(String propertyName),判断属性值是否为空。 ● static Criterion isNull(String propertyName),判断属性值是否不为空。 ● static Criterion not(Criterion expression),对Criterion求否。 ● static Criterion sizeEq(String propertyName, int size),判断某个属性的元素个数是否与size相等。 ● static Criterion sqlRestriction(String sql),直接使用SQL语句作为筛选条件。 ● static Criterion sqlRestriction(String sql, Object[] values, Type[] types),直接使用带参数占位符的SQL语句作为条件,并指定多个参数值。 ● static Criterion sqlRestriction(String sql, Object value, Type type),直接使用带参数占位符的SQL语句作为条件,并指定参数值。 Order实例代表一个排序标准,Order有如下构造器: Order(String propertyName, boolean ascending),根据propertyName排序,是否采用升序,如果后一个参数为true,采用升序排序,否则采用降序排序。如果需要使用关联类的属性来增加查询条件,则应该对属性再次使用createCriteria方法。看如下示例: session.createCriteria(Person.class) .add(Restrictions.like("name" , "dd%")) .createCriteria("addresses") .add(Restrictions.like("addressdetail" , "上海%")) .list(); 上面的代码表示建立Person类的条件查询,第一个查询条件是直接过滤Person的属性,即选出name属性以dd开始的Person实例,第二个查询条件则过滤Person关联实例的属性,其中addresses是Person类的关联持久化类Address,而addressdetail则是Address类的属性。值得注意的是,查询并不是查询Address持久化类,而是查询Person持久化类。注意:使用关联类的条件查询,依然是查询原有持久化类的实例,而不是查询被关联类的实例。 4.5 SQL查询 Hibernate还支持使用SQL查询,使用SQL查询可以利用某些数据库的特性,或者用于将原有的JDBC应用迁移到Hibernate应用上。使用命名的SQL查询还可以将SQL语句放在配置文件中配置,从而提高程序的解耦,命名SQL查询还可以用于调用存储过程。如果是一个新的应用,通常不要使用SQL查询。 SQL查询是通过SQLQuery接口来表示的,SQLQuery接口是Query接口的子接口,因此完全可以调用Query接口的方法: ● setFirstResult(),设置返回结果集的起始点。 ● setMaxResults(),设置查询获取的最大记录数。 ● list(),返回查询到的结果集。但SQLQuery比Query多了两个重载的方法: ● addEntity,将查询到的记录与特定的实体关联。 ● addScalar,将查询的记录关联成标量值。执行SQL查询的步骤如下:(1)获取Hibernate Session对象;(2)编写SQL语句;(3)以SQL语句作为参数,调用Session的createSQLQuery方法创建查询对象;(4)如果SQL语句包含参数,调用Query的setXxx方法为参数赋值;(5)调用SQLQuery对象的addEntity或addScalar方法将选出的结果与实体或标量值关联;(6)调用Query的list方法返回查询的结果集。看下面的SQL查询示例: private void test() { //获取Hibernate Session对象 Session session = HibernateUtil.currentSession(); //开始事务 Transaction tx = session.beginTransaction(); //编写SQL语句 String sqlString = "select {s.*} from student s where s.name like '马军'"; //以SQL语句创建SQLQuery对象 List l = session.createSQLQuery(sqlString) //将查询到的记录与特定实体关联起来 .addEntity("s",Student.class) //返回全部的记录集 .list(); //遍历结果集 Iterator it = l.iterator(); while (it.hasNext()) { //因为将查询结果与Student类关联,因此返回的是Student集合 Student s = (Student)it.next(); Set enrolments = s.getEnrolments(); Iterator iter = enrolments.iterator(); while(iter.hasNext()) { Enrolment e = (Enrolment)iter.next(); System.out.println(e.getCourse().getName()); } } //提交事务 tx.commit(); //关闭Session HibernateUtil.closeSession(); } 上面的示例显示了将查询记录关联成一个实体的示例。事实上,SQL查询也支持将查询结果转换成标量值,转换成标量值可以使用addScalar方法,如: Double max = (Double) session.createSQLQuery("select max(cat.weight) as maxWeight from cats cat") .addScalar("maxWeight", Hibernate.DOUBLE); .uniqueResult(); 使用SQL查询,如果需要将查询到的结果转换成特定实体,就要求为选出的字段命名别名。这别名不是随意命名的,而是以“/”实例名.属性名“/”的格式命名,例如: //依次将多个选出的字段命名别名,命名别名时都以ss作为前缀,ss是关联实体的别名 String sqlStr = "select stu.studentId as {ss.studentNumber}," + "stu.name as {ss.name} from " + "student as stu where stu.name like '杨海华'"; List l = session.createSQLQuery(sqlStr) //将查询出的ss实例,关联到Student类 .addEntity("ss",Student.class) .list(); 在第一个示例中,以{s.*}代表该表的全部字段,且关联实例的别名也被指定为s。注意:如果不使用{s.*}的形式,就可让实体别名和表别名互不相同。关联实体的类型时,被关联的类必须有对应的setter方法。 4.5.1 命名SQL查询可以将SQL语句不放在程序中,而放在配置文件中,这种方式以松耦合的方式配置SQL语句,可以提高程序解耦。在Hibernate的映射文件中定义查询名,然后确定查询所用的SQL语句,然后就可以直接调用该命名SQL查询。在这种情况下,不需要调用addEntity()方法,因为在配置命名SQL查询时,已经完成了查询结果与实体的关联。下面是命名SQL查询的配置片段: <!-- 每个sql-query元素定义一个命名SQL查询 --> <sql-query name="mySqlQuery"> <!-- 关联返回的结果与实体类 --> <return alias="s" class="Student"/> <!-- 定义命名SQL查询的SQL语句 --> SELECT {s.*} from student s WHERE s.name like'杨海华' </sql-query> sql-query元素是hibernate-mapping元素的子元素。因此,sql-query定义的名可以直接通过Session访问,上面定义的mySqlQuery查询可以直接访问,下面是使用该命名SQL查询的示例代码: private void testNamedSQl() { //获取Hibernate Session对象 Session session = HibernateUtil.currentSession(); //开始事务 Transaction tx = session.beginTransaction(); //调用命名查询,直接返回结果 List l = session.getNamedQuery("mySqlQuery") .list(); //遍历结果集 Iterator it = l.iterator(); while (it.hasNext()) { //在定义SQL查询时,已经将结果集与Student类关联起来 //因此,集合里的每个元素都是Student实例 Student s = (Student)it.next(); Set enrolments = s.getEnrolments(); Iterator iter = enrolments.iterator(); while(iter.hasNext()) { Enrolment e = (Enrolment)iter.next(); System.out.println("====================================="); System.out.println(e.getCourse().getName()); System.out.println("====================================="); } } tx.commit(); HibernateUtil.closeSession(); } 4.5.2 调用存储过程 Hibernate 3增加了存储过程的支持,该存储过程只能返回一个结果集。下面是Oracle 9i的存储过程示例: CREATE OR REPLACE FUNCTION selectAllEmployments RETURN SYS_REFCURSOR AS st_cursor SYS_REFCURSOR; BEGIN OPEN st_cursor FOR SELECT EMPLOYEE, EMPLOYER, STARTDATE, ENDDATE, REGIONCODE, EID, VALUE, CURRENCY FROM EMPLOYMENT; RETURN st_cursor; END; 如果需要使用该存储过程,可以先将其定义成命名SQL查询,例如: <!-- 定义命名SQL查询,name属性指定命名SQL查询名 --> <sql-query name="selectAllEmployees_SP" callable="true"> <!-- 定义返回列与关联实体类属性之间的映射 --> <return alias="emp" class="Employment"> <!-- 依次定义每列与实体类属性的对应 --> <return-property name="employee" column="EMPLOYEE"/> <return-property name="employer" column="EMPLOYER"/> <return-property name="startDate" column="STARTDATE"/> <return-property name="endDate" column="ENDDATE"/> <return-property name="regionCode" column="REGIONCODE"/> <return-property name="id" column="EID"/> <!-- 将两列值映射到一个关联类的组件属性 --> <return-property name="salary"> <!-- 映射列与组件属性之间的关联 --> <return-column name="VALUE"/> <return-column name="CURRENCY"/> </return-property> </return> { ? = call selectAllEmployments() } </sql-query> 调用存储过程还有如下需要注意的地方: ● 因为存储过程本身完成了查询的全部操作,所以调用存储过程进行的查询无法使用setFirstResult()/setMaxResults()进行分页。 ● 存储过程只能返回一个结果集,如果存储过程返回多个结果集,Hibernate将仅处理第一个结果集,其他将被丢弃。 ● 如果在存储过程里设定SET NOCOUNT ON,将有更好的性能表现。当然也可以没有该设定。4.6 数 据 过 滤数据过滤并不是一种常规的数据查询方法,而是一种整体的筛选方法。数据过滤也可对数据进行筛选,因此,将其放在Hibernate的数据查询框架中介绍。如果一旦启用了数据过滤器,则不管数据查询,还是数据加载,该过滤器将自动作用于所有数据,只有满足过滤条件的记录才会被选出来。过滤器与定义在类和集合映射文件上的“where”属性非常相似。它们的区别是过滤器可以带参数,应用程序可以在运行时决定是否启用指定的过滤器,以及使用什么样的参数值。而映射文件上的“where”属性将一直生效,且无法动态传入参数。过滤器的用法很像数据库视图,区别是视图在数据库中已经定义完成,而过滤器则还需在应用程序中确定参数值。过滤器的使用分成三步:(1)定义过滤器。使用filter-def元素定义过滤器;(2)使用过滤器。使用filter元素使用过滤器;(3)在代码中启用过滤器。前两个步骤都是在Hibernate的映射文件中完成的,其中filter-def是hibernate-mapping元素的子元素,而filter元素是class集合元素的子元素。filter-def元素用于定义一个过滤器,filter则将指定的过滤器应用到指定的持久化类。一个持久化类或集合可以使用多个过滤器,而一个过滤器也可以作用于多个持久化类或集合。看下面的映射文件示例:<?xml version="1.0"?><!-- Hibernate配置文件的文件头,包含DTD等信息 --><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><!-- Hibernate 配置文件的根元素 --><hibernate-mapping > <!-- 每个class元素定义一个持久化类 --> <class name="Category" table="category"> <!-- 定义标识属性 --> <id name="id" column="category_id" > <!-- 指定主键生成器策略 --> <generator class="native"/> </id> <!-- 映射name属性 --> <property name="name" type="string"/> <!-- 映射effectiveStartDate属性 --> <property name="effectiveStartDate" column="eff_start_date" type="java.util.Date"/> <!-- 映射effectiveEndDate属性 --> <property name="effectiveEndDate" column="eff_end_date" type="java.util.Date"/> <!-- 映射N-N关联属性 --> <set cascade="none" inverse="true" name="products" table="product_category"> <!-- 定义关联属性的key,对应连接表中的外键列 --> <key column="category_id"/> <!-- 定义关联属性 --> <many-to-many column="product_id" class="Product"/> </set> <!-- 使用过滤器,并设置过滤器条件 --> <filter name="effectiveDate" condition=":asOfDate BETWEEN eff_start_date and eff_end_date"/> </class> <!-- 定义第二个持久化类 --> <class name="Product" table="product"> <!-- 定义标识属性 --> <id name="id" column="product_id" > <!-- 指定主键生成器策略 --> <generator class="native"/> </id> <!-- 映射name属性 --> <property name="name" type="string"/> <!-- 映射stockNumber属性 --> <property name="stockNumber" column="stock_number" type="int"/> <!-- 映射effectiveStartDate属性 --> <property name="effectiveStartDate" column="eff_start_date" type="java.util.Date"/> <!-- 映射effectiveEndDate属性 --> <property name="effectiveEndDate" column="eff_end_date" type="java.util.Date"/> <!-- 映射N-N关联属性 --> <set cascade="all" name="categories" fetch="join" table="product_category" > <!-- 定义关联属性的key,对应连接表中的外键列 --> <key column="product_id"/> <!-- 定义关联属性 --> <many-to-many column="category_id" class="Category" fetch="join"> <!-- 对关联属性使用第一个过滤器 --> <filter name="effectiveDate" condition=":asOfDate BETWEEN eff_start_date and eff_end_date"/> <!-- 对关联属性使用第二个过滤器 --> <filter name="category" condition="category_id = :catId"/> </many-to-many> </set> <filter name="effectiveDate" condition=":asOfDate BETWEEN eff_start_date AND eff_end_date"/> </class> <!-- 定义第一个过滤器,该过滤器包含一个date类型的参数 --> <filter-def name="effectiveDate"> <filter-param name="asOfDate" type="date"/> </filter-def> <!-- 定义第二个过滤器,该过滤器包含一个long类型的参数 --> <filter-def name="category"> <filter-param name="catId" type="long"/> </filter-def></hibernate-mapping>在上面的配置文件中,定义了两个过滤器,过滤器的定义通过filter-def元素完成。定义过滤器时,只需要指定过滤器的名字,以及过滤器的参数即可。如Java里的一个方法声明,只有方法名和参数列表,具体的方法实现是没有的。过滤器的过滤条件是使用过滤器时才确定的,使用过滤器通过filter元素确定,filter的condition属性用于确定过滤条件,满足该条件的记录才会被抓取到。系统默认不启用过滤器,必须显式通过enableFilter(String filterName)才可以启用过滤器,该方法返回一个Filter实例,Filter包含setParameter方法用于为过滤器参数赋值。一旦启用了过滤器,过滤器在整个Session内有效,所有的数据加载将自动应用该过滤条件,直到调用disableFilter方法。看下面的使用过滤器的示例代码:private void test() throws Exception{ //获取Hibernate Session对象 Session session = HibernateUtil.currentSession(); //开始事务 Transaction tx = session.beginTransaction(); //启用第一个过滤器 session.enableFilter("effectiveDate") //为过滤器设置参数 .setParameter("asOfDate", new Date()); //启动第二个过滤器 session.enableFilter("category") //为过滤器设置参数 .setParameter("catId", new Long(2)); //执行查询,该查询没有任何的查询条件 Iterator results = session.createQuery("from Product as p") .iterate(); //遍历结果集 while (results.hasNext()) { Product p = (Product)results.next(); System.out.println(p.getName()); //此处获取Product关联的种类,过滤器也将自动应用过滤 Iterator it = p.getCategories().iterator(); System.out.println(p.getCategories().size()); while (it.hasNext()) { Category c = (Category)it.next(); System.out.println(c.getName()); } } tx.commit(); HibernateUtil.closeSession();}通过使用过滤器定义常用的数据筛选规则,如果是临时的数据筛选,还是使用常规查询比较好。对于从前使用行列表达式视图的地方,此处可以考虑使用过滤器。