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 students = new HashSet<>();
    
    // 省略setter/getter
}

映射文件

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

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






    
        
            
            
        
        
            
        
        
            
        
    





    
        
            
        
        
            
        
        
            
        
        
            
            
            
        
    

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文件中去除标签。
Student.hbm.xml中添加


       

测试

/**
* 多对一
 * 学生 -> 班级
 */
@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文件中,建立一对多关联关系:


    
    
    
        

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


   

测试

@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节点的一个属性。
节点的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初探之一对多映射)