Hibernate3.x教程(二) Hibernate关联映射

对于关系型数据库,表间的关联关系是最用的,在Hibernate中对关联关系的映射也是最常见的,同时也是最难配置的。不仅因为关联关系的复杂性,也关乎关联关系带来的性能问题。

下面以学校中班级(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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值