Hibernate中cascade与inverse属性详解
Hibernate中cascade与inverse属性详解
关于Hibernate中cascade与inverse的理解。
您买的Hibernate书是哪一本呢?孙卫琴的精通Hibernate,还是深入浅出Hibernate还是那本。。。
我是两本都买了,总体来说还可以,但是,有的地方讲的比较书面化,比如inverse这属性。
在学习Hibernate的过程中最不好理解的就是这两个属性了。
(我当初学习Hibernate的时候,发现网上介绍这两个属性的文章倒是不少,但是,居然有好多都是转帖。。。还有的就是照书搬~~-_-!!!)。。。
据个例子:书上说inverse=false时,由主控方维持关系。。。
由于我也是初学者。。。再加上语文水平偏低。。。不理解“维持关系是啥意思”囧~
提示:
(1)如果:您不了解Hibernate的one-to-many或many-to-one的概念。
(2)如果:你不了解Hibernate的“自由态”“持久态”“游离态”的概念。
(3)如果:您不了解Hibernate中的“脏数据”的概念。
(4)如果:您对Hibernate中Session缓存,没有初步了解的话。
(在Hibernate中调用save进行存储数据的时候,并不是马上就对数据库进行insert操作,而是会将其“数据对象(vo)”纳入Hibernate的Session缓存。)
在上面的4条提示中,如果您对其中的某一条,不是很清楚的话。希望请先了解有关知识。
否则,可能您将“无法或很难”理解cascade或inverse这2个属性。
首相,cascade与inverse这两个属性,其实是完全不同的两个东西,想要了解他们各自的“用途与区别”,详见如下介绍:
这里有两个表:
(1)class(班级表)
相应字段:
cidvarchar(32)主键not-null(班级id)
cnamevarchar(16)not-null(班级名称)
(2)student(学生表)
相应字段:
sidvarchar(32)主键not-null(学生id)
snamevarchar(16)not-null(学生姓名)
class_idvarchar(32)not-null(学生所属班级)
一个班级(class)对应多个学生(student),所以班级表(class)就是“one-to-many”端
反之student就是many-to-one
//--------Class类的代码--------
publicclassClassimplements.....
{
privatecid="";
privatecname="";
privatestudents=java.util.HashMap();
//省略对应的getersetter
}
//--------Class.hbm.xml--------
<hibernate-mapping>
<classname="lcx.vo.Class"table="class"
catalog="demo">
<idname="cid"type="java.lang.String">
<columnname="cid"length="32"/>
<generatorclass="uuid.hex"/>
</id>
<propertyname="name"type="java.lang.String">
<columnname="cname"length="16"not-null="true"/>
</property>
<setname="students"table="student"cascade="save-update">
<keycolumn="class"/>
<one-to-manyclass="lcx.vo.Student"/>
</set>
</class>
</hibernate-mapping>
//--------Student类的代码;*******
publicclassStudentimplements.....
{
privatesid="";
privatesname="";
privateClassclass=null;
//省略对应的getersetter
}
//Student.hbm.xml
<hibernate-mapping>
<classname="lcx.vo.Student"table="student"catalog="demo">
<idname="cid"type="java.lang.String">
<columnname="sid"length="32"/>
<generatorclass="uuid.hex"/>
</id>
<many-to-onename="class"
class="lcx.vo.Class"
column="class_id"
not-null="true"
/>
</class>
</hibernate-mapping>
(一)cascade的介绍:
当Hibernate持久化一个“临时对象(也叫自由态对象)”时,在默认的情况下(即:没有设置cascade属性或cascade=none时),Hibernate不会自动“持久化他所关联”的其他临时对象。
上面这些话是什么意思呢?什么叫不会自动“持久化”关联的临时对象呢?
看如下代码:
//创建一个临时对象(也叫自由态对象)
//也就是说这个class没有被Hibernate纳入Session缓存管理。
Classclass=newClass();
//class.id为自动生成
class.setName("一年级1班");
Studentstu=newStudent();
//student.id为自动生成
stu.setName("小白兔");
stu.setClass(class);
//关键就是这里。。。
class.getStudents().add(stu);
session.save(class);
//提交
//注意:Class.hbm.xml文件中,cascade="save-update"并且也没有设置inverse属性,也就是说inverse=false;
//此时如果你开启了Hibernate的显示HQL语句功能,那么控制台将会显示如下3条HQL:
//----------------------------------------********
insertintodemo.class(cid,cname)values(66666666666666666666666666666666,一年级1班)
insertintodemo.student(sid,sname,class_id)values(8888888888888888811cb2e04c888888,小白兔,66666666666666666666666666666666)
updatedemo.studentsetclass_id=66666666666666666666666666666666wheresid=8888888888888888811cb2e04c888888
//----------------------------------------********
那么为什么会出现,这3条HQL语句呢,我们来一一分析一下:
第1条HQL语句:
其实第一条HQL比较好理解,
当我们调用session.save(class)后,在Hibernate进行提交的时候,
会发现“有”一条“新”的数据要插入(insert),所以就往class表中,插入了这条新的class记录。
第2条HQL语句:
注意问题就在这里:
这里为什么又出现了一条insert语句呢?而且还是向student表中插入数据。
我们在上面的代码中,并没有编写类似“session.save(student)”这样的语句啊。
这是为什么呢?
其实原因,是这么回事:因为我们在class端,设置了"级联更新"(即:cascade="save-update"),
也就是说,当Hibernate在向class表中插入“新”对象记录时,会检查“Class对象”所关联的属性(就是<set>对应的属性),是否发生过变化,如果发生了变化,就按照“级联属性(cascade)”所设定的内容
进行操作。
上面讲的这句话到底是什么意思呢?
用你们“人”话说,就是:
因为调用了class.getStudents().add(stu);
所以,在Hibernate在进行插入class对象的时候,发现class对象,所关联的集合中,有一条
“自由态”的对象,而又因为class端设置了“级联属性cascade”,所以,在插入这条“新class对象”时,也一同把他内部的那些,还属于“自由态”的其他对象,也一同插入到,他们所对应的表中去了。
还是不明白的话。。。可以看看。孙卫琴的《精通Hibernate》,在书上的第149页有。
但是关于inverse的介绍。。。写的就有些书面化了,如果语文不好的话。。。就难懂咯~
第3条HQL语句:
第三条HQL语句是一条update语句,是不是觉得,很莫名其妙。。。。
Hibernate大脑进水了吧,怎么吃饱了撑得,重复更新记录啊啊啊啊啊
假如:我们把class端的配置文档中的invser属性设置为true(即:inverse=true)
在执行上面的程序,发现,就变成2条insert语句啦。。。。。(update没啦。。。)
看来第三条的update语句和inverse有着密切的关系(他两有一腿~)。
所以我们下边,就来介绍一下inverse属性:
当调用Class.getStudents().add(stu)方法,进行添加操作时,
(即:向"这个Class对象"所属的“集合(也就是调用getStudents方法所返回的那个Set集合)”中添加一个Student(即add(stu)),也就是说,这个“新”添加的Student对象(stu),
他的Student.class_id字段“必须”,要等于“被添加方Class”的主键(即:Class.cid)。
从“数据库”层面来讲,也就是说,这个“新”添加的“Student”的class_id字段,必须要与“Class”的cid字段,存在"主外键关联"。)
正因为如此:所以Hibernate“怕”在进行"Class.getStudents().add(stu)"这样的操作时,
出现意外情况(如:stu.getClass=null,即:stu没有所属班级),
即“添加方”(Student)与“被添加方”(Class),存在“外键”不一致的情况发生。
所以就出现了那条多余的update语句。即:one-to-many(Class端)主动去维护Child.Class_id
所以就是说,Hibernate怕出错,就给你多执行一次无用的更新语句,以保证add到Class“集合”中的所有Student
都是要与Class有外键关联的。
用普通话说就是:
一年1班.getStudents().add(小白兔);
一年1班.getStudents().add(大白兔);
也就是说现在不管是小白兔还是大白兔
如果他们,目前还没有自己的班级的话,
一年1班的班主任就会主动邀请他们成为一年1班的同学啦~。
也就是说一年1班的班主任主动邀请同学,而不是同学自己来~~~所以效率也降低了。。。。
所以我们一般把一对多端invser设置为true,即:不让主控端去维护主键关联,
(即:让同学自己去找班级)
说白了,就是,one-to-many端不用去管理“新添加对象”的主外键约束问题。
把one-to-many端(即:class端)的invser设置为true
(即:每次向class.getStudents这个集合中添加student时,不去主动update对应的外键),
而是在student端去手动设置
例如:
student.setClass(class);
session.save(student);
这样手动设置student与class关联啦。。。。
所以上面的程序“最好”还是写成这样:
Classclass=newClass();
class.setName("一年级1班");
session.save(class);
Studentstu=newStudent();
stu.setName("小白兔");
stu.setClass(class);
session.save(class);
/*
此时向class集合add内容,不会进行数据库操作(update)。
“更新”的只是session缓存中,数据镜像。
这样做的好处是:不仅减少了update语句,
而且,同时也更新了session缓存。
------------------------
而在原来:
one-to-many端inverse=false时,虽然也更新seesion缓存中的class集合,
但是有却又多余update
*/
class.getStudents().add(stu);
//提交
总结:
当inverse=false并且向one-to-many端的关联集合,添加“新对象(即:自由态对象)”时,
Hibernate就会自动,去update那“个刚刚到来的”“自由态对象”的外键。
(如果你向,one-to-many端添的集合中,add一个“已经持久化了的对象”,那就不会出现update了(因为已经持久化过了),除非,你去更改“那个持久化对象”所对应的外键。。。那样的话。。。呵呵呵~~~
你可以试一试,应该不会报错,你可以当做练习去做一下,加深cascade和inverse这两个属性的理解)
//如果看懂了上面的内容。来看一下,下面的东西。
假如,将one-to-many端(即:Class端)的hbm.xml文档中的cascade移除掉或把cascade="none"。
那么上面的代码会出现什么情况呢。
结果会出现2条HQL,和一堆Exception
insertintodemo.class(cid,cname)values(66666666666666666666666666666666,一年级1班)
updatedemo.studentsetclass_id=66666666666666666666666666666666wheresid=8888888888888888811cb2e04c888888
HibernateExceptinon......................................
相比较cascade被设置"save-update"的时候,缺少了1条insert语句,而且也多了一些Exception。
那么,到底是少了哪1条insert语句呢?
就是这条:
insertintodemo.student(sid,sname,class_id)values(8888888888888888811cb2e04c888888,小白兔,66666666666666666666666666666666)
之所以会出现,这样的现象,想必您已经早就看出来了。
因为,我没有设置Class端的Cascade,所以在save(class)的时候,并没有自动将其所关联的“自由态对象”进行持久化操作。
然而,又因为Class端的inverse=false,所以,Class会自动去维持,那个“新来的student”的外键。
所以会出现,没有insert就要update啦。。。。
然后在就是Exception了