`

第十章 Inverse的作用

阅读更多
Inverse的作用

我们还是拿上一章的班级与学生的示例来说明问题,
请看下面的代码:
Student student1 = new Student("张三");
Student student2 = new Student("李四");
MyClass myClass = new MyClass("java0801");
Set<Student> students = myClass.getStudents();
students.add(student1);
students.add(student2);
myClassDao.create(myClass);
现在是让班级知道学生,可以观察一下控制台输出的SQL语句:
Hibernate: insert into myclass (name, id) values (?, ?)
Hibernate: insert into students (name, student_id, id) values (?, ?, ?)
Hibernate: insert into students (name, student_id, id) values (?, ?, ?)
Hibernate: update students set student_id=? where id=?
Hibernate: update students set student_id=? where id=?
可以看到执行了五条SQL语句,

前三台,我们可以理解,做了插入的动作,那么后面的二条Update语句是怎么来的呢?

我们先将级联的开关取消,也就是将集合的配置如下:
<set name="students">
<key column="student_id" />
<one-to-many class="chapter9.model.Student" />
</set>

测试代码:
MyClassDao myClassDao = new MyClassDao();
StudentDao studentDao = new StudentDao();

Student student1 = new Student("张三");
Student student2 = new Student("李四");
MyClass myClass = new MyClass("java0801");
Set<Student> students = myClass.getStudents();
students.add(student1);
students.add(student2);

myClassDao.create(myClass);
studentDao.create(student1);
studentDao.create(student2);

那么这时我们可以看到控制台上面的信息,发现程序出现了异常:
Hibernate: insert into myclass (name, id) values (?, ?)
Hibernate: update students set student_id=? where id=?
Exception in thread "main" org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: chapter9.model.Student

我们先分析一下代码,当程序执行到myClassDao.create(myClass);这一行代码时,会执行一条INSERT SQL语句向数据库插入班级信息,我们说过,在事务开始与事务结束之间,这时的对象是持久状态,对象的状态与数据库是同步的.那么这个时候myClass发现自己的学生集合中有两个学生,那么这个时候很自然的就需要维护班级与学生之间的关系,就去数据库中将学生表中的外键更新为自己的主键.但是这个时候Hibernate又发现学生表中又没有这个学生的信息,所以就报错了(object references an unsaved transient instance,翻译后就是” 对象引用一个未保存的瞬态实例”)

如果我们要保存的话,就必须得先保存学生再保存班级.将代码改为:
studentDao.create(student1);
studentDao.create(student2);
myClassDao.create(myClass);
这时就不会报错了,
当创建学生时,因为班级还没有创建,所以学生表的外键暂时是空的,
当创建班级时,因为可以找到学生了,所以就需要更新学生表的外键为自己的主键.
我们现在可以看到执行了五条SQL语句,但是我们现在可以理解为什么要多执行两条Update语句了.

接下来我们反过来,先保存班级,再保存学生,执行下面的代码:
Student student1 = new Student("张三");
Student student2 = new Student("李四");
MyClass myClass = new MyClass("java0801");
student1.setMyClass(myClass);
student2.setMyClass(myClass);

myClassDao.create(myClass);
studentDao.create(student1);
studentDao.create(student2);
可以看到控制台只执行了三条SQL语句:
Hibernate: insert into myclass (name, id) values (?, ?)
Hibernate: insert into students (name, student_id, id) values (?, ?, ?)
Hibernate: insert into students (name, student_id, id) values (?, ?, ?)

我们来分析一下:
当执行myClassDao.create(myClass);这一句时,Hibernate发现此时班级对象的学生集合是空的,那么这时就不需要维护班级与学生之间的关系,因为现在还没有一个学生.
当执行到的添加学生的语句时,Hibernate发现学生对象中有一个班级对象,正好这个班级对象已经有了,那么这时候拿过来直接用就可以了.所以这时就没有多余的Update语句了.

这时说明了一个问题,在一对多的关系中,在做增加操作时,如果让一方维护关系,会多执行若干条Update语句,如果让多方来维护关系,则不会执行多余的Update语句,从而效率上要高一些.
可以打个比喻:如果让一个老师记住班上所有学生的姓名,估计难度大一些(老师需要记四十五个学生的姓名),但是如果让学生记住老师的姓名则容易的多(每个学生只需要记住一个老师的姓名即可).

Inverse的作用就是强制性的让一方不去维护自己与另一方的关系(比如强制性的让老师不去记学生的姓名),此属性通常设置在一方,即有集合的一方,比如set标签上,
<set name="students" inverse="true">
<key column="student_id" />
<one-to-many class="chapter9.model.Student" />
</set>

我们再来执行下面的代码:
MyClassDao myClassDao = new MyClassDao();
StudentDao studentDao = new StudentDao();

Student student1 = new Student("张三");
Student student2 = new Student("李四");
MyClass myClass = new MyClass("java0801");
Set<Student> students = myClass.getStudents();
students.add(student1);
students.add(student2);

studentDao.create(student1);
studentDao.create(student2);
myClassDao.create(myClass);
可以看到控制台只执行了三条Insert语句,并没有执行其它的Update语句,
Hibernate: insert into students (name, student_id, id) values (?, ?, ?)
Hibernate: insert into students (name, student_id, id) values (?, ?, ?)
Hibernate: insert into myclass (name, id) values (?, ?)
但是我们再来看看数据库

可以看到外键成了Null

这时我们就能说明Inverse的作用了,那么这时,如果我们要作增加操作的话,必须从学生那一端下手了,先创建班级,再创建学生,从而强制性的避免执行多余的Update语句了.

但是不能两端都设置这个属性为true,如果都为true,意思就是都不去维护彼此之间的关系,那么他们之间就没有关系了,从而违背了程序的本意.

接下来,我们看下面的代码:
<list name="students" inverse="true">
<key column="class_id" />
<list-index column="listindex" />
<one-to-many class="chapter2.model.Student" />
</list>
如果我们需求是需要保存学生的存储顺序,那么这时我们问题就来了,如果我们在List标签上设置了inverse="true",那么就有点说不通了,因为自相矛盾了,list集合要记住学生的顺序,那么这时他必须记住学生才行,如果我们强制性的让他不去记,那么怎么维护学生的顺序呢?
所以在Hibernate中有一个规定,在有序集合的标签中(list, array)是不能使用inverse="true"

inverse这个属性并不是所有的标签都能够设置,比如在多方
<many-to-one name="myClass" column="student_id" />
就没有这个属性设置
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics