Hibernate 中HQL语句
本章介绍了Hibernate的几种主要检索方式:HQL检索方式、QBC检索方式、SQL检索方式。HQL是Hibernate Query Language的缩写,是官方推荐的查询语言。QBC是Query By Criteria的缩写,是Hibernate提供的一个查询接口。Hibernate是一个轻量级的框架,它允许使用原始SQL语句查询数据库。
HQL是Hiberante官方推荐的Hibernate检索方式,它使用类似SQL的查询语言,以面向对象的方式从数据库中查询。可以使用HQL查询具有继承、多态和关联关系的数据。在检索数据时应优先考虑使用HQL方式。
在讲解本章时,在没有特殊说明时,用到的数据库均为joblog,也就是在第4章建立的数据库。joblog中添加了3个表:学生表student、课程表course和选课表sc。
学生表student中各字段的结构如图6-1所示,字段的中文含义在Comment列中。
图6-1 学生表的数据结构
学生表student中的数据如图6-2所示,没有特殊说明时,用到的均为这6条记录。
此处仍然使用在第4章建立的HibernateProject项目,但是这里新建了一个包hibernate.ch06,这个包存放本章中的所有代码。在hibernate.ch06包中建立学生表对应的持久化类Student.java,代码如下。
图6-2 学生表中的数据
package hibernate.ch06;
// 学生类
public class Student {
private Integer id; // 对象标识符
private Integer sno;// 学号
private String sname; // 姓名
private String ssex;// 性别
private String sdept; // 所在系别
private Integer sage; // 年龄
private String saddress; // 籍贯
…… //省略了所有的get/set访问器
}
课程表course中的各个字段的结构如图6-3所示,字段的中文含义在Comment列中。
图6-3 课程表的结构
课程表中的数据如图6-4所示,如果没有特殊说明,用到的均为这4条记录。
图6-4 课程表的数据
在hibernate.ch06中新建持久化类Course.java类,代码如下。
package hibernate.ch06;
// 课程类
public class Course {
private Integer id; // 对象标识符
private Integer cno;// 课程号
private String cname; // 课程名
private Integer Ccredit; // 学分
…… //省略了get/set访问器
}
选修表sc(sc为student-course的缩写)的结构如图6-5所示,字段的中文含义在Comment列中。
图6-5 选修表的结构
选修表中的数据如图6-6所示,没有特殊说明时,用到的均为这5条记录。
图6-6 选修表的数据
在hibernate.ch06中新建持久化类SC.java,SC.java的代码如下。
package hibernate.ch06;
// 选课类
public class SC implements java.io.Serializable {
private Integer id; // id
private Integer sno; // 学号
private Integer cno; // 课程号
private Integer grade; // 成绩
public SC() {
}
…… // 省略get/set访问器
}
后面的章节中将用这3个表和3个持久化类进行讲解。
使用HQL语句可以检索出一个类的所有对象,如HQL语句“from Student”表示检索Student类的所有对象。下面的程序检索学生类的所有对象。
Query query = session.createQuery("from Student"); // 创建Query对象
List list = query.list(); // 执行查询
// 以下代码做显示用,以后不再写出来
Iterator it = list.iterator();
while (it.hasNext()) {
Student stu = (Student) it.next();
System.out.println("id" + stu.getId());
System.out.println("name" + stu.getSname());
System.out.println("\n");
}
session.createQuery()以HQL查询语句为参数,生成一个查询对象。本例中的HQL语句为“from Student”,这是from子句,格式如下。
from 类名
其中,类名可以为类的全限定名,如:
from hibernate.ch06.Student
Hibernate使用自动引入功能(auto import),会自动寻找需要的类,所以不推荐使用类的全限定名。注意,类名区分大小写,如果写成from student,将会抛出以下异常。
java.lang.NoClassDefFoundError: hibernate/ch06/student (wrong name: hibernate/ch06/Student)
HQL关键字不区分大小写,FROM、from和From是一样的。
调用query.list()时,真正开始执行HQL查询语句,并把查询的结果放在List中。
本例中查询的是Student类中的所有属性,如果查询Student类中的某一个或某几个属性,如查询所有学生的姓名和所在系,需要用到属性查询。
与SQL语句类似,HQL语句可以检索类的某一个或者某几个属性。以下代码查询所有学生的姓名和所在系。
// 创建Query对象
Query query = session.createQuery("select Student.sname,Student.sdept from Student");
List list = query.list(); // 执行查询
// 以下代码显示查询的信息
Iterator it = list.iterator();
while (it.hasNext()) {
Object[] stu = (Object[]) it.next();
System.out.println("id" + stu[0]);
System.out.println("name" + stu[1]);
System.out.println("\n");
}
属性查询使用select关键字,属性查询的格式如下。
select 属性1,属性2,… from 类名
属性前可以加上类名加以限定,如:
select 属性1,属性2,… from 类名
但一般没有必要。
属性查询区分大小写,上面的代码中如果写成:
select SNAME,Sdept from Student
将抛出异常,提示找不到属性SNAME和属性Sdept。
查询结果将只显示查询的属性列。
属性查询的结果,对于用it.next()获得的每条记录,可以存储在Object[]数组中,以便进行存取。
在查询时,可以用关键字as指定查询的别名,指定别名可以简化查询,有时必需指定别名才能进行查询。以下代码查询学号中含有4的学生的姓名和所在系。
select s.sname,s.sdept from Student as s where s.sno like '%4%'from Student s
s就是类Student的别名。注意as可以省略,即下面的查询语句和上面的语句是等效的。
select s.sname,s.sdept from Student s where s.sno like '%4%'from Student s
where条件子句跟SQL中的where条件子句类似,它检索符合条件的对象。例如,查询所有所在系别为计算机系的学生:
select s.sname,s.sdept from Student s where s.dept=’计算机’
where子句指定查询的条件,其语法和SQL类似。
在where子句中可以指定比较运算符:>、>=、<、<=、<>,其含义分别为大于、大于等于、小于、小于等于、不等于。
查询年龄在22到23岁的学生:
from Student s where s.sage>=22 and s.sage<=23
在where子句中指定查询的属性是否为null:is null、is not null,其含义分别表示为空和不为空。
查询所在籍贯为空的学生:
from Student s where s.saddress is null
使用distinct关键字将去掉结果中的重复值,只检索符合条件的对象。如下面的例子检索学生实例中的不重复的年龄。
Session session=Hsf.currentSession();//创建Session
String hql="select distinct s.sage from Student s"; //HQL查询语句
Query query=session.createQuery(hql);//创建查询
List list=query.list(); //执行查询
检索的结果如下,可见结果中去掉了一个重复的22岁。
20
21
22
23
24
HQL语句可以直接对复合条件的对象进行删除,可以指定删除的对象,并在提交后永久持久化到数据库。HQL使用delete进行删除,如删除年龄大于25岁的学生可以使用如下代码。
Session session=Hsf.currentSession(); //创建Session
Transaction tx=null; //声明事务
try{
tx=session.beginTransaction(); //开始事务
//创建查询
String hql="delete Student s where s.sage>25";
Query query=session.createQuery(hql);
query.executeUpdate(); //执行
tx.commit(); //成功,则提交
tx=null;
}catch(Exception e){
e.printStackTrace();
if(tx!=null){
tx.rollback(); //失败则回滚
}
}finally{
session.close();
}
注意以下两点。
在删除对象时,执行query.executeUpdate()进行数据删除,但只有执行了tx.commit()进行事务提交时,才真正从数据库中删除数据。
如果设置了级联删除,则与之相关联的对象实例也被删除。
更新对象的HQL语句与SQL语法很相似,使用update更新对象的值。如下面例子更新对象的sage属性。
ransaction tx=null; //声明事务
try{
tx=session.beginTransaction(); //开始事务
String hql="update Student s set s.sage='22' where s.id=11"; //更新语句
Query query=session.createQuery(hql);
query.executeUpdate(); //执行
tx.commit(); //成功,则提交
tx=null;
}catch(Exception e){
e.printStackTrace();
if(tx!=null){
tx.rollback(); //失败则回滚
}
}finally{
session.close();
}
HQL可以查询经过计算的值,在一些需要计算的地方可以进行计算,例如查询全体学生的姓名和出生年份。
select s.sname,2006-s.sage from Student as s
select子句十分灵活,几乎和SQL语句有着同样的能力,对象的属性值可以参与运算。
这行代码假设当前的年份是2006年。
下面是另外几个查询计算属性值的例子。
select s.sname,2006-s.sage from Student as s
当需要调用函数时,HQL提供了一些类似SQL的函数。这些函数可以简化操作。例如查询学生的姓名、出生日期和性别,其中性别用小写表示。
select s.sname,2006-s.sage,lower(s.ssex) from Student as sselect s.sname,2006-s. sage from Student as s
between...and...用来查询属性值在指定范围内的实体对象,not between...and...用来查询属性值不在指定范围内的实体对象。如查询学生年龄在22到23之间的学生:
select s.sno,s.sname,s.sage from Student s where s.sage between 22 and 23
查询将返回如下结果。
---------------------------------------------------------------------
1 20040001 李晓梅 22 计算机系
---------------------------------------------------------------------
2 20040002 王蒙 23 外语系
---------------------------------------------------------------------
4 20050004 李文 22 计算机系
between后跟的是查询范围的下限,and后跟的是查询范围的上限,所以下面的查询语句总没有对象返回。
from Student s where s.sage between 23 and 22
关键字in用来查询指定属性值属于指定集合的对象,关键字not in用来查询指定属性值不属于指定集合的对象。如查询不是计算机系,也不是数学系的学生。
select s.sno,s.sname,s.sdept from Student s where s.sdept not in ('计算机系','数学系')
查询将返回如下结果。
---------------------------------------------------------------------
20040002 王蒙 外语系
---------------------------------------------------------------------
20050003 姜浩 化学系
---------------------------------------------------------------------
20050005 薛鹏 生物系
用like进行模糊查询时有两个可用的通配符:“%”和“_”。“%”代表长度大于等于0的字符,“_”代表长度为1的单个字符。
查询姓李的学生:
select s.sno,s.sname,s.sdept from Student s where s.sname like '%李%'
查询结果如下。
---------------------------------------------------------------------
20040001 李晓梅 计算机系
---------------------------------------------------------------------
20050004 李文 计算机系
---------------------------------------------------------------------
20050006 李思 数学系
查询姓名为两个字符的学生:
select s.sno,s.sname,s.sdept from Student s where s.sname like '__'
查询结果如下。
---------------------------------------------------------------------
20040002 王蒙 外语系
---------------------------------------------------------------------
20050003 姜浩 化学系
---------------------------------------------------------------------
20050004 李文 计算机系
---------------------------------------------------------------------
20050005 薛鹏 生物系
---------------------------------------------------------------------
20050006 李思 数学系
当要检索指定的多个条件,且条件的逻辑关系为与时,使用“and”关键字。如检索计算机系的女生,这个检索要求包含两个条件:“计算机系”和“女生”。
select s.sno,s.sname,s.sdept from Student s where s.sdept='计算机系' and s.ssex='F'
检索的结果如下。
---------------------------------------------------------------------
20040001 李晓梅 计算机系
---------------------------------------------------------------------
20050004 李文 计算机系
当检索的多个条件,且条件的逻辑关系为或时,使用“or”关键字。如检索姓王,或者年龄大于22岁的学生:
select s.sno,s.sname,s.sdept from Student s where s.sname like '王%' or s.sage>22
检索结果如下。
---------------------------------------------------------------------
20040002 王蒙 外语系
---------------------------------------------------------------------
20050005 薛鹏 生物系
“order by”关键字对结果进行排序,默认为升序。“order by asc”为升序,“order by desc”为降序。例如将学生表中的学生按照年龄升序排序:
from Student s order by s.sage
检索结果如下。
---------------------------------------------------------------------
6 20050006 李思 20 数学系
---------------------------------------------------------------------
3 20050003 姜浩 21 化学系
---------------------------------------------------------------------
1 20040001 李晓梅 22 计算机系
---------------------------------------------------------------------
4 20050004 李文 22 计算机系
---------------------------------------------------------------------
2 20040002 王蒙 23 外语系
---------------------------------------------------------------------
5 20050005 薛鹏 24 生物系
将学生表中的学生按照年龄降序排列,按照所在系升序排列。
from Student s order by s.sage,s.sdept desc
对查询进行分组可以对查询进行细化。分组经常和聚集函数一起使用,这样聚集函数将作用于每个分组。
group by的用法为:
select 属性1,属性2,属性3 ,…,属性n from 类名 group by 属性m
其中属性1,属性2,…,属性n必须满足下列条件。
要么作为聚集函数的参数,要么为属性m。例如,检索各个系的学生的平均年龄:
select avg(s.sage),s.sdept from Student s group by s.sdept
其中字段s.sage作为平均值函数的参数,s.sdept是group by后的一个属性。检索的结果如下。
---------------------------------------------------------------------
化学系 21.0
---------------------------------------------------------------------
计算机系 22.0
---------------------------------------------------------------------
生物系 24.0
---------------------------------------------------------------------
数学系 20.0
---------------------------------------------------------------------
外语系 23.0
检索各个课程号与对应的选课人数。
select cno,count(sno) from SC s group by s.cno
having关键字和group by关键字搭配使用,它对分组后的记录进行筛选,输出符合having指定条件的组。例如查询人数超过1000人的系。
select s.sdept from Student s group by s.sdept having count(*)>1000
查询男生人数多于500人的系。
select s.sdept from Student s where s.ssex=’M’ group by s.sdept having count(*)>500
以上面查询男生人数多于500人的系为例:
select s.sdept from Student s where s.ssex=’M’ group by s.sdept having count(*)>500
查询过程中同时使用group by和having关键字时,查询步骤如下。
(1)检索符合s.ssex=‘M’的所有男生。
(2)根据s.sdept分组成不同的系。
(3)对于每一个分组,计算分组中的记录条数大于500的系。
(4)将符合上述条件的s.sdept选出来。
where和having的区别在于作用对象不同。where作用于基本表,而having作用于分组后的组。
聚集函数包括count()、avg()、sum()、max()、min(),其含义如表6-1所示。
表6-1 聚集函数及其含义
聚 集 函 数 |
含 义 |
count() |
计算符合条件的记录条数 |
avg() |
计算符合条件的平均值 |
sum() |
计算符合条件的和 |
max() |
计算符合条件的最大值 |
min() |
计算符合条件的最小值 |
各个函数的用法举例如下。
检索学生实例的对象个数:
select count(*) from Student
检索计算机系的人数:
select count(*) from Student s where s.sdept='计算机系'
检索学生实例的平均年龄:
select avg(s.sage) from Student s
检索课程表course的所有课程的学分的总和:
select sum(c.ccredit) from Course c
检索课程号为“1”的课程的最高成绩:
select max(s.grade) from SC s where s.cno=1
检索课程号为“1”的课程的最低成绩:
select min(s.grade) from SC s where s.cno=1
聚集函数经常和group by分组关键字搭配使用。
检索各门课程的平均成绩:
select s.cno,avg(s.grade) from SC s group by s.cno
检索各科不及格人数:
select s.cno,count(*) from SC s where s.grade<60 group by s.cno
下面讲述HQL一些比较高级的应用,包括如何使用HQL查询继承关系数据、绑定参数和在配置文件中使用查询语句。
默认情况下,当查询一个类时,Hibernate会自动搜索这个类的所有继承类。假如有如下3个类,类的关系如图6-7所示。
图6-7 Animal类及其子类Bird和Mammal
当调用如下HQL语句时,会查询出所有的Animal实例、Bird实例和Mammal实例。
from Animal
所有的类均继承自java.lang.Object,所以下面的HQL语句查询所有的类的对象实体,即查询所有映射表的记录。
from java.lang.Object
Query接口提供了两个函数,用于限制每次查询返回的对象数。
SetFirstResult(int firstResult) 用于设置从哪一个对象开始检索。参数firstResult设置开始检索的起始记录。
setMaxResults(int maxResults) 用于设置每次检索返回的最大对象数。参数maxResults用于设置每次检索返回的对象数目。
这两个函数结合起来用,经常用于分页显示对象。例如数据库中有10000条记录,如果一次性显示实在太多,就可以进行分页显示。
下面的程序当每次循环时,从Student实例中检索出pageSize个对象,并输出到控制台,是一个典型的分页显示程序。
/**
* 分页输出对象
*
* @param pageSize
* 每页显示的记录条数
*/
public void pagenate(int pageSize) {
Session session = Hsf.currentSession(); // 创建Session
String hql = "from Student";// 检索Student实例的HQL语句
String hql1 = "select count(*) from Student"; // 检索出表中有多少条记录的HQL语句
Query q = session.createQuery(hql1); // 创建Query
List list = q.list(); // 执行查询
int count = ((Integer) list.get(0)).intValue();// 总的对象个数
int pageCount = (count + pageSize - 1) / pageSize; // 总的页数
Query query = session.createQuery(hql); // 创建检索Student的查询
for (int i = 0; i < pageCount; i++) {
query.setFirstResult(i * pageSize);
query.setMaxResults(pageSize);
List list1 = query.list(); // 执行查询
Iterator it = list1.iterator();
System.out.println("***************************");
while (it.hasNext()) {
Student stu = (Student) it.next();
System.out.println(stu.getId() + "\t" + stu.getSno() + "\t" + stu.getSname() + "\t"
+ stu.getSsex() + "\t" + stu.getSage() + "\t" + stu.getSdept() + "\t"
+ stu.getSaddress());
}
}
}
pagenate(int pageSize) 函数对指定的对象实例循环查询,每次循环检索出pageSize个对象。
HQL语句select count(*) from Student检索出学生对象的个数,把对象个数存入到变量count中。
用(count+pageSize-1)/pageSize计算出总的页数。
每次for循环时输出一页的记录。
使用绑定参数可以在程序运行时动态决定一些参数的值。下面比较不使用绑定参数和使用绑定参数时的情况。
第4章的4.3.6节中,ViewLog.java中有一个检索指定时间段日志的HQL语句:
String HQL="from Contents where logdate>='"+beginDate+" 00:00:00' and logdate<= '"+endDate+" 00:00:00'";
上述做法存在以下缺陷。
代码的可读性比较差。
安全性问题,用户可能执行别的代码或存在SQL注入式攻击。
性能低下。
查询语句中以“:”开头的变量叫做命名参数,把命名参数和一个具体的值进行绑定的过程叫做绑定参数。如下面的程序。
//声明hql语句,待绑定的参数以":"开头
String hql="from Student where sname=:name and sage>:age"; //name和age为命名参数
Query query=session.createQuery(hql); //创建查询
query.setParameter("name","李晓梅"); //进行绑定
query.setParameter("age",new Integer(20));
List list=query.list(); //执行查询
对上述代码说明如下。
“:name”指定了命名参数name,“:age”指定了命名参数age。
调用Query接口的setParameter()为命名参数赋一固定值。第一个参数指定参数名称,第二个参数指定参数值。当明确知道参数类型时,则可以使用相应的方法,例如参数为int型,可以使用setInteger()方法;参数为String型,可以使用setString()方法。
可以使用按照参数位置对参数进行绑定,如下面的代码所示。
String hql="from Student where sname=? and sage>?"; //声明hql语句,命名参数用“?”代替
Query query=session.createQuery(hql); //创建查询
query.setParameter(0,"李晓梅"); //绑定参数
query.setParameter(1,new Integer(20));
List list=query.list(); //执行查询
在HQL语句中用问号“?”代替命名参数。此时setParameter()函数的第一个参数指定参数的位置(position),0为HQL查询语句中的第一个参数,1为第二个参数,以此类推。
为了使程序具有更大的灵活性,Hibernate可以在映射文件中配置HQL语句。如下所示为在Student.hbm.xml中的配置。
<hibernate-mapping>
<class name="hibernate.ch06.Student" table="student" catalog="joblog">
<!--此处省略了配置-->
</class>
<query name="searchStudent">
<![CDATA[
from Student s where s.sage>22
]]>
</query>
</hibernate-mapping>
可以用如下代码访问配置文件中的HQL语句。
Session session=Hsf.currentSession();//创建Session
Query query=session.getNamedQuery("searchStudent"); //用getNamedQuery得到查询
List list=query.list(); //执行查询
Iterator it=list.iterator();
while(it.hasNext()){
Student stu=(Student)it.next();
System.out.println(stu.getSname());
}
其中,getNamedQuery()函数用来访问映射文件Student.hbm.xml中配置的HQL语句,参数为配置的名称。
在SQL中,一个select-from-where语句成为一个查询块。将一个查询块嵌套在另一个查询块的where子句或having短语的条件中,这样的查询称为嵌套查询或者子查询。如:
from Student s
where s.sno in
(select sno from sc where cno='1')
上面的HQL语句在Hibernate后台生成的SQL语句为:
select
student0_.id as id1_,
student0_.Sno as Sno1_,
student0_.Sname as Sname1_,
student0_.Ssex as Ssex1_,
student0_.Sdept as Sdept1_,
student0_.Sage as Sage1_,
student0_.Saddress as Saddress1_
from
joblog.student student0_
where
student0_.Sno in (
select
sc1_.Sno
from
joblog.sc sc1_
where
sc1_.Cno='1'
)
在这个例子中,下层查询块select sno from sc where cno='1'是嵌套在上层查询块from Student s where s.sno in的where条件中的。上层查询块又称为外层查询或父查询,下层查询块称为内层查询或者子查询。
嵌套查询的求解方法由里向外处理,每一个子查询在其上一级查询处理之前查询,子查询的结果用于建立父查询的查询条件。
带有IN谓词的子查询指的是父查询与子查询用谓词IN连接,判断某个属性列值是否在子查询的结果中。在嵌套查询中,子查询的结果往往是一个集合。
例如,查询与“李晓梅”在同一个系学习的学生。可以使用如下的方法进行:先查询“李晓梅”所在系,然后查询在这个系里的所有学生。先查询的作为条件是子查询,后查询的是父查询。具体代码如下。
from Student s
where s.sdept in
(select s.sdept from s where s.sname='李晓梅')
如果确切知道子查询返回的是单值,可以用=、>、>=、<、<=、<>比较运算符进行比较子查询。
例:查询与“李晓梅”在同一个系学习的学生。
这个例子与上面的例子一样。“李晓梅”只可能在一个系学习,所以子查询返回单值,可以用比较子查询。
from Student s
where s.sdept=
(select s.sdept from s where s.sname='李晓梅')
使用ANY或者ALL谓词时,必须同时使用比较运算符。查询其他系中比计算机系任一学生年龄小的学生名单。
from Student s
where s.sage<ANY(select s.sage from s where s.sdept='计算机系')
and s.sdept<>'计算机系'
子查询查询出计算机系学生的所有年龄,然后用“<ANY”关键字进行年龄比较。
and后的条件s.sdept<>‘计算机系’是父查询的条件。
带有ANY或ALL的子查询的谓词如下所述。
>ANY,大于子查询结果中的某个值。
<ANY,小于子查询中的某个值。
>=ANY,大于等于子查询中的某个值。
<=ANY,小于等于子查询中的某个值。
=ANY,等于子查询中的某个值。
!=ANY或者<>ANY,不等于子查询中的某个值。
>ALL,大于子查询中的所有值。
<ALL,小于子查询中的所有值。
>=ALL,大于等于子查询中的所有值。
<=ALL,小于等于子查询中的所有值。
=ALL,等于子查询中的所有值。
!=ALL或者<>ALL,不等于子查询中的任何一个值。
对象之间总是有各种各样的关系,关联关系是类之间最常见的关系。多表查询是HQL中的强大功能之一,包括内连接、左连接和右连接等。多表查询的设置及运行都比较麻烦,在运行本节中的示例时,务必保证每一步都没有错误。
在数据库joblog中用到了3个表:student(学生表)、course(课程表)和sc(选课表)。这些表的详细信息见1.1.1节“示例中用到的默认数据库表和数据”。在现实模型中,一个学生可以选择多门课程,一个课程可以被多个学生选择,student和course是多对多的关联关系。为了便于演示HQL的多表查询,本节中假设student和course之间是单向关联关系。
在多对多的关联关系中,一般来说有个中间表,这个表描述了多对多关系,这就是选课表sc,sc每一行数据代表一个学生的选课和成绩。
各个表的主键、外键设置如下。
student表的主键是id字段。
course表的主键是id字段。
sc表的主键是id字段。
sc表中的Sno字段是student表id字段的外键。
sc表中的Cno字段是course表id字段的外键。
图6-8是3个表之间关系的直观表示。
图6-8 3个表之间的关系
在MySQL Query Browser中设置好上述关系。如果此处设置不正确,可能会影响多表连接查询。其中sc表的建表信息如下(其中包含了外键关系)。
CREATE TABLE 'joblog'. 'sc' (
'id' int(10) unsigned NOT NULL auto_increment COMMENT 'id',
'Sno' int(10) unsigned NOT NULL default '0' COMMENT '学号',
'Cno' int(10) unsigned NOT NULL default '0' COMMENT '课程号',
'Grade' int(10) unsigned default NULL COMMENT '成绩',
PRIMARY KEY ('id'),
KEY 'FK_sc_1' ('Sno'),
KEY 'FK_sc_2' ('Cno'),
CONSTRAINT 'FK_sc_1' FOREIGN KEY ('Sno') REFERENCES 'student' ('id'), /* 外键信息 */
CONSTRAINT 'FK_sc_2' FOREIGN KEY ('Cno') REFERENCES 'course' ('id') /* 外键信息 */
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
这一节中用到了3个表的数据,student表和course表的数据如6.1节中图6-2和6-4所示,但是sc表的内容变为图6-9所示的数据,其中Sno和Cno存储的分别是student表和course表中对应的主键值。
图6-9 本节中所用的表sc中的内容
Student对象和Course对象之间是多对多的关系。此处使用的是单向关联,仅仅建立从Student到Course的单向关联。如图6-10所示,仅有Student到Course的单向关联。
图6-10 Student到Course类的单向关联
为了建立Student到Course的单向关联关系,在Student.java中新加一个属性course。course属性是Set型的,可以在这个属性中加入多个Course对象,建立起关联关系。下面是加入course属性后的源代码,粗体部分为加入的代码。
package hibernate.ch06;
import java.util.HashSet;
import java.util.Set;
public class Student implements java.io.Serializable {
private Integer id;// ID
private Integer sno; // 学号
private String sname; // 姓名
private String ssex; // 性别
private String sdept; // 系部
private Integer sage; // 年龄
private String saddress; // 住址
private Set course = new HashSet(); // 所选课程
public Student() {
}
// 此处省略其他的构造方法
// 此处省略getter/setter访问器
// course属性的get访问器
public Set getCourse() {
return course;
}
// course属性的set访问器
public void setCourse(Set course) {
this.course = course;
}
}
持久化类Course.java和SC.java无需修改。
在Student.hbm.xml映射配置文件中,加入Student到Course的映射信息。关于如何映射关联关系,将在第8章讲解,读者可暂时按照下面的设置,具体含义等阅读完第八章便可理解。具体代码如下。
<set name="course" table="sc" lazy="false" cascade="save-update">
<key column="sno" />
<many-to-many class="hibernate.ch06.Course" column="cno" />
</set>
说明如下。
<set>元素是和<class>元素平行的元素。<set>元素表明将要映射的字段对应着一个集合。<set>元素包含多个属性,其中:name属性用于设置映射的持久化类的属性名称,在本例中为Student表的course属性;table属性表示多对多关联关系的中间表名称,此处为sc表;cascade表示当保存或者更新Student实例时,是否保存或更新Course对象。
<set>元素的子元素<key column="sno" />设定与student表关联的中间表sc的外键sno。
<set>元素的子元素<many-to-many>用于设定多对多关系。在该元素中,class属性用于设定多对多关系中,与Student类关联的类Course类;column属性设定中间表与course表连接的外键cno。
完整的配置文件Student.hbm.xml如下所示。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="hibernate.ch06.Student" table="student" catalog="joblog">
<id name="id" type="integer">
<column name="id" />
<generator class="identity"></generator>
</id>
<!--映射学号-->
<property name="sno" type="integer">
<column name="Sno" not-null="true" />
</property>
<!--映射姓名-->
<property name="sname" type="string">
<column name="Sname" length="45" />
</property>
<!--映射系部-->
<property name="sdept" type="string">
<column name="Sdept" length="10" />
</property>
<!--映射年龄-->
<property name="sage" type="integer">
<column name="Sage" />
</property>
<!--映射性别-->
<property name="ssex" type="string">
<column name="Ssex" length="2" />
</property>
<!--映射住址-->
<property name="saddress" type="string">
<column name="Saddress" length="45" />
</property>
<!--联接-->
<set name="course" table="sc" lazy="false" cascade="save-update">
<key column="sno" />
<many-to-many class="hibernate.ch06.Course" column="cno" /><!--多对多-->
</set>
</class>
</hibernate-mapping>
左外连接(Left Outer Join)查询出左表对应的复合条件的所有记录,如查询李晓梅同学的选课信息。下面是类HQLLeftOuterJoinQuery的源代码。
package hibernate.ch06;
import hibernate.Hsf;
import java.util.Iterator;
import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
public class HQLLeftOuterJoinQuery {
public static void main(String[] args) {
Session session = Hsf.currentSession();
// HQL查询语句
String hql = "from Student s left join s.course c where s.sname='李晓梅'";
Query query = session.createQuery(hql); // 创建查询
List list = query.list(); // 执行查询
Iterator it = list.iterator();
while (it.hasNext()) {
Object[] obj = (Object[]) it.next();
Student stu = (Student) obj[0];
Course course = (Course) obj[1];
System.out.println("*********学生信息及其选课信息******************");
if (course != null) {
System.out.println(stu.getSno() + "\t" + stu.getSname() + "\t" + "课程:"
+ course.getCname());
} else {
System.out.println(stu.getSno() + "\t" + stu.getSname() + "\t");
}
}
}
}
如果只用单表查询,只能从student表中查询出李晓梅的个人信息,而无法知道她的选课信息,因为选课信息存储在中间表sc中。HQL语句from Student s left join s.course c where s.sname='李晓梅'检索出了李晓梅的选课信息。
在HQL中使用left outer join关键字进行左外连接,outer关键字可以省略。
s.course是Student对象中的一个属性,用来存储Student对象的选课信息。在执行查询时,将根据Student.hbm.xml中的配置生成SQL语句,并检索信息。
查询的结果返回一个Object[]数组,数组的第0个元素是Student对象,第1个元素是与Object[0]中对应的学生所选课的Course对象。
HQLLeftOuterJoinQuery类在执行过程中生成的左外连接的SQL语句如下。
Hibernate:
select
student0_.id as id1_0_,
course2_.id as id4_1_,
student0_.Sno as Sno1_0_,
student0_.Sname as Sname1_0_,
student0_.Sdept as Sdept1_0_,
student0_.Sage as Sage1_0_,
student0_.Ssex as Ssex1_0_,
student0_.Saddress as Saddress1_0_,
course2_.Cno as Cno4_1_,
course2_.Cname as Cname4_1_,
course2_.Ccredit as Ccredit4_1_
from
joblog.student student0_
left outer join
sc course1_
on student0_.id=course1_.sno
left outer join
joblog.course course2_
on course1_.cno=course2_.id
where
student0_.Sname='李晓梅'
Hibernate:
select
course0_.sno as sno1_,
course0_.cno as cno1_,
course1_.id as id4_0_,
course1_.Cno as Cno4_0_,
course1_.Cname as Cname4_0_,
course1_.Ccredit as Ccredit4_0_
from
sc course0_
left outer join
joblog.course course1_
on course0_.cno=course1_.id
where
course0_.sno=?
程序的查询结果如下。
*********学生信息及其选课信息******************
20040001 李晓梅 课程:数据库
*********学生信息及其选课信息******************
20040001 李晓梅 课程:操作系统
使用如下语句将只返回Student对象。
select s from Student s left join s.course c where s.sname='李晓梅'
如下是只返回Student对象的部分代码。
Session session = Hsf.currentSession();
// HQL查询语句
String hql = "select s from Student s left join s.course c where s.sname='李晓梅'";
Query query = session.createQuery(hql); // 创建查询
List list = query.list(); // 执行查询
Iterator it = list.iterator();
while (it.hasNext()) {
Student stu = (Student) it.next();
System.out.println("*********学生信息及其选课信息******************");
System.out.println(stu.getSno() + "\t" + stu.getSname() + "\t");
}
左外抓取连接指定在Hibernate检索数据时,采用抓取的方式,直接将数据加载到与Student对象关联的course属性中。下面是左外抓取连接的程序。
// HQL查询语句
String hql = "select s from Student s left join fetch s.course c where s.sname='李晓梅'";
Query query = session.createQuery(hql); // 创建查询
List list = query.list(); // 执行查询
Iterator it = list.iterator();
while (it.hasNext()) {
Student stu = (Student) it.next();
System.out.println("*********学生信息及其选课信息******************");
System.out.println(stu.getSno() + "\t" + stu.getSname() + "\t");
}
左外抓取连接使用left join fetch关键字。
与左外连接不同的是:左外抓取连接query.list()返回的集合中存放Student对象的引用,与之相关联的选课信息存放在course属性中。
HQL中使用关键字right outer join右外连接,outer关键字可以省略。右外连接与左外连接类似,不再赘述。
内连接(Inner Join)是指两个表中指定的关键字相等的值才会出现在结果集中的一种查询方式。HQL中使用关键字inner join进行内连接,下面是使用内连接的程序。
Session session = Hsf.currentSession();//创建Session
String hql = "from Student s inner join s.course c"; // HQL查询语句
Query query = session.createQuery(hql);// 创建查询
List list = query.list(); // 执行查询
Iterator it = list.iterator();
while (it.hasNext()) {
Object[] obj = (Object[]) it.next();
Student stu = (Student) obj[0];
Course course = (Course) obj[1];
System.out.println("*********学生信息及其选课信息******************");
System.out.println(stu.getSno() + "\t" + stu.getSname() + "\t" + "课程:"
+ course.getCname());
}
HQL中使用inner join进行内连接,内连接只关联并检索那些选了课的学生信息及其选课信息,没有选课的学生不在检索结果中。
可以使用select s from Student s inner join s.course c只返回Student对象。
抓取内连接与内连接不同之处在于其对象的内存状态不一样。HQL中使用inner join fetch进行抓取内连接,如下程序所示。
Session session = Hsf.currentSession();// 创建Session
String hql = "select s from Student s inner join fetch s.course c"; // HQL语句
Query query = session.createQuery(hql);// 创建查询
List list = query.list(); // 执行查询
Iterator it = list.iterator();
while (it.hasNext()) {
Student stu = (Student) it.next();
System.out.println("*********学生信息及其选课信息******************");
System.out.println(stu.getSno() + "\t" + stu.getSname() + "\t");
}
内抓取连接使用inner join fech关键字。
它与内连接的区别是返回检索的list中存放的是Student对象的引用,与之相关联的选课信息存放在course属性中。