在 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()
来避免麻烦。