Hibernate 中,NonUniqueObjectException 是一个常见的运行时异常,它的全名是:

org.hibernate.NonUniqueObjectException

为什么会抛出 NonUniqueObjectException?

这个异常的含义是:

Hibernate 的同一个 Session 中已经存在一个具有相同主键的持久化对象,但你又试图将另一个不同的对象与这个主键关联。

通俗点说:Hibernate 的 一级缓存(Session 缓存)中,已经存在一个主键为 X 的对象了,你又手动创建了一个主键也是 X 的新对象,并试图保存或更新它,Hibernate 不知道该听谁的,就报错了。

举个例子:

Session session = sessionFactory.openSession();
session.beginTransaction();

User user1 = session.get(User.class, 1); // 从数据库中加载了 ID 为 1 的用户

User user2 = new User(); // 另一个新对象
user2.setId(1); // 设置相同的主键
user2.setName("New Name");

session.saveOrUpdate(user2); // 这里就会抛出 NonUniqueObjectException

session.getTransaction().commit();
session.close();

解释: user1 已经在 Session 缓存中了,而你又把 user2(主键也为 1)传进来更新,Hibernate 不知道你想用哪个对象,所以直接抛错。

如何解决?

以下是几种常见的解决方法:

方法 1:使用 session.merge()

session.merge(user2);
  • merge() 会把你的 user2 的状态合并到已有的 user1 实例中。
  • 不会抛出异常。
  • 适合“拿一个 detached 对象回来更新”的场景。

方法 2:避免重复加载主键相同对象

在使用前检查 Session 是否已加载对象,避免重复手动 new 同一个主键的对象。

User existing = session.get(User.class, 1);
if (existing != null) {
    existing.setName("New Name");
    session.update(existing);
}

方法 3:先 evict 原对象,再 save/update

session.evict(user1); // 把缓存中的 user1 移除
session.saveOrUpdate(user2); // 再更新 user2 就不会报错了

方法 4:关闭当前 Session,重新打开新的 Session 操作

如果你用的是 detached 对象(如从别的层传下来的对象),可以开启新 Session 去更新。

总结:

原因Hibernate 一级缓存中已经有同主键对象,导致冲突
异常名NonUniqueObjectException
常见解决方式merge()、避免重复对象、evict()、重新开启 Session

如果你是从前端或别的系统传进来的对象想要更新,推荐使用 merge() 来避免麻烦。