我们有一个 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 在彼此的 256 毫秒内启动)。
进一步调查显示,前八个字符生成的值 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
...
我的问题是:
- Hibernate 看到的 IP 地址怎么可能是 127.0.0.1 而不是 10.191.8.50?
- 我们可以做些什么来防止在已部署的系统上出现这种情况?
最佳答案
正如@thatotherguy 在评论中指出的那样,AbstractUUIDGenerator
和UUIDHexGenerator
的 Hibernate 实现与 RFC-4122 兼容相去甚远。在我仔细观察之前,我从未真正意识到实现有多么糟糕。
除此之外,就其实现而言,这里问题的根本原因归结为 UUIDHexGenerator
s使用 InetAddress.getLocalHost()
(通过 AbstractUUIDGenerator
)得出一个“唯一”值。如果您的主机名的名称查找结果为 127.0.0.1(例如,它在您的 /etc/hosts
文件中),或者如果主机名是“本地”,这就是它将使用的名称。
您有几个选择:
如果这是一个选项,您可以更新
/etc/hosts
以包含主机名的 LAN IP。不过,您不会使用正确的 UUID(与下一点的最后一部分相同的警告)。如果Hibernate的算法不够用,你可以自定义一个
IdentifierGenerator
并提供更适合您任务的更好的UUID生成算法。我会基于 Java 的内置UUID
,这是合规的。但是,您可能可以通过扩展UUIDHexGenerator
来“破解”它并覆盖protected int getIP()
以返回准确的 IP 地址。这是一个黑客攻击,因为AbstractUUIDGenerator
s implementation (您的getIP()
将不再返回其IP
实例字段的值)并且因为它仍然不是正确的 UUID。这可能就足够了,但我不推荐它。不使用生成器,而是指定手动 ID 分配,并自行生成 UUID。同样,Java 的
UUID
可以在这里为您工作。有一个更新的 UUID 生成器策略“uuid2”,它使用
UUIDGenerator
.它在 3.6 中是新的,在 3.3.2 中不可用。它的来源is available .我以前没有使用过这个策略,不能代表它;然而,正如 Andrew Stein 在下面的评论中所观察到的,对来源的检查表明它 provides a strategy built around Java'sUUID
,这可能是一个不错的选择,而且肯定比旧的AbstractUUIDGenerator
派生的变体更好。
如果选项 1 适合您,它是最简单的快速解决方案,但可能存在维护/部署问题,而且不会真正生成格式正确的 UUID。从长远来看,使用 UUID
的选项 2(或具有适当策略的选项 4)可能是最正确的。
有一个article describing various UUID assignment strategies for Hibernate ,其中可能包含一些更有帮助的见解和示例。
关于java - Hibernate 生成重复的 UUID,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27110438/