Hibernate初探之一对多映射

常见的关联对应关系

  • OneToMany
  • ManyToOne
  • OneToOne
  • ManyToMany

关联关系是需要区分方向的,比如OneToMany,ManyToOne实际上是相等的。只是维护方不同而已

单向一对多关联

如,一个班级有多个学生。 这就是一种一对多的关系。如何实现呢?

在数据库中,可以通过添加主外键的关联,表现一对多的关系。

在java中,通过在一方持有多方的集合实现,即在“一”的一端中使用<set>元素表示持有“多”的一端的对象。

代码实现

建立数据库和Java Bean

数据库的建立这里就省略了,可以根据Java Bean来建立。
接下来建立两个Java Bean文件

/**
 * 一对多的映射 学生是单一一方
 */
public class Student implements Serializable {

    private int sid;
    private String sname;
    private String sex;
    private Grade grade;
    
    // 省略setter/getter
}
/**
 * 一对多映射 教室是多的一方
 */
public class Grade implements Serializable {
    private int gid;
    private String gname;
    private String gdesc;
    // 在一方定义一个多方集合
    private Set<Student> students = new HashSet<>();
    
    // 省略setter/getter
}

映射文件

首先需要在hibernate.cfg.xml文件中做相应的数据库配置以及文件映射,这里的代码就省略了。可以参考上一篇《Hibernate初探之单表映射》

接下来重点讲一下两个Java Bean所对应的映射文件

<!-- 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 2018-1-5 10:34:28 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping>
    <class name="com.whyalwaysmea.hibernate.one2many.Student" table="student">
        <id name="sid" type="int">
            <column name="SID" />
            <generator class="increment" />
        </id>
        <property name="sname" type="java.lang.String">
            <column name="SNAME" length="20" not-null="true"/>
        </property>
        <property name="sex" type="java.lang.String">
            <column name="sex" />
        </property>
    </class>
</hibernate-mapping>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC 
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="com.whyalwaysmea.hibernate.one2many.Grade" table="grade">
        <id name="gid" column="gid" type="java.lang.Integer">
            <generator class="increment"></generator>
        </id>
        <property name="gname" type="java.lang.String">
            <column name="gname" length="20" not-null="true"></column>
        </property>
        <property name="gdesc" type="java.lang.String">
            <column name="gdesc"></column>
        </property>
        <set name="students" table="student">
            <!-- 指定关联的外键列 -->
            <key column="gid"></key>
            <one-to-many class="com.whyalwaysmea.hibernate.one2many.Student"/>
        </set>
    </class>
</hibernate-mapping>

CRUD

public void saveOne2Many() {
    Grade grade = new Grade("Java大神班", "每个人都是厉害的角儿");
    Student student = new Student("Jack", "男");
    Student student2 = new Student("Rose", "女");
    grade.getStudents().add(student);
    grade.getStudents().add(student2);
    
    session.save(grade);
    session.save(student);
    session.save(student2);
}

我们可以看到,通过将student设置进入grade的set集合中,从而就建立起了关联关系。那么sql语句是怎么样的呢?

-- 因为是id自增的,所以前两条语句先查询出grade,student的最大id值
Hibernate: select max(gid) from grade;
Hibernate: select max(SID) from student;
-- 插入grade, student
Hibernate: insert into grade (gname, gdesc, gid) values (?, ?, ?);
Hibernate: insert into student (SNAME, sex, SID) values (?, ?, ?);
Hibernate: insert into student (SNAME, sex, SID) values (?, ?, ?);
-- 更新student的gid值
Hibernate: update student set gid=? where SID=?;
Hibernate: update student set gid=? where SID=?;

其实就是先分别插入grade和student,然后更新一下student的gid字段的值。

