美文网首页
Hibernate初探之一对多映射

Hibernate初探之一对多映射

作者: 最最最最醉人 | 来源:发表于2018-01-15 15:49 被阅读20次

    常见的关联对应关系

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

    相关文章

      网友评论

          本文标题:Hibernate初探之一对多映射

          本文链接:https://www.haomeiwen.com/subject/jmhdoxtx.html