您的位置:首页 > 编程语言 > Java开发

Hibernate学习笔记(三)——Hibernate的关联关系映射

2016-09-24 20:27 561 查看
在数据库中存在四种关联关系映射,分别是一对一(one-to-one)、一对多(one-to-many)、多对一(many-to-one)和多对多(many-to-many),其中日常开发中比较常用的是一对多和多对一的映射,那么下面将分别通过几个实例来介绍一下一对多和多对一的映射。

一、一对多关系映射

什么叫做一对多关系映射呢?举个例子,就好比班级和学生,站在班级的角度来看,一个班级包含多个学生,那么班级和学生的关系就是一对多的关系,在这里我们先介绍单向的一对多关系,下面会介绍双向的一对多和多对一关系。了解了一对多关系的概念之后,一对多的关系在数据库和Java代码中又是如何体现的呢?在数据库中,可以通过添加主外键的关联,表现一对多的关系;而在Java代码里,可以通过在一方持有多方的集合实现,即在“一”的一端中使用<set>元素表示持有“多”的一端的对象,接下来就通过实例来具体分析一下。
首先需要建立数据库表,我们在数据库中要创建两张表,班级表Grade和学生表Student,使用MySQL数据库建表语句如下,其中学生表中使用外键与班级表的主键gid关联。
Grade表:
CREATE TABLE `Grade` (
`gid` int(11) NOT NULL AUTO_INCREMENT,
`gname` varchar(20) NOT NULL,
`gdesc` varchar(45) DEFAULT NULL,
PRIMARY KEY (`gid`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;
Student表:
CREATE TABLE `Student` (
`sid` int(11) NOT NULL AUTO_INCREMENT,
`sname` varchar(20) NOT NULL,
`sex` char(2) NOT NULL,
`gid` int(11) DEFAULT NULL,
PRIMARY KEY (`sid`),
KEY `fk_Student_1_idx` (`gid`),
CONSTRAINT `fk_Student_1` FOREIGN KEY (`gid`) REFERENCES `Grade` (`gid`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;
创建好数据库表之后,就可以进行编码了,要使用Hibernate,就需要配置hibernate.cfg.xml文件,在这里就不用多介绍了,在前两篇文章里有更详细的介绍。然后就可以根据数据库表创建实体类,首先创建学生类Student,里面包含了学生的id、姓名和性别信息。
package com.imooc.one_to_many.vo;

public class Student {

private int sid;

private String sname;

private String sex;

public int getSid() {
return sid;
}

public void setSid(int sid) {
this.sid = sid;
}

public String getSname() {
return sname;
}

public void setSname(String sname) {
this.sname = sname;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public Student(String name, String sex) {
this.sname = name;
this.sex = sex;
}

public Student() {
}

@Override
public String toString() {
return "Student [name=" + sname + ", sex=" + sex + "]";
}

}
然后创建班级类Grade,因为是单向一对多的关联,所以需要在Grade中定义一个学生类的集合,同时为了防止出现空指针异常,需要给学生类的集合进行初始化。
package com.imooc.one_to_many.vo;

import java.util.HashSet;
import java.util.Set;

public class Grade {

private int gid;

private String gname;

private String gdesc;

private Set<Student> students = new HashSet<Student>();

public int getGid() {
return gid;
}

public void setGid(int gid) {
this.gid = gid;
}

public String getGname() {
return gname;
}

public void setGname(String gname) {
this.gname = gname;
}

public String getGdesc() {
return gdesc;
}

public void setGdesc(String gdesc) {
this.gdesc = gdesc;
}

public Set<Student> getStudents() {
return students;
}

public void setStudents(Set<Student> students) {
this.students = students;
}

public Grade(String gname, String gdesc) {
super();
this.gname = gname;
this.gdesc = gdesc;
}

public Grade() {
}

}
定义好实体类之后就可以创建映射文件了,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">
<!-- Generated 2016-9-23 19:37:50 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
<class name="com.imooc.one_to_many.vo.Student" table="Student">
<id name="sid" type="int">
<column name="sid" />
<generator class="native" />
</id>
<property name="sname" type="java.lang.String">
<column name="sname" />
</property>
<property name="sex" type="java.lang.String">
<column name="sex" />
</property>
</class>
</hibernate-mapping>
接下来就需要配置Grade.hbm.xml文件了,在这里要注意的是因为配置了单向的一对多关联映射,所以需要在这个配置文件里添加一个set标签来表示单向一对多关联映射。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2016-9-23 19:37:50 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
<class name="com.imooc.one_to_many.vo.Grade" table="Grade">
<id name="gid" type="int">
<column name="gid" />
<generator class="native" />
</id>
<property name="gname" type="java.lang.String">
<column name="gname" />
</property>
<property name="gdesc" type="java.lang.String">
<column name="gdesc" />
</property>
<!-- 配置一对多的集合属性 table 指对应表的名称-->
<set name="students" table="Student">
<key>
<column name="gid" />
</key>
<one-to-many class="com.imooc.one_to_many.vo.Student" />
</set>
</class>
</hibernate-mapping>
set标签有如下几个常用属性(图片摘自慕课网),在下面会详细介绍inverse属性的用法。在set标签内,还配置了key子标签,表示要关联的外键,one-to-many表示配置一对多的映射关系,其中class属性表示映射的类。



接下来只需要在hibernate.cfg.xml中添加映射就可以了。
<mapping resource="com/imooc/one_to_many/vo/Student.hbm.xml"/>
<mapping resource="com/imooc/one_to_many/vo/Grade.hbm.xml"/>
所有都配置完成后,就可以使用JUnit进行测试了,首先我们测试添加功能,写下面的代码,其中init()方法和destory()方法的作用在前面也介绍过了,就不再赘述了。
package com.imooc.test;

import java.util.Set;

import org.
4000
hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.imooc.one_to_many.vo.Grade;
import com.imooc.one_to_many.vo.Student;

public class OneToManyTest {

private SessionFactory sessionFactory;

private Session session;

private Transaction transaction;

@Before
public void init() {
Configuration cfg = new Configuration().configure();

sessionFactory = cfg.buildSessionFactory();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}

@After
public void destory() {
transaction.commit();
session.close();
sessionFactory.close();
}

@Test
public void testAdd() {
Grade grade = new Grade("五(一)班", "五年级一班");
Student stu1 = new Student("张三", "男");
Student stu2 = new Student("李四", "女");

grade.getStudents().add(stu1);
grade.getStudents().add(stu2);

session.save(stu1);
session.save(stu2);
session.save(grade);
}

}
主要测试添加方法testAdd(),在这个方法里定义一个班级和两个学生,然后将学生添加到班级的学生集合中,最后使用Session.save()方法将数据保存到数据库中,运行程序,查看数据库,会发现数据插入成功了,并且学生信息中的班级id号gid已经自动关联上班级表中的gid了(因为设置id是自增的,数据库中原来存在部分数据,所以id未从1开始)。
班级表:



学生表:



接下来我们分别测试查询、修改和删除方法。
@Test
public void testQuery() {
Grade grade = session.get(Grade.class, 14);

Set<Student> students = grade.getStudents();
for(Student student : students) {
System.out.println(student);
}

}

@Test
public void testUpdate() {
Grade grade = new Grade("五(二)班", "五年级二班");
Student stu = session.get(Student.class, 22);

grade.getStudents().add(stu);
session.save(grade);
}

@Test
public void testDelete() {
Student stu = session.get(Student.class, 23);

session.delete(stu);
}
在查询方法中,我们首先查询处gid为14的班级信息,因为配置了单向的一对多关联映射,所以查询到班级信息后就可以获取到学生信息的集合,然后就可以遍历出学生的信息。更新方法里,我们新建一个班级,然后将现有的学生直接添加到新的班级里,再保存的时候我们会发现学生的班级已经变为新的班级id了。删除方法就不再赘述了,直接使用delete()方法进行删除。

二、多对一关系映射

多对一关系映射和关系数据库中外键的参照关系最匹配,即在己方的表中的一个外键参照另一个表的主键,在Hibernate中多对一的关系映射是通过在多方定义一方的引用来实现的,同时需要在映射文件里使用many-to-one的标签。
下面仍然使用上面的Grade表和Student表来分析以下多对一关系映射,首先修改Grade实体类,删除其中Student的集合,在Student类中定义Grade的引用。
package com.imooc.many_to_one.vo;

import java.util.HashSet;
import java.util.Set;

public class Grade {

private int gid;

private String gname;

private String gdesc;

//private Set<Student> students = new HashSet<Student>();

public int getGid() {
return gid;
}

public void setGid(int gid) {
this.gid = gid;
}

public String getGname() {
return gname;
}

public void setGname(String gname) {
this.gname = gname;
}

public String getGdesc() {
return gdesc;
}

public void setGdesc(String gdesc) {
this.gdesc = gdesc;
}

/*public Set<Student> getStudents() {
return students;
}

public void setStudents(Set<Student> students) {
this.students = students;
}*/

public Grade(String gname, String gdesc) {
super();
this.gname = gname;
this.gdesc = gdesc;
}

public Grade() {
}

@Override
public String toString() {
return "Grade [gid=" + gid + ", gname=" + gname + ", gdesc=" + gdesc
+ "]";
}

}
package com.imooc.many_to_one.vo;

public class Student {

private int sid;

private String sname;

private String sex;

private Grade grade;

public int getSid() {
return sid;
}

public void setSid(int sid) {
this.sid = sid;
}

public String getSname() {
return sname;
}

public void setSname(String sname) {
this.sname = sname;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public Grade getGrade() {
return grade;
}

public void setGrade(Grade grade) {
this.grade = grade;
}

public Student(String name, String sex) {
this.sname = name;
this.sex = sex;
}

public Student() {
}

@Override
public String toString() {
return "Student [name=" + sname + ", sex=" + sex + "]";
}

}
然后定义两个实体类的映射文件,在Grade.hbm.xml映射文件里,我们不再使用set子标签。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2016-9-23 19:37:50 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
<class name="com.imooc.many_to_one.vo.Grade" table="Grade">
<id name="gid" type="int">
<column name="gid" />
<generator class="native" />
</id>
<property name="gname" type="java.lang.String">
<column name="gname" />
</property>
<property name="gdesc" type="java.lang.String">
<column name="gdesc" />
</property>
<!-- <set name="students" table="Student">
<key>
<column name="gid" />
</key>
<one-to-many class="com.imooc.many_to_one.vo.Student" />
</set> -->
</class>
</hibernate-mapping>
而在Student.hbm.xml映射文件里,我们新定义一个many-to-one的子标签,用于表示单向的多对一映射关系,使用column指定外键关联Grade表的主键。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2016-9-23 19:37:50 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
<class name="com.imooc.many_to_one.vo.Student" table="Student">
<id name="sid" type="int">
<column name="sid" />
<generator class="native" />
</id>
<property name="sname" type="java.lang.String">
<column name="sname" />
</property>
<property name="sex" type="java.lang.String">
<column name="sex" />
</property>
<many-to-one name="grade" class="com.imooc.many_to_one.vo.Grade" column="gid"></many-to-one>
</class>
</hibernate-mapping>
这时可以写一个测试方法来验证以下多对一的关联是否成功,在原来的测试类中新增一个方法。
@Test
public void testAdd2() {
Grade grade = new Grade("六(一)班", "六年级一班");
Student stu1 = new Student("张三", "男");
Student stu2 = new Student("李四", "女");

stu1.setGrade(grade);
stu2.setGrade(grade);

session.save(grade);
session.save(stu1);
session.save(stu2);
}
运行程序,我们会发现Grade表中新增了班级信息,而在Student表中新增了学生信息,并且学生信息与班级信息相关联。我们再来测试一下查询方法。
@Test
public void testQuery() {
Student stu = session.get(Student.class, 28);
System.out.println(stu);

Grade grade = stu.getGrade();
System.out.println(grade);
}
从这个例子中我们可以看到,因为配了单向的多对一的关联映射关系,所以查询到学生信息之后,就可以直接获取到班级的信息。

三、双向一对多、多对一关系映射

上面讲的一对多和多对一关系映射都是单向的,也就是说只在一方之中定义另一方,下面我们来看以下双向的关系映射,而要想实现双向的关系映射,利用上面多对一的例子,我们只需要再在Grade类中添加Student类的集合并在映射文件中配置即可。
package com.imooc.many_to_one.vo;

import java.util.HashSet;
import java.util.Set;

public class Grade {

private int gid;

private String gname;

private String gdesc;

private Set<Student> students = new HashSet<Student>();

public int getGid() {
return gid;
}

public void setGid(int gid) {
this.gid = gid;
}

public String getGname() {
return gname;
}

public void setGname(String gname) {
this.gname = gname;
}

public String getGdesc() {
return gdesc;
}

public void setGdesc(String gdesc) {
this.gdesc = gdesc;
}

public Set<Student> getStudents() {
return students;
}

public void setStudents(Set<Student> students) {
this.students = students;
}

public Grade(String gname, String gdesc) {
super();
this.gname = gname;
this.gdesc = gdesc;
}

public Grade() {
}

@Override
public String toString() {
return "Grade [gid=" + gid + ", gname=" + gname + ", gdesc=" + gdesc
+ "]";
}

}
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2016-9-23 19:37:50 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
<class name="com.imooc.many_to_one.vo.Grade" table="Grade">
<id name="gid" type="int">
<column name="gid" />
<generator class="native" />
</id>
<property name="gname" type="java.lang.String">
<column name="gname" />
</property>
<property name="gdesc" type="java.lang.String">
<column name="gdesc" />
</property>
<set name="students" table="Student">
<key>
<column name="gid" />
</key>
<one-to-many class="com.imooc.many_to_one.vo.Student" />
</set>
</class>
</hibernate-mapping>
修改插入的方法,在Grade的Student集合里添加学生信息。
public void testAdd2() {
Grade grade = new Grade("六(一)班", "六年级一班");
Student stu1 = new Student("张三", "男");
Student stu2 = new Student("李四", "女");

grade.getStudents().add(stu1);
grade.getStudents().add(stu2);
stu1.setGrade(grade);
stu2.setGrade(grade);

session.save(grade);
session.save(stu1);
session.save(stu2);
}
运行程序,我们发现数据也插入成功了,同时学生信息也与班级信息关联上了,但是在后台打印执行的SQL语句时我们发现,Hibernate在执行完插入班级信息、学生信息的操作之后又更新了学生的信息,这一操作是冗余的,因为是双向关系映射,在插入学生信息之后就已经将关联的班级信息添加进数据库了,所以再执行更新操作将班级信息与学生信息相关联是多余的,这样操作也是影响了系统的性能,而要想不执行更新操作,就需要配置set标签的inverse属性了。
Hibernate: insert into Grade (gname, gdesc) values (?, ?)
Hibernate: insert into Student (sname, sex, gid) values (?, ?, ?)
Hibernate: insert into Student (sname, sex, gid) values (?, ?, ?)
Hibernate: update Student set gid=? where sid=?
Hibernate: update Student set gid=? where sid=?

四、inverse属性和cascade属性的用法

1、inverse属性

set标签的inverse属性指定了关联关系的控制方向,默认是由one方来维护的,所以在配置了上面的双向关联时,执行完插入操作后又会执行更新操作。在上面的例子中,关联关系由Grade控制,当Hibernate插入了班级信息和学生信息之后,它认为学生信息还没有关联上班级信息,所以就会执行更新学生信息的操作,其实因为在学生信息里也配置了多对一的关联关系,所以学生信息在插入数据库之后就已经关联了班级信息,所以说更新操作是多余的。如果关联关系的控制方向由Student控制,那么就不会出现冗余的情况。
要使关联关系的控制方向由many方维护,就需要配置set的inverse属性,设置为true就可以了,因为inverse默认值是false,这样做还有助于改善性能。根据这个思想,修改上面的Grade.hbm.xml文件,修改inverse属性为true。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2016-9-23 19:37:50 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
<class name="com.imooc.many_to_one.vo.Grade" table="Grade">
<id name="gid" type="int">
<column name="gid" />
<generator class="native" />
</id>
<property name="gname" type="java.lang.String">
<column name="gname" />
</property>
<property name="gdesc" type="java.lang.String">
<column name="gdesc" />
</property>
<!-- 设置inverse属性为true -->
<set name="students" table="Student" inverse="true">
<key>
<column name="gid" />
</key>
<one-to-many class="com.imooc.many_to_one.vo.Student" />
</set>
</class>
</hibernate-mapping>
这时在运行插入数据库的程序,控制台只打印了插入的SQL语句,说明关联关系的控制方向由many方Student来控制了。
Hibernate: insert into Grade (gname, gdesc) values (?, ?)
Hibernate: insert into Student (sname, sex, gid) values (?, ?, ?)
Hibernate: insert into Student (sname, sex, gid) values (?, ?, ?)

2、cascade属性

在上面的插入数据库的例子中,我们发现,要使插入数据库能够成功,我们需要连续使用三次Session.save()方法,虽然配置了关联关系,但是Hibernate不能自动持久化所关联的对象,这样做是很不方便的,那么有什么办法可以使Hibernate自动持久化所关联的对象呢?我们可以使用cascade属性,cascade属性默认值是none,当配置cascade的值不为none时,Hibernate将自动持久化关联的对象。cascade属性的值和作用可以参考下表(表格摘自慕课网)。



例如上面的例子,我们在Grade.hbm.xml中配置set的cascade为save-update,这时在测试插入方法中插入了班级信息之后,不需要再使用插入学生信息,Hibernate就可以自动将关联的学生信息插入数据库表。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2016-9-23 19:37:50 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
<class name="com.imooc.many_to_one.vo.Grade" table="Grade">
<id name="gid" type="int">
<column name="gid" />
<generator class="native" />
</id>
<property name="gname" type="java.lang.String">
<column name="gname" />
</property>
<property name="gdesc" type="java.lang.String">
<column name="gdesc" />
</property>
<!-- 设置inverse属性为true,设置cascade属性为save-update -->
<set name="students" table="Student" inverse="true" cascade="save-update">
<key>
<column name="gid" />
</key>
<one-to-many class="com.imooc.many_to_one.vo.Student" />
</set>
</class>
</hibernate-mapping>
public void testAdd2() {
Grade grade = new Grade("六(一)班", "六年级一班");
Student stu1 = new Student("张三", "男");
Student stu2 = new Student("李四", "女");

grade.getStudents().add(stu1);
grade.getStudents().add(stu2);
stu1.setGrade(grade);
stu2.setGrade(grade);

session.save(grade);
}
这里需要注意一下,虽然使用cascade属性可以方便我们编码,但是可能会带来性能上的一些变动,所以需要谨慎使用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息