// 查找
@Test
public void findGradeAndStudent() {
    Grade grade = (Grade) session.get(Grade.class, 1);
    System.out.println(grade.getGname());
    for(Student student : grade.getStudents()) {
        System.out.println(student.getSname());
    }

    System.out.println("---Student---");
    Student jack = (Student) session.get(Student.class, 1);
    Student role = (Student) session.get(Student.class, 2);

    System.out.println(jack.getSname() + "--" + jack.getGrade());
    System.out.println(role.getSname() + "--" + role.getGrade());
}

此时通过查询grade是可以获取到grade中的student值的。但是查询Student是无法获取到Student中的grade值。

// 更新
@Test
public void updateStudent() {
    Grade grade = (Grade) session.get(Grade.class, 1);
    Student role = (Student) session.get(Student.class, 2);
    role.setSname("肉丝");
    grade.getStudents().add(role);
    session.save(grade);
}
@Test
public void delStudent() {
    Student jack = (Student) session.get(Student.class, 1);
    session.delete(jack);
}

单向多对一

Students.java持久化类中添加private Grade grade;属性,并增加get/set方法。
Grade.hbm.xml文件中去除<set>标签。
Student.hbm.xml中添加

<!-- 多对一  -->
<many-to-one name="grade" class="com.whyalwaysmea.hibernate.one2many.Grade" column="gid"></many-to-one>       

测试

/**
* 多对一
 * 学生 -> 班级
 */
@Test
public void saveMany2One() {
    Grade g = new Grade("德玛西亚", "Java软件开发一班");
       Student stu1 = new Student("盖伦", "男");
       Student stu2 = new Student("拉克丝","女");
       //设置关联关系
       stu1.setGrade(g);
       stu2.setGrade(g);
       
       session.save(g);
       session.save(stu1);
       session.save(stu2);
}

同样的,这里我们也观察一下保存数据时的sql语句

Hibernate: select max(gid) from grade
Hibernate: select max(SID) from student
Hibernate: insert into grade (gname, gdesc, gid) values (?, ?, ?)
Hibernate: insert into student (SNAME, sex, gid, SID) values (?, ?, ?, ?)
Hibernate: insert into student (SNAME, sex, gid, SID) values (?, ?, ?, ?)

这里的大体流程和单向一对多的时候差不多,只是在新增student的时候就已经带上了gid的值

@Test
public void findStudent() {
    Student student = (Student) session.get(Student.class, 1);
    System.out.println(student.toString());
    
    System.out.println("----");
    Grade grade = (Grade) session.get(Grade.class, 1);
    System.out.println(grade.getGname() + "--" + grade.getStudents());          
}

查询语句在这里所查询到的Student是带有grade属性值的,而查询到的Grade则是没有students值的。

双向多对一/一对多

单向一对多,建立了班级到学生的关系
单向多对一,建立了学生到班级的关系
实际上,班级和学生的关系是相互的。
需要双方配置关联关系
在Grade.hbm.xml文件中,建立一对多关联关系:

<set name="students" table="student" >
    <!-- 指定关联的外键列 -->
    <key column="gid"></key>
    <one-to-many class="com.whyalwaysmea.hibernate.one2many.Student"/>
        </set>

在Student.hbm.xml中建立多对一关联关系

<!-- 多对一  -->
<many-to-one name="grade" class="com.whyalwaysmea.hibernate.one2many.Grade" column="gid"></many-to-one>   

测试

@Test
public void find() {
    Grade grade = (Grade) session.get(Grade.class, 1);
    System.out.println(grade.getGname());
    for(Student student : grade.getStudents()) {
        System.out.println(student.getSname());
    }

    System.out.println("---Student---");
    Student jack = (Student) session.get(Student.class, 1);
    Student role = (Student) session.get(Student.class, 2);

    System.out.println(jack.getSname() + "--" + jack.getGrade());
    System.out.println(role.getSname() + "--" + role.getGrade());
}

此时查询出来的Student里面的grade是有值的了,查询的Grade里面的students也是有值的了。这就是双向的好处

