4

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

歌曲,封面艺术,封面图片

Songs 代表音乐文件,CoverImage 代表一张图片,CoverArt 用于将 CoverImages 与 Songs 关联起来,一首歌曲可以包含多个封面图片。

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

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

我正在使用

session.merge(coverImage);

所以我认为这可以解决这个问题,但它似乎没有帮助

4

2 回答 2

4

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

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

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

由于merge()在事务内部工作,它无法看到其他事务在其快照中做了什么,因此无法克服这个问题。

于 2012-04-16T13:16:39.033 回答
1

另一种选择是对coverImages 使用包装器。例如。封面图像包装。CoverImageWrapper 有自己的密钥 - 基于 uuId,而不是 messageDigest。此类与 CoverImage 一对一链接。

在存储到数据库时,此 CoverImageWrapper 键始终由应用程序生成,因此您拥有所有三个键(Song、CoverArt 和 CoverImageWrapper) - 由应用程序生成,并且在所有线程中都是唯一的。因此,这种方式可以避免重复键异常。

于 2012-04-16T15:24:07.660 回答