Hibernate框架——关系操作(一)

在之前两篇博文中,介绍了Hibernate框架的基本操作和对象的状态和一级缓存的知识,在本篇博文中将介绍对象之间的关系操作.

关系操作

一对多的单项关联

持久化类和映射文件

1
2
3
4
5
6
7
8
public class Classes implements Serializable{
private Long cid;
private String name;
private String description;
private Set<Student> studnets;
// set和get方法因为版面,省略不写
}
1
2
3
4
5
6
7
public class Student implements Serializable{
private Long sid;
private String name;
private String description;
private Classes classes;
// set和get方法因为版面,省略不写
}

可以看到只能通过Classes查找到Student,但是不能通过student查找到Classes。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.wcc.hibernate.domain.Classes">
<id name= "cid" length = "5">
<generator class="increment"></generator>
</id>
<property name="name" length="20"></property>
<property name="description" length="20"></property>
<!--
cascade 级联
save-update : 当保存或者更新classes的时候,级联操作student
inverse :维护关系
true 不维护关系
false 维护关系
default 默认设置为false
-->
<set name="students" cascade="save-update">
<!-- key 代表外键 : 用来关联classes 和student表,用于在hibernate底层生成SQL语句。 -->
<key>
<column name="cid"></column>
</key>
<!-- 建立类与类之间的关联,用于客户端的编码 -->
<one-to-many class="com.wcc.hibernate.domain.Student"/>
</set>
</class>
</hibernate-mapping>

从映射文件可以看出,set元素描述了两个方面的内容:

  1. key从外键的角度描述了两者之间的关系,用于生成SQL语句。
  2. one-to-many 从类与类的角度描述了两者的关系,用于客户端的编码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 保存班级的同时保存学生
@Test
public void testSaveStudentAndStudnt(){
Session session=HibernateUtils.sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
//创建一个Class对象
Classes classes = new Classes();
//classes.setCid(2L);
classes.setName("java");
classes.setDescription("java基础");
//创建学生的集合
Set<Student> students = new HashSet<Student>();
//创建两个学生
Student student = new Student();
student.setName("毕向东");
student.setDescription("java大师");
Student student2 = new Student();
student2.setName("王昭珽");
student2.setDescription("web大师");
students.add(student);
students.add(student2);
//通过Class建立classes与student之间的关联
classes.setStudnets(students);
session.save(classes);
transaction.commit();
}

说明
1、在hibernate中,通过国session.save方法保存一个持久化对象,这种方式称为显式保存。
2、在hibernate中,通过级联的方式来操作保存一个对象,这样的方式称为隐式保存。
3、对student对象进行隐式的保存操作,是因为studnet是一个临时状态的对象,在数据库中有没对应的记录,所以应该对student执行insert语句。

更新班级级联更新学生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 更新班级的时候级联学生
*/
@Test
public void testUpdateStudentAndClasses(){
Session session = HibernateUtils.sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
//获取cid为2的classes
Classes classes = (Classes) session.get(Classes.class, 2L);
//获取该班级的所有学生
Set<Student> set = classes.getStudnets();
for (Student student : set) {
student.setDescription("好孩子");
}
transaction.commit();
}

说明:

  1. 当执行Classes classes = (Classes)session.get(Classes.class, 2L);的时候产生如下的SQL语句。
    输出Classes的查询

2.当执行Set set = classes.getStudnets();的时候,产生了如下SQL语句
输出Student的查询

  1. 当执行 transaction.commit();的时候执行如下的SQL语句。该语句有两条,得到的set集合中有两条记录。
    学生的update
  2. 没有发出更新classess的update语句,因为classes的属性没有发生改变。

总结
Cascade:
Save-update
在session.save/update 一个对象的时候,级联操作关联对象,关联对象或者执行save语句或者执行update语句或者什么都不执行
Delete
在session.delete一个对象的时候,级联删除关联对象
All
save-update和delete的结合

在次讨论session.flush

  1. 检查session一级缓存中所放入持久化对象的状态,决定发出insert语句或者update语句。
  2. 会检查所有的持久化对象关联对象,如果有级联操作,则对关联对象进行insert语句或update语句。

关系操作

让一个新的学生加入已经存在的班级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//关系操作 让一个新的学生加入已经存在的班级
@Test
public void testNewStudentAddClasses(){
Session session = HibernateUtils.sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
Student student = new Student();
student.setName("王小臭");
student.setDescription("好厉害");
Classes classes = (Classes) session.get(Classes.class, 2L);
//得到该班的所有学生, 将新的学生添加大班级中
classes.getStudents().add(student);
/*
这里面要进行session.save(student);吗? 这里是如果想让hibernate隐式对的操作,那么就在映射文件中set元素的cascade 属性为save-update*/
transaction.commit();
}

