Hibernate重复关键问题

时间:2012-04-16 11:16:41

标签: java hibernate

使用Hibernate 4.0我有三个休眠实体:

Song,CoverArt,CoverImage

歌曲代表音乐文件,CoverImage代表图像,CoverArt用于将CoverImages与歌曲联系起来,歌曲可以包含多个封面图像。

Song和CoverArt有一个由Hibernate自动生成的主键。但Cover Image主键是手动完成的,构造为图像数据的MessageDigest。我这样做是因为许多歌曲可以使用相同的图像,我不希望在数据库中多次存储相同图像的单独实例,也因为可以根据数据构建密钥,我可以在数据库中检查文件是否已经存在,如果是这样,则检索它而不是构造一个新的CoverImage。

问题是我的应用程序是多线程的,Hibernate实际上并没有实际向数据库提交内容,因此线程1可以检查封面图像是否已经存在于数据库中,发现它不是并且构造了一个新的Song,CoverArt和CoverImage对象。但是当数据被提交到数据库时,一个单独的线程可能已经添加了一个CoverImage,所以我得到了一个例外,因为我的新CoverImage具有相同的密钥 作为现有的

我正在使用

session.merge(coverImage);

所以我认为会处理这个问题,但似乎没有帮助

2 个答案:

答案 0 :(得分:4)

除了重试失败的事务之外,没有可靠的方法来处理这种情况。

因此,如果由于CoverImage的主键上的约束违规而导致事务回滚,则应该在假设CoverImage已存在的情况下重试该事务。请注意,您需要一个新的Session来执行此操作,因为Hibernate异常是无法恢复的。

merge()无法处理此问题,因为它的原因在于事务隔离语义更深层次。在现代基于MVCC的DBMS中,每个事务都会看到自己的数据库快照。因此,并发事务可以对其快照进行冲突更改(尽管它们不能对同一记录进行更改,因此这些更改必须是不相交的),并且这种冲突只能在提交期间由DBMS检测到,并且只有在它导致约束时才会检测到违规,如你的情况(没有约束冲突将被忽视,请参阅write skew anomaly)。

由于merge()在事务内部工作,因此无法看到其他事务在其快照中执行的操作,因此无法解决此问题。

答案 1 :(得分:1)

另一个选择是使用coverImages包装。例如。 CoverImageWrapper。 CoverImageWrapper有自己的密钥 - 基于uuId,而不是messageDigest。这个类与CoverImage一对一链接。

在存储到数据库中时,这个CoverImageWrapper键总是由应用程序生成,所以这样你就拥有了所有三个键(Song,CoverArt和CoverImageWrapper) - 由应用程序生成,它在所有线程中都是唯一的。因此,这样可以避免重复键异常。