Hibernate初探之一对多映射

常见的关联对应关系

  • OneToMany
  • ManyToOne
  • OneToOne
  • ManyToMany

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

单向一对多关联

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

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

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

代码实现

建立数据库和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会自动持久化所关联的对象

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初探之一对多映射
相关代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值