说明:

  1. 当执行 Classes classes = (Classes)session.get(Classes.class, 2L);语句的时候,hibernate会发出加载classes的SQL语句。
  2. 当执行classes.getStudents().add(student);的时候,会发出加载classes班级中的所有的学生的SQL语句
  3. 因为Classes.hbm.xml 文件中set元素的cascade属性设置了save-update,所以在更新classes的时候,要检查classes中的student集合,发现集合中多了一个对象,所以要对该对象进行insert语句。
    4.当执行session.flush的时候,会发出维护关系的update语句,因为classes负责维护classes与student之间的关系。

Classes.hbm.xml文件中的set元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--cascade :级联
save-update : 当保存或者更新classes的时候,级联操作student
inverse :维护关系
true 不维护关系
false 维护关系
default 默认设置为false:inverse属性没有写,保持默认值,维护classess与studnet 之间的关系-->
<set name="students" cascade="save-update">
<!-- key 代表外键 : 用来关联classes 和student表,用于在hibernate底层生成SQL语句。-->
<key>
<column name="cid"></column>
</key>
<!-- 建立类与类之间的关联,用于客户端的编码 -->
<one-to-many class="com.wcc.hibernate.domain.Student"/>
</set>

重新建立关系1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//重新更新关系
@Test
public void testChangRealtive(){
Session session = HibernateUtils.sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
//先找到一个学生的对象
Student student = (Student) session.get(Student.class, 1L);
Classes classes = (Classes) session.get(Classes.class, 1L);
Classes classes2 = (Classes) session.get(Classes.class,2L);
// 先要解除原来的关系,
classes.getStudents().remove(student);
//再将这个学生和其他的班级进行关联
classes2.getStudents().add(student);
/*
因为我们这里拿到的全是持久化对象,所以,
不用进行Session.save()的操作。
*/
transaction.commit();
}

说明:

  • 执行 classes.getStudents().remove(student);的时候,发出维护关系的SQL语句
    remove_null
    因为解除了关系了,所以cid为null
  • classes2.getStudents().add(student);的时候,发出更新的操作。
    remove_studnet
    因为建立了关系,所以更新了外键
  • 从上面的代码和SQL语句可以看出,外键变了两次,但是不需要改变两次,请看下面的优化版的重新建立关系。

重新建立关系2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//重新更新关系
@Test
public void testChangRealtive(){
Session session = HibernateUtils.sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
//先找到一个学生的对象
Student student = (Student) session.get(Student.class, 1L);
Classes classes = (Classes) session.get(Classes.class, 1L);
// 建立学生的新的班级关系
classes.getStudents().add(student);
/*因为我们这里拿到的全是持久化对象,所以,不用进行Session.save()的操作。
*/
transaction.commit();
}

说明:只需要建立关系就可以了,因为发出的是更新外键的SQL语句,把外键的值改变了就可以了。

解除一个学生和一个班级之间的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 已经存在一个班级,已经存在一个学生,
* 解除该学生和改班级之间的关系
*/
@Test
public void testReleaseR(){
Session session = HibernateUtils.sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
/**
* 基础cid 为2 和sid为2 的关系
*/
Student student = (Student) session.get(Student.class, 2L);
Classes classes = (Classes) session.get(Classes.class, 2L);
// 得到该班级class 然后在从学生的集合中删除该学生
//即可以解除该学生与之前的关系
classes.getStudents().remove(student);
transaction.commit();
}

解除该班级和所有学生之间的关系

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 解除该班级和所有学生之间的关系
*/
@Test
public void testRealseAll(){
Session session = HibernateUtils.sessionFactory.getCurrentSession();
Transaction transaction = session.getTransaction();
Classes classes = (Classes) session.get(Classes.class,2L);
//classes.getStduents().clear(); 这样操作是查询出学生之后,一个个清除,效率是不高的。
classes.setStudents(null);//这样写就是不查询的,效率是最高的。
transaction.commit();
}

一般来说hibernate发出的SQL语句越少的话,效率是越高的,当执行 classes.setStudents(null) 的时候,不需要根据学生进行加载了,这样执行效率会更加的高效一些。

删除班级

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 删除班级
* <set name= "students" save="save-update">
*/
@Test
public void testDeleteClasses(){
Session session = HibernateUtils.sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
Classes classes = (Classes) session.get(Classes.class, 2L);
session.delete(classes);
transaction.commit();
}

说明:

  1. classes负责维护关系。
  2. 在执行session.flush的时候,在删除班级之前,解除该班级和所有的学生之间的关系(Student表中的外键全变为null,即解除了关系)。 如果你设置在配置文件中的set 节点中inverse 为true(即不维护表之间的关系),那么在删除班级的时候是不会进行Student表的外键解除约束的,这样就会报错。在这种情况下,只能先去删除学生,再去删除班级了,所以说,不同的配置,就对应不同的代码和逻辑的操作。
  3. 解除关系以后,再删除班级。
  4. 上诉的代码是:先查询班级,再查询该班级中的每一个学生,再根据每一个学生的sid删除每一个学生,这样的效率比较低。

删除班级级联删除学生

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 删除班级,级联删除学生
* <set name="students" save="all" >
*/
@Test
public void testDeleteCalsses(){
Session session = HibernateUtils.sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
Classes classes = (Classes) session.get(Classes.class, 2L);
session.delete(classes);
transaction.commit();
}

