Hibernate3.x教程(二) Hibernate关联映射
2013-03-03 17:09
253 查看
对于关系型数据库,表间的关联关系是最用的,在Hibernate中对关联关系的映射也是最常见的,同时也是最难配置的。不仅因为关联关系的复杂性,也关乎关联关系带来的性能问题。
下面以学校中班级(Grade)、学生(Student)、课程(Course)这几个实体关系为例,介绍Hibernate的各种关联关系配置,也会尝试寻找最优配置。
班级表(Grade),只有自增主键id和一个基本属性name;
学生表(Student),包含主键id、姓名name、年龄age以及一个关系外键grade_id;
课程表(Course),也是只有主键id和课程名称name。
一、多对一关联关系(many-to-one)
在以上三个实体之间,学生和班级之间是多对一的关联关系,如果在学生实体中配置该生所在班级,我们需要在学生实体和映射文件中配置多对一的关联关系。
Student实体对应映射文件:
Student实体类:
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student
student0_ where student0_.id=?
Hibernate: select grade0_.id as id1_0_, grade0_.name as name1_0_ from school.grade grade0_ where grade0_.id=?
二、一对多关联关系(one-to-many)
班级与学生是一对多关联关系,获取班级信息时,若读取该班级下所有学生信息,那么就需要在班级实体和映射文件中配置一对多的关联关系。
Grade实体对应映射文件:
Grade实体类:
查询班级表,通过班级实体获取该班级的学生列表时,生成SQL如下:
Hibernate: select grade0_.id as id1_0_, grade0_.name as name1_0_ from school.grade grade0_ where grade0_.id=?
Hibernate: select students0_.grade_id as grade4_1_, students0_.id as id1_, students0_.id as id0_0_, students0_.name as name0_0_, students0_.age as age0_0_, students0_.grade_id as grade4_0_0_
from school.student students0_ where students0_.grade_id=?
三、双向关联的级联更新
在两个关联实体中,同时配置了两种关联关系,这是这两个实体间就形成了双向关联映射,查询数据时互不影响,但是插入、更新、删除数据就会产生级联更新的问题。
这个问题其实是两个实体间关联关系的维护和级联操作的问题。关联关系维护其实就是外键字段的维护,而级联操作是指一方更新,另一方随之更新。
首先,我们来看级联关系的维护,分别看下面两段代码(id为1的学生默认班级id为1):
代码1执行SQL如下:
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
Hibernate: select grade0_.id as id1_0_, grade0_.name as name1_0_ from school.grade grade0_ where grade0_.id=?
Hibernate: update school.student set name=?, age=?, grade_id=? where id=?
代码2执行SQL如下:
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
Hibernate: select grade0_.id as id1_0_, grade0_.name as name1_0_ from school.grade grade0_ where grade0_.id=?
Hibernate: select students0_.grade_id as grade4_1_, students0_.id as id1_, students0_.id as id0_0_, students0_.name as name0_0_, students0_.age as age0_0_, students0_.grade_id as
grade4_0_0_ from school.student students0_ where students0_.grade_id=?
Hibernate: update school.student set grade_id=? where id=?
以上两段代码都可以更改班级、学生的关联关系,即学生表的外键字段。但是,两个实体的关系是存在学生表的,从上面的执行SQL可以看出通过班级更新关联需要额外的查询。所以,通常情况下,关联关系只交由“多”的一方进行维护,那么就需要在“一”的一方的配置文件中,加入invers="true"的设置。
修改Grade.hbm.xml映射文件配置如下,student表中配置不变:
invers属性是用于指定关联关系由哪一方维护,默认为false,即默认自己这一方维护关联关系(所以默认情况下两方都可以维护关联关系),设置为true,即表示将关联关系交由对方去维护。(注:many-to-one中不存在invers属性,即便配置了invers="true",它也是无效的)
我们再执行“代码2”会发现不会再对它们的关联关系进行维护,就不会执行update那句SQL语句。
然后,来看如下代码:
以上代码,同时创建了一个班级和一个学生对象,并将两个实体在双方都进行了关联,最后保存班级实体。查看执行SQL:
Hibernate: insert into school.grade (name) values (?)
SQL执行只对班级信息进行了保存,学生信息并没有保存,他们的关联关系就更不用说。
那么,如果要在保存班级信息时同时保存学生信息,这就涉及了级联操作。级联操作是通过cascade属性进行配置的,cascade默认属性none,即不级联更新,因此并没有同时保存学生信息。所以,在执行这段代码时,如果班级实体的映射未设置invers="true",它就会试图去保存关联关系,而由于学生信息没有保存,执行时就会出现异常。
cascade属性用于指定在对当前对象进行保存、更新、删除操作时,如何操作与之关联的其他对象。它的可选值有:
none:不进行级联操作,默认值
save-update:当进行save()、update()、saveOrUpdate()操作时,级联保存更新与之关联的对象(新建对象或瞬时状态对象)
delete:当进行delete()操作时,级联删除与之关联的所有对象
all:对任何操作都进行级联操作。包含save-update、delete的行为。
修改Grade.hbm.xml映射文件配置,student映射配置不变:
我们再执行上面的代码,显示执行SQL:
Hibernate: insert into school.grade (name) values (?)
Hibernate: insert into school.student (name, age, grade_id) values (?, ?, ?)
这时,student对象保存进了数据库中。
班级对象的映射配置了invers="true",所以:
student.setGrade(grade);//这行代码的作用是保存关联关系,注释掉这行代码,保存后外键字段会为null
grade.getStudents().add(student);//这行代码的作用是建立与grade关联的对象,注释掉这行代码,不会进行级联操作(上面那行代码只表示与student关联的对象,不能表示与grade关联的对象)
invers和cascade是有着截然不同的含义和作用的,没有什么互相影响的关系,只是在维护关系时希望进行级联操作,他们会同时出现罢了。假如使用下面代码进行同样的操作,它同样能够保存成功,并且不需要设置cascade属性的。
四、关联关系的更新操作最佳实践
对于单向关联的多对一关系,保存学生信息时,因为是使用一个班级对象映射的关联关系,所以有时我们需要首先查询出所在班级,然后进行保存:
以上代码执行对应的SQL为:
Hibernate: select grade0_.id as id2_0_, grade0_.name as name2_0_ from school.grade grade0_ where grade0_.id=?
Hibernate: insert into school.student (name, age, grade_id) values (?, ?, ?)
从代码中可以看出,其实大多数情况下我们事先已经知道了班级ID,并且实际上我们也只需要id就足够了,额外的班级信息查询是不需要的,这种情况可以更改代码:
这样,执行SQL就不会再查询一次班级信息。
现在,还有一个问题,既然只要id就可以了,班级的实例化也是一个多余的操作。那么,我们就希望查询的时候可以获取班级实体对象,插入更新数据的时候只要班级ID就好。在配置文件中,默认是无法对同一个字段映射两种属性的,但是对多对一映射增加这样的属性insert="false"
update="false",达到这一目的:
这样在实体对象中,就可以将grade_id分别映射到gradeId和grade对象中了。
Strudent实体可以这样设计:
在查询时,可以像之前那样直接获取班级的实体对象grade。在插入更新时,只需要设置gradeId属性就可以:
五、多对多关联关系(many-to-many)
最后,简单介绍一下多对多关系的配置,数据库中是无法配置两个表多对多的关系的,所以需要一张中间表来处理多对多的关联关系。对于学生表和课程表,他们是多对多的关联关系,通常是会有一个成绩表作为关联表,将他们的关系转化为学生表与成绩表的一对多关联关系,课程表与成绩表的一对多关联关系。
建立score表,字段包括主键id、student_id、course_id、分数score。
修改学生表的映射配置,增加多对多映射:
在学生类中加入属性:
修改课程表的映射配置,增加多对多映射:
课程类中加入属性:
在程序中,我们就可以通过student.getCourses()获取该生所学的课程,同样通过course.getStudents()就可以得到学习该课程的所有学生。
在前面已经提到,使用一对多并不是一个好的方法,多对多适用的场景是更少的。对于多对多的配置,了解即可。在这个例子中,可以映射score表,在score映射文件中配置学生和课程的多对一关联关系,需要查找课程或成绩时,直接查询score就可以了。
Hibernate关联映射的代码示例:http://download.csdn.net/detail/boyazuo/5106028
下面以学校中班级(Grade)、学生(Student)、课程(Course)这几个实体关系为例,介绍Hibernate的各种关联关系配置,也会尝试寻找最优配置。
班级表(Grade),只有自增主键id和一个基本属性name;
学生表(Student),包含主键id、姓名name、年龄age以及一个关系外键grade_id;
课程表(Course),也是只有主键id和课程名称name。
一、多对一关联关系(many-to-one)
在以上三个实体之间,学生和班级之间是多对一的关联关系,如果在学生实体中配置该生所在班级,我们需要在学生实体和映射文件中配置多对一的关联关系。
Student实体对应映射文件:
<hibernate-mapping> <class name="com.boya.hibernate.entity.Student" table="student" catalog="school"> <id name="id" type="java.lang.Integer"> <column name="id" /> <generator class="native" /> </id> <property name="name" type="java.lang.String"> <column name="name" length="50" /> </property> <property name="age" type="java.lang.Integer"> <column name="age" /> </property> <many-to-one name="grade" column="grade_id" class="com.boya.hibernate.entity.Grade"/> </class> </hibernate-mapping>
Student实体类:
public class Student { private Integer id; private String name; private Integer age; private Grade grade; //getter、setter方法 }查询学生表,通过学生实体获取班级信息时,生成SQL如下:
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student
student0_ where student0_.id=?
Hibernate: select grade0_.id as id1_0_, grade0_.name as name1_0_ from school.grade grade0_ where grade0_.id=?
二、一对多关联关系(one-to-many)
班级与学生是一对多关联关系,获取班级信息时,若读取该班级下所有学生信息,那么就需要在班级实体和映射文件中配置一对多的关联关系。
Grade实体对应映射文件:
<hibernate-mapping> <class name="com.boya.hibernate.entity.Grade" table="grade" catalog="school"> <id name="id" type="java.lang.Integer"> <column name="id" /> <generator class="native" /> </id> <property name="name" type="java.lang.String"> <column name="name" length="50" /> </property> <set name="students" inverse="true" cascade="all"> <key column="grade_id" /> <one-to-many class="com.boya.hibernate.entity.Student"/> </set> </class> </hibernate-mapping>
Grade实体类:
public class Grade { private Integer id; private String name; private Set<Student> students = new HashSet<Student>(); //getter、setter方法 }
查询班级表,通过班级实体获取该班级的学生列表时,生成SQL如下:
Hibernate: select grade0_.id as id1_0_, grade0_.name as name1_0_ from school.grade grade0_ where grade0_.id=?
Hibernate: select students0_.grade_id as grade4_1_, students0_.id as id1_, students0_.id as id0_0_, students0_.name as name0_0_, students0_.age as age0_0_, students0_.grade_id as grade4_0_0_
from school.student students0_ where students0_.grade_id=?
三、双向关联的级联更新
在两个关联实体中,同时配置了两种关联关系,这是这两个实体间就形成了双向关联映射,查询数据时互不影响,但是插入、更新、删除数据就会产生级联更新的问题。
这个问题其实是两个实体间关联关系的维护和级联操作的问题。关联关系维护其实就是外键字段的维护,而级联操作是指一方更新,另一方随之更新。
首先,我们来看级联关系的维护,分别看下面两段代码(id为1的学生默认班级id为1):
//代码1:通过学生实体保存关联关系 Student student = studentDao.get(1); Grade grade = gradeDao.get(2); student.setGrade(grade); studentDao.save(student);
代码1执行SQL如下:
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
Hibernate: select grade0_.id as id1_0_, grade0_.name as name1_0_ from school.grade grade0_ where grade0_.id=?
Hibernate: update school.student set name=?, age=?, grade_id=? where id=?
//代码2:通过班级实体保存关联关系 Student student = studentDao.get(1); Grade grade = gradeDao.get(1); grade.getStudents().add(student); gradeDao.save(grade);
代码2执行SQL如下:
Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_, student0_.grade_id as grade4_0_0_ from school.student student0_ where student0_.id=?
Hibernate: select grade0_.id as id1_0_, grade0_.name as name1_0_ from school.grade grade0_ where grade0_.id=?
Hibernate: select students0_.grade_id as grade4_1_, students0_.id as id1_, students0_.id as id0_0_, students0_.name as name0_0_, students0_.age as age0_0_, students0_.grade_id as
grade4_0_0_ from school.student students0_ where students0_.grade_id=?
Hibernate: update school.student set grade_id=? where id=?
以上两段代码都可以更改班级、学生的关联关系,即学生表的外键字段。但是,两个实体的关系是存在学生表的,从上面的执行SQL可以看出通过班级更新关联需要额外的查询。所以,通常情况下,关联关系只交由“多”的一方进行维护,那么就需要在“一”的一方的配置文件中,加入invers="true"的设置。
修改Grade.hbm.xml映射文件配置如下,student表中配置不变:
<set name="students" inverse="true"> <key column="grade_id" /> <one-to-many class="com.boya.hibernate.entity.Student"/> </set>
invers属性是用于指定关联关系由哪一方维护,默认为false,即默认自己这一方维护关联关系(所以默认情况下两方都可以维护关联关系),设置为true,即表示将关联关系交由对方去维护。(注:many-to-one中不存在invers属性,即便配置了invers="true",它也是无效的)
我们再执行“代码2”会发现不会再对它们的关联关系进行维护,就不会执行update那句SQL语句。
然后,来看如下代码:
Grade grade = new Grade(); Student student = new Student(); grade.setName("83班"); student.setName("张三"); student.setGrade(grade); grade.getStudents().add(student); gradeDao.save(grade);
以上代码,同时创建了一个班级和一个学生对象,并将两个实体在双方都进行了关联,最后保存班级实体。查看执行SQL:
Hibernate: insert into school.grade (name) values (?)
SQL执行只对班级信息进行了保存,学生信息并没有保存,他们的关联关系就更不用说。
那么,如果要在保存班级信息时同时保存学生信息,这就涉及了级联操作。级联操作是通过cascade属性进行配置的,cascade默认属性none,即不级联更新,因此并没有同时保存学生信息。所以,在执行这段代码时,如果班级实体的映射未设置invers="true",它就会试图去保存关联关系,而由于学生信息没有保存,执行时就会出现异常。
cascade属性用于指定在对当前对象进行保存、更新、删除操作时,如何操作与之关联的其他对象。它的可选值有:
none:不进行级联操作,默认值
save-update:当进行save()、update()、saveOrUpdate()操作时,级联保存更新与之关联的对象(新建对象或瞬时状态对象)
delete:当进行delete()操作时,级联删除与之关联的所有对象
all:对任何操作都进行级联操作。包含save-update、delete的行为。
修改Grade.hbm.xml映射文件配置,student映射配置不变:
<set name="students" inverse="true" cascade="all"> <key column="grade_id" /> <one-to-many class="com.boya.hibernate.entity.Student"/> </set>
我们再执行上面的代码,显示执行SQL:
Hibernate: insert into school.grade (name) values (?)
Hibernate: insert into school.student (name, age, grade_id) values (?, ?, ?)
这时,student对象保存进了数据库中。
班级对象的映射配置了invers="true",所以:
student.setGrade(grade);//这行代码的作用是保存关联关系,注释掉这行代码,保存后外键字段会为null
grade.getStudents().add(student);//这行代码的作用是建立与grade关联的对象,注释掉这行代码,不会进行级联操作(上面那行代码只表示与student关联的对象,不能表示与grade关联的对象)
invers和cascade是有着截然不同的含义和作用的,没有什么互相影响的关系,只是在维护关系时希望进行级联操作,他们会同时出现罢了。假如使用下面代码进行同样的操作,它同样能够保存成功,并且不需要设置cascade属性的。
Grade grade = new Grade(); Student student = new Student(); grade.setName("83班"); student.setName("张三"); student.setGrade(grade); //grade.getStudents().add(student); gradeDao.save(grade); studentDao.save(student);
四、关联关系的更新操作最佳实践
对于单向关联的多对一关系,保存学生信息时,因为是使用一个班级对象映射的关联关系,所以有时我们需要首先查询出所在班级,然后进行保存:
Student student = new Student(); student.setName("张三"); Grade grade = gradeDao.get(2); student.setGrade(grade); studentDao.save(student);
以上代码执行对应的SQL为:
Hibernate: select grade0_.id as id2_0_, grade0_.name as name2_0_ from school.grade grade0_ where grade0_.id=?
Hibernate: insert into school.student (name, age, grade_id) values (?, ?, ?)
从代码中可以看出,其实大多数情况下我们事先已经知道了班级ID,并且实际上我们也只需要id就足够了,额外的班级信息查询是不需要的,这种情况可以更改代码:
//Grade grade = gradeDao.get(2); //student.setGrade(grade); student.setGrade(new Grade(2));
这样,执行SQL就不会再查询一次班级信息。
现在,还有一个问题,既然只要id就可以了,班级的实例化也是一个多余的操作。那么,我们就希望查询的时候可以获取班级实体对象,插入更新数据的时候只要班级ID就好。在配置文件中,默认是无法对同一个字段映射两种属性的,但是对多对一映射增加这样的属性insert="false"
update="false",达到这一目的:
<property name="gradeId" type="java.lang.Integer"> <column name="grade_id"/> </property> <many-to-one name="grade" column="grade_id" class="com.boya.hibernate.entity.Grade" insert="false" update="false"/>
这样在实体对象中,就可以将grade_id分别映射到gradeId和grade对象中了。
Strudent实体可以这样设计:
private Integer gradeId; private Grade grade;
在查询时,可以像之前那样直接获取班级的实体对象grade。在插入更新时,只需要设置gradeId属性就可以:
Student student = new Student(); student.setName("李四"); student.setGradeId(2); studentDao.save(student); student.setGradeId(1); studentDao.save(student);在实际应用中,除非有一些特殊的查询需要,我们很少使用一对多的关联映射,我们更少使用到级联操作。这些操作对于性能以及对数据的控制都有一定的影响。更多时候,我们是仅使用多对一的关联映射,需要的话,可以通过程序代码实现一对多的查询和级联操作,就像上面演示的例子那样。
五、多对多关联关系(many-to-many)
最后,简单介绍一下多对多关系的配置,数据库中是无法配置两个表多对多的关系的,所以需要一张中间表来处理多对多的关联关系。对于学生表和课程表,他们是多对多的关联关系,通常是会有一个成绩表作为关联表,将他们的关系转化为学生表与成绩表的一对多关联关系,课程表与成绩表的一对多关联关系。
建立score表,字段包括主键id、student_id、course_id、分数score。
修改学生表的映射配置,增加多对多映射:
<set name="courses" table="score"> <key column="student_id"></key> <many-to-many class="com.boya.hibernate.entity.Course" column="course_id" /> </set>
在学生类中加入属性:
private Set<Course> courses;
修改课程表的映射配置,增加多对多映射:
<set name="students" table="score"> <key column="course_id"></key> <many-to-many class="com.boya.hibernate.entity.Student" column="student_id" /> </set>
课程类中加入属性:
private Set<Student> students;
在程序中,我们就可以通过student.getCourses()获取该生所学的课程,同样通过course.getStudents()就可以得到学习该课程的所有学生。
在前面已经提到,使用一对多并不是一个好的方法,多对多适用的场景是更少的。对于多对多的配置,了解即可。在这个例子中,可以映射score表,在score映射文件中配置学生和课程的多对一关联关系,需要查找课程或成绩时,直接查询score就可以了。
Hibernate关联映射的代码示例:http://download.csdn.net/detail/boyazuo/5106028
相关文章推荐
- Hibernate教程04_关系映射之一对一单向外键关联
- Hibernate学习笔记总结(二)——Hibernate 3.x 关联映射(重要)Lazy策略 悲观锁与乐观锁
- Hibernate教程05_关系映射之一对一双向外键关联
- Hibernate教程07_关系映射之一对一双向主键关联
- ORM框架Hibernate多对多关联映射的HQL中的in条件查询问题
- Hibernate之对象关系映射01一对一单向关联
- Hibernate一对一关联映射配置
- hibernate之关于关联映射的综合应用 推荐
- Hibernate从入门到精通(九)一对多双向关联映射
- Hibernate映射详解--一对一主键关联映射
- hibernatea实现一对多关联映射关系
- hibernate中集合映射关联映射小记
- hibernate中的关联映射图解 ---- 一对多
- 【Hibernate步步为营】--双向关联一对一映射详解(二)
- hibernate双向一对多关联映射XML与注解版
- Hibernate 关联映射
- Hibernate关联映射之多对一单向关联映射
- Hibernate关联映射之多对多单/双向关联映射
- hibernate---->多对一关联映射
- [置顶] Hibernate从入门到精通(十一)多对多双向关联映射