java - Hibernate 生成重复的 UUID

标签 java linux hibernate

我们有一个 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 在评论中指出的那样,AbstractUUIDGeneratorUUIDHexGenerator 的 Hibernate 实现与 RFC-4122 兼容相去甚远。在我仔细观察之前,我从未真正意识到实现有多么糟糕。

除此之外,就其实现而言,这里问题的根本原因归结为 UUIDHexGenerators使用 InetAddress.getLocalHost() (通过 AbstractUUIDGenerator)得出一个“唯一”值。如果您的主机名的名称查找结果为 127.0.0.1(例如,它在您的 /etc/hosts 文件中),或者如果主机名是“本地”,这就是它将使用的名称。

您有几个选择:

  1. 如果这是一个选项,您可以更新 /etc/hosts 以包含主机名的 LAN IP。不过,您不会使用正确的 UUID(与下一点的最后一部分相同的警告)。

  2. 如果Hibernate的算法不够用,你可以自定义一个IdentifierGenerator并提供更适合您任务的更好的UUID生成算法。我会基于 Java 的内置 UUID ,这是合规的。但是,您可能可以通过扩展 UUIDHexGenerator 来“破解”它并覆盖 protected int getIP() 以返回准确的 IP 地址。这是一个黑客攻击,因为 AbstractUUIDGenerators implementation (您的 getIP() 将不再返回其 IP 实例字段的值)并且因为它仍然不是正确的 UUID。这可能就足够了,但我不推荐它。

  3. 不使用生成器,而是指定手动 ID 分配,并自行生成 UUID。同样,Java 的 UUID 可以在这里为您工作。

  4. 有一个更新的 UUID 生成器策略“uuid2”,它使用 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 ,其中可能包含一些更有帮助的见解和示例。

关于java - Hibernate 生成重复的 UUID,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27110438/

相关文章:

java - JDom 空指针异常

java - Apache HttpComponents EntityUtils 内存泄漏?

java - 检测浏览器区域设置/语言并生成输出

linux - 用于 Linux 开发的 Visual C++ + Docker

hibernate - 外键(FK_ 必须与引用的主键具有相同的列数

java - 在 Eclipse 中更改将单词分配给颜色

c - 项目目录中的 scons 可执行文件 + 共享库

linux - 安装mongodb时找不到sudo服务命令

java - 声明式事务 vs 程序化事务

mysql - Hibernate @JoinColumn insertable=false 不起作用(重复的列名)