在hibernate中Cascade 与inverse的区别

  • cascade描述的是对象与对象之间的关系, cascade和外键没有关系,在Studen表中,sid、name、description和cascade有关系,但是cid和cascade没有关系。
  • inverse描述的是对象与外键之间的关系,inverse只和cid有关系,如果维护,则发出update语句(更新外键的SQL语句),如果不维护,则不管。

总结

  • 只能通过classes操作student
  • 只要classes维护关系,就会发出维护关系的update语句,所以让classes维护关系效率比较低。
  • 让student维护效率比较高(让较多的一方维护效率更高)
  • Session.flush的时候,hibernate内部做的事情
    1、检查一级缓存中所有的持久化对象,决定发出insert语句还是update语句
    2、检查持久化对象的关联对象,看持久化对象的映射文件中的cascade,决定关联对象是否发出insert语句或者update语句或者delete语句
    3、检查持久化对象的映射文件的针对关联对象的inverse属性,来决定是否维护关系,如果维护关系,则发出update语句(维护关系的语句)

一对多双向关联

Classes对象

1
2
3
4
5
6
7
8
9
public class Classes implements Serializable{
private Long cid;
private String name;
private String description;
private Set<Student> studnets;
...
//省略 set和get方法
}

Student对象

1
2
3
4
5
6
7
public class Student implements Serializable{
private Long sid;
private String name;
private String description;
private Classes classes;
//省略set和get方法
}

Student映射文件如下,其中可以发现配置中 Classes中有一个外键,Student建立关联的时候也有一个外键,那么会不会有冲突呢?事实上是,通过谁建立关系,就看谁的映射文件,那么就使用按个外键。不会进行冲突的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.wcc.hibernate.domain.Student">
<id name="sid" length="5">
<generator class="increment"></generator>
</id>
<property name="name" length = "20"></property>
<property name="description" length = "20"></property>
<!-- column 为外键-->
<many-to-one name="classes" column="cid" class="com.wcc.hibernate.domain.Classes" cascade="save-update"
>
</many-to-one>
</class>
</hibernate-mapping>

操作

保存学生,级联保存班级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
保存学生,级联保存班级
*/
public void testSaveStudent_Cascade_SaveClass(){
Session session = sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
Student student = new Student();
student.setName("cc");
Classes classes = new Classes();
classes.setName("皇马");
// 通过sudent建立student与classess 之间的联系
student.setClasses(classes);
session.save(student);
transaction.commit();
}

说明:

  1. studnet.setClasses(classes)是通过student建立student与classes之间的关联,所以看外键时是看Student.hubm.xml文件,不看Classes文件了。
  2. 在Student.hbm.xml文件中,设置了针对classes的级联
    1
    2
    <many-to-one name="classes" column="cid" class="com.wcc.hibernate.domain.Classes" cascade="save-update">
    </many-to-one>

3.在保存学生的时候级联保存了班级
4.保存学生相当于维护了关系。在保存学生的时候,cid有值了,不需要发出维护关系的update语句。
保存班级级联保存学生

新建一个学生,并且和一个已存在的班级级联

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 新建一个学生,并且和一个已存在的班级关联
*/
@Test
public void testSaveStudent_BuildR(){
Session session = HibernateUtils.sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
Student student = new Student();
student.setName("王小模");
student.setDescription("好好的");
//将班级提取出来
Classes classes = (Classes) session.get(Classes.class, 2L);
//通过学生来进行与班级的关系操作
student.setClasses(classes);
// 保存到了 student
session.save(student);
transaction.commit();
}

学生转班操作

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 学生转班
* */
@Test
public void testTransfran(){
Session session = HibernateUtils.sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
Student student = (Student) session.get(Student.class, 2L);
Classes classes = (Classes) session.get(Classes.class, 1L);
// 学生转班级
student.setClasses(classes);
transaction.commit();
}

说明: 从上述的语句可以看出不仅更新了外键,而且把其他的属性也更新了,所以更新关系指的就是更新student 对象本身的操作。

解除一个班级和一个学生之间的关系

1
2
3
4
5
6
7
8
9
10
11
/**
* 解除一个班级和一个学生的关系
*/
@Test
public void testRealse(){
Session session = HibernateUtils.sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
Student student = (Student) session.get(Student.class, 1L);
student.setClasses(null);
transaction.commit();
}

解除该班和所有的学生的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 解除一个班级所有学生的与它的关系
* */
@Test
public void testRealseAll(){
Session session = HibernateUtils.sessionFactory.getCurrentSession();
Transaction transaction = session.beginTransaction();
Classes classes = (Classes) session.get(Classes.class, 2L);
Set<Student> set = classes.getStudnets();
//取得该班的所有的学生集合,
//然后一个一个的解除该班的学生关系。
// 但是,这个的效率说是并不太高的。
for (Student student : set) {
student.setClasses(null);
}
}

总结

一般情况下,一对多,多的一方维护关系效率比较高。

坚持原创技术分享,您的支持将鼓励我继续创作!