【问题标题】:Hibernate updatable = false UUID field is updatedHibernate 可更新 = false UUID 字段已更新
【发布时间】:2026-02-04 11:10:02
【问题描述】:

我正在将 Spring Data JPA 与 Hibernate 一起使用,但 @Column 注释上的 updatable=false 属性遇到问题。

我的所有@Entity 对象都有一个基类,其 UUID 定义如下:

@MappedSuperclass
@Getter @Setter @EqualsAndHashCode(of= {"uuid"})
public abstract class AbstractEntity implements Persistable<Long> {

    @Id
    @GeneratedValue(strategy = AUTO)
    @Column(unique = true, updatable = false)
    private Long id;

    @Column(unique = true, updatable = false)
    private UUID uuid = UUID.randomUUID();

}

注意updatable = false 注释。

为了验证 UUID 字段实际上是不可更新的,我编写了这个测试:

@Test
public void testChangeUUID() {

    User user = userRepo.findOne(1L);

    assertNotNull(user);
    assertEquals(USER_UUID, user.getUuid().toString());

    final UUID newUuid = UUID.randomUUID();

    user.setUuid(newUuid);

    user = userRepo.save(user);

    assertEquals(newUuid, user.getUuid());

    User user2 = userRepo.findOne(1L);
    assertNotNull(user2);
    assertEquals("UUID should not have changed", USER_UUID, user2.getUuid().toString());
}

我实际上希望在调用userRepo.save(user) 时会引发异常,但这并没有发生。相反,最终的 assertEquals() 失败了,这意味着 UUID 实际上正在更新。

这是预期的行为吗?有什么方法可以防止 UUID 被更改?

【问题讨论】:

  • 它没有更新,你的测试有缺陷。也不例外,在保存/更新对象时,该字段将被静默忽略。数据库中没有任何变化。您的测试的问题是所有事情都发生在一个事务中,因此只有一个一级缓存(即EntityManager)。 useruser2 对象是同一个对象(user == user2 应该返回 true)。您的最后一个断言是错误的,因为您更改了 User 对象的内存表示。不是数据库之一。您想使用普通查询来测试它。

标签: java spring hibernate jpa spring-data-jpa


【解决方案1】:

根据documentationupdatable 属性决定该列是否是更新语句的一部分。这意味着 Hibernate 在向数据库发送更新时会忽略它。因此,内存状态和数据库状态会有所不同。

要验证这一点,请在调用 User user2 = userRepo.findOne(1L) 之前尝试清除会话(驱逐)

【讨论】:

  • 我知道第一部分,但没有意识到 Hibernate 正在缓存对象。我在测试中的findOne() 之前添加了entityManager.clear();,现在它准确通过了。感谢您直接提示正确。
  • 奇怪的是,日志中有另一个 sql 更新似乎将原始值放回 updateable=false 字段。这真的有必要吗..
【解决方案2】:

虽然最初的问题已经得到解答,但我想强调一点对于刚接触 Hibernate 的人,因为它可能有助于避免一些混淆。

@Column 注释的javadoc of Hibernate 5.4 表示其可选元素可更新:

(可选)该列是否包含在由持久性提供程序生成的 SQL UPDATE 语句中

如果您使用 HQL 发出更新语句或使用 CriteriaUpdate,针对您已使用 @Column(updatable = false) 注释的字段,您的更新语句将被执行 .

当您使用 Hibernate 的 update 或 JPA 的 merge 方法时,@Column(updatable = false) 有效。

不会抛出异常(不像例如:@Column(nullable= false),因为它创建了一个约束...),但生成的更新语句将不包括标记的字段。

如需更多信息,请务必熟悉 JPA 的实体生命周期状态以及管理它们的方法,如下图所示。

我强烈建议您阅读此article,它详细解释了这一点,并且是所附图片的来源

【讨论】: