我们有一个6节点的Red Hat 4.4.7/Linux 2.6.32网络,每个节点运行一个Java应用程序,该应用程序使用Hibernate 3.3.2.GA在中央Oracle数据库中创建记录。
我们遇到了一个问题,其中Hibernate生成重复的UUID。
所讨论的Java类定义如下:
@Entity
@Table(name = "X_Y")
@GenericGenerator(name = "x-y-uuid", strategy = "uuid")
public class XY implements ... {
@Id
@Column(name = "X_Y_ID")
@GeneratedValue(generator = "x-y-uuid")
private String id;
...
}
使用我们已经成功使用了一段时间的这个定义,我们遇到了重复的X_Y_ID密钥的问题。我们禁用了X_Y_ID的唯一约束,然后重新运行该过程。同时,我们开始在代码以及Hibernate代码中挖掘可能的错误。阅读Hibernate的
UUIDHexGenerator
,似乎UUID的前8个字符是基于机器IP地址的,而后8个字符是基于JVM启动时间的。完成对X_Y_ID禁用唯一约束的过程后,我们对所得的UUID进行了一些分析。我们发现实际上有59个重复的X_Y_ID值。
令我们惊讶的是,查询:
select SUBSTR(X_Y_ID,1,8), COUNT(*)
from X_Y
group by SUBSTR(X_Y_ID,1,8)
表示所有6台机器的前8个字符都相同。查询:
select SUBSTR(X_Y_ID,9,8), COUNT(*)
from X_Y
group by SUBSTR(X_Y_ID,9,8)
给了
"49d99de6" 2148309
"49d99e3c" 2044966
"49d99def" 2228095
"49d99df2" 2091068
"49d99dee" 4110661
如您所见,有5行,最后一行大约是行数的两倍。这本身并不奇怪。 (这意味着在两台不同机器上的JVM彼此之间的启动时间为256ms)。
进一步的调查显示,为前八个字符
ff808081
生成的值对应于IP地址127.0.0.1(本地主机)。在其中一台计算机上运行
ifconfig
可以给出(例如):eth0 Link encap:Ethernet HWaddr 00:50:56:81:2C:20
inet addr:10.191.8.50 Bcast:10.191.63.255 Mask:255.255.192.0
inet6 addr: fe80::250:56ff:fe81:2c20/64 Scope:Link
...
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
...
我的问题是:
最佳答案
正如@thatotherguy在评论中指出的那样,AbstractUUIDGenerator
和UUIDHexGenerator
的Hibernate实现与RFC-4122兼容还很遥远。在仔细研究之前,我从未真正意识到过实现的糟糕程度。
顺便说一句,以其实现本身为基础,此处问题的根本原因归结为 UUIDHexGenerator
s使用 InetAddress.getLocalHost()
(通过AbstractUUIDGenerator
)来得出“唯一”值。如果对主机名的名称查找结果为127.0.0.1(例如,您的/etc/hosts
文件中的主机名),或者主机名是“local”,则将使用它。
您有几种选择:
/etc/hosts
以包含主机名的LAN IP。不过,您将不会使用正确的UUID(与下一点的最后一部分相同)。 IdentifierGenerator
并提供更适合您的任务的更好的UUID生成算法。我将以Java内置的 UUID
为基础,它是兼容的。但是,您可以通过扩展 UUIDHexGenerator
并覆盖protected int getIP()
以返回准确的IP地址来“破解”它。这是由于 AbstractUUIDGenerator
s implementation(您的getIP()
将不再返回其IP
实例字段的值)而引起的黑客攻击,因为它仍然不是正确的UUID。可能就足够了,但我不建议这样做。 UUID
在这里可以为您工作。 UUIDGenerator
。它是3.6中的新功能,而3.3.2中不可用。的来源is available。我以前没有使用过这种策略,也不能为它辩护。但是,正如安德鲁·斯坦(Andrew Stein)在下面的评论中观察到的那样,对源的检查显示它为provides a strategy built around Java's UUID
,这很可能是一个不错的选择,并且肯定比旧的AbstractUUIDGenerator
派生的变体更好。 如果适用,选项1是最简单的快速修复方法,但是可能存在维护/部署问题,并且实际上不会生成格式正确的UUID。从长远来看,使用
UUID
的选项2(或具有适当策略的选项4)可能是最正确的。有一个article describing various UUID assignment strategies for Hibernate,其中可能包含一些更有用的见解和示例。