hibernate的级联可以说是hibernate最重要的部分,只有深入了解了级联的特性与用法,才能运用自如。
这次讨论一对多的情况,所以就使用博客项目的用户表和博客表作为示例,来一起学习hibernate的级联
基本准备
文件结构:
hibernate核心配置文件hibernate.cfg.xml:
)
1 | 复制代码<?xml version="1.0" encoding="UTF-8"?> |
View Code
如果对hibernate的配置还不是很清楚,可以看看这里
实体类的创建
Hibernate中,可以直接将表的关系用对象表示。
如本例中,一个博客只能有一个作者,所以Blog就可以添加一个User对象。
一个用户有多个博客,所以可以在User中添加一个Blog的Set 集合。
这里需要注意的是如果关联的是一个对象,那么不能在类中进行初始化new操作。
如果关联的是一个集合,那么必须用HashSet在类中进行初始化new操作
实体类Blog.java
)
1 | 复制代码package com.cky.domain; |
View Code
实体类User.java
)
1 | 复制代码package com.cky.domain; |
View Code
编写基础映射文件
多对一情况映射文件的编写
多对一时,使用
name:指定此标签所映射的属性名
class:关联的表所对应的实体类的全限定类名
column:关联表的外键名
Blog.hbm.xml文件具体内容
1 | 复制代码<?xml version="1.0" encoding="UTF-8"?> |
一对多情况映射文件的编写
与多对一情况不同的是,一对多时关联对象是一个set集合。
配置文件需要使用
在
User.hbm.xml具体如下:
1 | 复制代码<?xml version="1.0" encoding="UTF-8"?> |
为了方便使用,还需要一个工具类HibernateUtils.java,很简单就不介绍了,下面是代码:
)
1 | 复制代码package com.cky.utils; |
View Code
测试基础配置(不使用级联)
到这里,基本的配置都设置完了,接下来测试配置的怎么样
1 | 复制代码package com.cky.Demo; |
什么,居然报错了:TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing
翻译一下,大致意思就是user对象引用了一个瞬时对象,因为当save(user)时,user已经被保存到缓存成为持久态对象,而给他添加的blog1和blog2,因为没有设置级联,所以不会被自动添加到缓存中,依然是瞬时态对象。
解决方法就是把两个blog1和blog2也进行save(),保存到session中:
1 | 复制代码@Test |
关于级联
hibernate中用cascade属性设置级联。
在基础的配置中,因为没有设置级联,默认是none,也就是不进行级联操作。
就如上面的代码一样,我们需要手动的保证对象和他级联的对象都在同一状态,才能正确运行,这显然是很麻烦的,下面就看看如何通过设置级联属性来让代码更简单。
cascade取值共有5个:
none 默认值,不级联
save-update 在保存、更新操作时自动级联保存更新关联对象
delete 在删除时自动级联删除关联对象
all 类似save-update-delete,即所以的操作都会级联
all-delete-orphan 解除某一节点的关系时删除该节点(默认只是清除外键关系)
接下来就在上面的基础配置上添加上面的属性看看有什么区别:
save-update:
为user配置文件的添加cascade属性
1 | 复制代码<set name="blogs" **cascade="save-update"**> |
此时我们运行上次报错的那段代码:
)
1 | 复制代码@Test |
View Code
发现可以正确执行,因为保存user时,会自动级联保存两个blog,所以他们就全是持久态。
我们同时为blog配置文件添加cascade属性
1 | 复制代码<many-to-one name="user" class="com.cky.domain.User" column="u_id" cascade="save-update"></many-to-one> |
然后保存一个blog看看会发生什么
1 | 复制代码@Test |
运行成功,不过更有意思的是他保存了三条信息,而不是两条。
因为当保存 blog2 时,会级联保存 user ,而user又会级联把 blog1 保存
删除也是同样的道理,就不演示了,下面再研究一个all-delete-orphan,传说的孤儿删除
关于all-delete-orphan
all-delete-orphan上面已经简单介绍过,就是解除关系时会把节点删除而不只是删除外键。
我们把使用和不使用孤儿删除分别用代码实现,并做一次比较:
正常情况下的解除关系:
原来的blog表中两条数据都和user id=1产生关系
现在我们把user和其中一个blog id=1解除关系
1 | 复制代码//普通解除关系 |
运行sql:
再看看表情况:
正常情况,解除关系只是删除外键。
使用all-delete-orphan时解除关系:
为user配置文件添加all-delete-orphan
1 | 复制代码 <set name="blogs" **cascade="all-delete-orphan"**> |
执行同样的代码解除关系:
1 | 复制代码//孤儿删除 |
sql的执行情况
数据表变化:
关于inverse(外键维护)
什么是外键维护呢?
就是在两个关联对象中,如果关系发生改变需要修改外键。这么一说感觉这个功能肯定是必备的,要不然这么保证对象之间的关系呢?
在hibernate是根据对象关系来判断是否要维护外键。
这里有两个关键字,对象关系和外键。
什么是对象关系?在hibernate中就是你这个对象A存的有对象B的引用,那么对象A就有对象B的的对象关系。有趣的是,对象关系可以是单向的,即A有B的对象关系,B不一定有A的对象关系。Hibernate是根据对象的对象关系来进行外键处理的。如果两边的对象关系都改变,那么默认hibernate都会进行外键处理(处理两次)。
举个例子
user1有blog1和blog2俩对象关系 、user2有blog 3和blog4俩对象关系
1.现在我们把blog3添加到user1中(对象关系改变)
2.因为这时blog3中的user还是user2,还要把blog3的user换成user1(对象关系改变)
上面两个操作都改变了对象关系,如之前说的,session的缓存和快照不一致了,对于User对象,需要更新外键,对于Blog对象,也需要更新外键。
但是,他们更新的是同一外键,也就是说对同一外键更新了两次,多了一个无意义的操作无疑增加了数据库的压力。
也许有人可能会说,我不执行步骤2不就行了,结果还是正确的,还减少了sql。
但是按照人的思维定式,在不知道的情况还是会按上面两个步骤走,感觉更合理。
所以解决方法就在一方放弃外键维护。并且在多对多的情况下必须有一方需要放弃外键,否者程序无法运行。
本文转载自: 掘金