使用Hibernate框架实现复杂的关联关系时容易出现的异常

关系数据库系统本身就比较复杂,加上Hibernate的O/R映射层,复杂度加重了,很容易出现问题,本人将最近遇到的问题和解决方法做一个总结,整理在下面的一系列文章中

本文是第四篇,总结一下在复杂的关联关系环境中,容易出现的异常和解决方法。

Hibernate将表进行对象封装,在其帮助下,在面向对象编程环境中,程序员会逐渐忘掉数据库的存在。但是,如果数据库结构比较复杂,忘记数据库的存在经常引起很多异常。此文,我们对下面几个异常的处理方法进行总结:
异常1:

object references an unsaved transient instance -
save the transient instance before flushing: xxx类

异常2:
not-null property references a null or transient value:: xxx类引用xxx属性

这两个异常最容易出现在多个表之间有外键约束的情况下。例如,表A, B, C之间,C有外键分别引用A和B。A到C是1:n关系,在A的hbm文件中,用一个Set存储所有相关的C;B到C也是1:n关系,在B中也有一个Set存储相关的C。如果程序中产生了这三个类的对象,而且在内存中对象的关联关系已经成功建立了,假设C的对象先进行持久化,必然引起上述异常。

读者也许会说:这个问题太显而易见了,自然会避免的。事实上,当长时间忘记数据库的存在的时候,这个问题会时不时的出现,尤其是使用了Hibernate级联持久化技术后,很容易犯错误。我们在在Hibernate中正确实现关联关系中的级联操作(cascading)一文中描写了一种理想场景,在理想情况下,我们确定了级联树的根,只对其进行存储操作,Hibernate完成级联操作。在复杂的现实面前,我们需要区别对待

针对上述情形,我更喜欢下面的编程模式

  1. A和B的对象创建后即进行持久化操作
  2. 创建C对象后,设定了C到A和B的引用关系后,将其持久化
  3. 编写各种复杂的业务逻辑,期间可能对A, B, C的对象的内容和关系进行了修改
  4. 执行Hibernate session.flush()操作,将内存中的修改同步到数据库
上述模式也许增加了对数据库的操作,导致性能的下降,但是,至少可以避免上述异常的发生。