2012-04-16 55 views
4

使用Hibernate 4.0我有三个休眠实体:重复使用Hibernate的关键问题

歌,封面作品,CoverImage

歌代表的音乐文件,CoverImage代表图像和封面作品用于涉及CoverImages歌曲,一歌曲可以包含多个封面图片。

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

问题是我的应用程序是多线程的,Hibernate实际上并没有将事情直接提交给数据库,所以线程1可能会检查coverimage是否已经在数据库中,发现它并没有构建新的歌曲,CoverArt和CoverImage对象。但是,数据被提交到CoverImage可能是由一个单独的线程加入数据库的时间,所以我得到一个例外,因为我的新CoverImage使用具有相同的密钥 作为现有

session.merge(coverImage); 

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

+0

添加更多代码片段。 – 2012-04-16 11:40:24

回答

4

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

因此,如果由于主键CoverImage上的约束违规而导致事务回滚,则应重试该事务,假定CoverImage已存在。请注意,你需要一个新的Session来做到这一点,因为Hibernate异常是不可恢复的。

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

由于merge()在事务内部工作,所以无法看到其他事务在其快照中做了什么,因此无法解决此问题。

+1

+1。请注意,您可以使用非常短的事务来获取或创建CoverImage,然后重试此短事务,而不是重试整个全局事务。唯一的缺点是您可以成功创建CoverImage并使全局事务失败,这将导致CoverImage未链接到任何CoverArt,但我不认为这会是一个问题。 – 2012-04-16 13:19:51

+0

是的我认为创建单独的txn只是为了添加封面图像可能是最有意义的,但我不明白为什么调用merge()不处理问题 – 2012-04-16 14:39:54

+0

@ijabz:已更新。 – axtavt 2012-04-16 15:00:52

1

另一种选择是使用wrapper for coverImages。例如。 CoverImageWrapper。 CoverImageWrapper除了messageDigest以外,还有其自己的基于uuId的密钥。这个类与CoverImage一对一连接。

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

+0

但是,你最终会得到多个封面图像行,为同一个图像不会吗? – 2012-04-16 15:31:35

+0

我想,你可以在CoverImageWrapper和CoverImage类之间建立多对一的映射关系。 Hibernate会在存储这样的协议时保持谨慎。那里不会重复CoverImage实例。您需要在coverImage类中覆盖equals和hashcode。 – zaga250 2012-04-16 15:40:16

+0

好吧,但我真的想让事情变得简单,这对我来说太复杂了,谢谢。 – 2012-04-16 15:56:38