@Test
public void save() {
    Grade g = new Grade("德玛西亚", "Java软件开发一班");
       Student stu1 = new Student("盖伦", "男");
       Student stu2 = new Student("拉克丝","女");
       // 设置班级->学生的一对多关系
       g.getStudents().add(stu1);
       g.getStudents().add(stu2);
       
       // 设置学生->班级的多对一关系
       stu1.setGrade(g);
       stu2.setGrade(g);
       
       session.save(g);
       session.save(stu1);
       session.save(stu2);
}

在这里可能会觉得有点奇怪,在单向关联的时候,只需要一方来设置关联就可以了,为什么双向关联的时候还要更复杂了呢。 其实这里是可以单独使用其中一种方式就可以的,不一定要同时设置关联关系。但是如果同时设置了关联关系会出现什么问题嘛? 我们可以来看一下sql语句

Hibernate: select max(gid) from grade
Hibernate: select max(sid) from student
Hibernate: insert into grade (gname, gdesc, gid) values (?, ?, ?)
Hibernate: insert into student (sname, sex, gid, sid) values (?, ?, ?, ?)
Hibernate: insert into student (sname, sex, gid, sid) values (?, ?, ?, ?)
Hibernate: update student set gid=? where sid=?
Hibernate: update student set gid=? where sid=?

由于同时设置了两种关联关系,所以sql语句上有点重复了。两条update语句对于班级对学生一对多关系时是多余的。虽然不会造成什么问题,但是对于性能上还是会有一定影响的。

为了解决掉上面的这个问题,我们可以使用inverse属性

inverse属性

inverse属性表示反转,是set节点的一个属性。
<set>节点的inverse属性指定关系的控制方向,默认由one方来维护。
关联关系中,inverse=”false”,则为主动方,由主动方维护关联关系。
当前面的双向关联关系中,多方(学生方)自身会进行关联关系的维护,如果双方都来维护关联关系,性能上是有影响的(上面的例子多了两条update语句)。

可以将inverse属性设置为true,反转,由多方进行维护,这时,班级就不会进行关联关系的维护,这将有助于性能的改善。
Grade.hbm.xml

Hibernate: select max(gid) from grade
Hibernate: select max(SID) from student
Hibernate: insert into grade (gname, gdesc, gid) values (?, ?, ?)
Hibernate: insert into student (SNAME, sex, gid, SID) values (?, ?, ?, ?)
Hibernate: insert into student (SNAME, sex, gid, SID) values (?, ?, ?, ?)

cascade属性

但是仔细观察上面的程序,在设置好关联关系后,班级中是有学生的信息的。班级是知道有哪些学生的。
那么,保存班级的时候,如果发现有学生在数据库中不存在,就应该自动把学生信息添加到数据库中,这个叫做级联操作。

当设置了cascade属性不为none时,Hibernate会自动持久化所关联的对象

属性值 含义和作用
all 对所有操作进行级联操作
save-update 执行保存和更新操作时进行级联操作
delete 执行删除时进行级联操作
none 对所有操作不进行级联操作

cascade属性的设置会带来性能上的变动,需谨慎设置

属性值 含义和作用
all 对所有操作进行级联操作
save-update 执行保存和更新操作时进行级联操作
delete 执行删除时进行级联操作
none 对所有操作不进行级联操作
/**
 * 设置了cascade之后保存
 */
@Test
public void saveByCascade() {
    Grade grade = new Grade("诺克萨斯", "哈哈哈哈嘿嘿嘿");
    Student stu1 = new Student("德莱厄斯", "男");
    Student stu2 = new Student("魔蛇之拥","女");
    stu1.setGrade(grade);
    stu2.setGrade(grade);
    
    grade.getStudents().add(stu1);
    grade.getStudents().add(stu2);
    
    session.save(grade);
}

参考

Hibernate中的单向一对多关联
慕课网-Hibernate初探之一对多映射
相关代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349