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

在数据库中存在四种关联关系映射,分别是一对一(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.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属性可以方便我们编码,但是可能会带来性能上的一些变动,所以需要谨慎使用。














阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011024652/article/details/52651831
个人分类: Hibernate
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