Hibernate学习笔记(三)——Hibernate的关联关系映射
2016-09-24 20:27
561 查看
在数据库中存在四种关联关系映射,分别是一对一(one-to-one)、一对多(one-to-many)、多对一(many-to-one)和多对多(many-to-many),其中日常开发中比较常用的是一对多和多对一的映射,那么下面将分别通过几个实例来介绍一下一对多和多对一的映射。
首先需要建立数据库表,我们在数据库中要创建两张表,班级表Grade和学生表Student,使用MySQL数据库建表语句如下,其中学生表中使用外键与班级表的主键gid关联。
Grade表:
接下来只需要在hibernate.cfg.xml中添加映射就可以了。
班级表:
学生表:
接下来我们分别测试查询、修改和删除方法。
下面仍然使用上面的Grade表和Student表来分析以下多对一关系映射,首先修改Grade实体类,删除其中Student的集合,在Student类中定义Grade的引用。
要使关联关系的控制方向由many方维护,就需要配置set的inverse属性,设置为true就可以了,因为inverse默认值是false,这样做还有助于改善性能。根据这个思想,修改上面的Grade.hbm.xml文件,修改inverse属性为true。
例如上面的例子,我们在Grade.hbm.xml中配置set的cascade为save-update,这时在测试插入方法中插入了班级信息之后,不需要再使用插入学生信息,Hibernate就可以自动将关联的学生信息插入数据库表。
一、一对多关系映射
什么叫做一对多关系映射呢?举个例子,就好比班级和学生,站在班级的角度来看,一个班级包含多个学生,那么班级和学生的关系就是一对多的关系,在这里我们先介绍单向的一对多关系,下面会介绍双向的一对多和多对一关系。了解了一对多关系的概念之后,一对多的关系在数据库和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属性可以方便我们编码,但是可能会带来性能上的一些变动,所以需要谨慎使用。
相关文章推荐
- hibernate中关联关系的映射
- hibernate学习笔记(一)属性映射和关联关系映射
- Hibernate关联关系映射-----单向一对一映射配置
- hibernate映射总结详解: 按外键映射一对一关联关系
- Hibernate关联关系配置-----基于连接表的双向一对多/多对一映射配置
- 攻城狮在路上(壹) Hibernate(十一)--- 映射实体关联关系
- Hibernate的关联关系映射
- Hibernate学习笔记(2)Hibernate关系映射整理
- 【Hibernate】--关联关系映射:继承映射
- 总结:Hibernate关联关系映射——七种映射的实现(更新中)
- hibernate中映射文件中的关联关系——多对一/一对多
- hibernate关联关系中的集合映射的比较
- Hibernate之关联关系映射(一对多和多对一映射,多对多映射)
- Hibernate学习之关联关系映射
- Hibernate学习笔记----基于外键或主键映射的1-1关联关系
- Hibernate注解方式一对多自关联关系映射
- 攻城狮在路上(壹) Hibernate(五)--- 映射一对多关联关系
- Hibernate关联关系映射实例速查
- Hibernate关联关系映射-----单向一对多配置
- hibernate映射总结详解: 按主键映射一对一关联关系