org.hibernate.NonUniqueObjectException 个人的解决办法

当使用Hibernatesave时,若session中已存在相同OID的对象,则会引发NonUniqueObjectException异常。本文通过示例代码说明了异常产生的原因,并提供了两种解决方案:一是更新现有对象属性后再保存;二是使用Hibernate3的merge方法。
Hibernate save 在 session 中已存在相同 OID(主键) 的对象,会出现异常,详细内容如下:

Exception in thread "main" org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.unmi.LoanDetail#1]
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:168)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:121)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:187)
at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:33)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:172)
at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:27)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:535)
at org.hibernate.impl.SessionImpl.save(SessionImpl.java:523)
at org.hibernate.impl.SessionImpl.save(SessionImpl.java:519)
at com.unmi.Test.main(Test.java:44)
重现以上错误的代码如下(去除了事物控制的代码行):

view sourceprint?01.Session session = HibernateSessionFactory.getSession();



// 加载OID为1L的对象,会被放在session缓存中

LoanDetail detail = (LoanDetail)session.get(LoanDetail.class,1L);



// new 一个OID也为1L的临时对象
LoanDetail newDetail = new LoanDetail(1L);
newDetail.setSubjectId(1000L);



// 持久化一个临时对象,试图放在session的缓存中,因OID冲突出现异常

session.save(newDetail);

// 执行saveOrUpdate同样会出现以上的异常

// session.saveOrUpdate(newDetail);
解决方法:
1) 如果用的 hibernate 2, 需要在get/load/query到持久化对象,赋上新的属性值,再 save/update/saveOrupdate.
对以上代码就是:不能 new 一个session中已存在OID的对象,直接
detail.setSubjectId(1000L);
session.save(detail);
session.save()一个持久化对象时,会转化成update调用。

2) 使用 hibernate 3 的 merge 方法. session.merge(newDetail)即可,它会在 session 缓存中找到持久化对象,把新对象的属性赋过去,再保存原session中的持久化对象。
如果在session或数据库中没有的对象,用merge方法的话,它也能够帮你把记录 insert 到表中,相当于 save 方法。

上面是一个简单的例子,实际业务中可能是经过一番复杂的操作后自己也很难搞清楚 new 的一个新对象在 session/数据库中是否已存在。所以第一种方法你需要清楚你的每一个对象状态,第二种方法在 hibernate 3 中就比较通用一些。

附 hibernate javadoc 对 session.merge() 方法的注释:
Copy the state of the given object onto the persistent object with the same identifier. If there is no persistent instance currently associated with the session, it will be loaded. Return the persistent instance. If the given instance is unsaved, save a copy of and return it as a newly persistent instance. The given instance does not become associated with the session. This operation cascades to associated instances if the association is mapped with cascade="merge".

The semantics of this method are defined by JSR-220.


[size=x-large]原创[/size][url]http://www.blogjava.net/Unmi/archive/2007/08/10/135771.html[/url]
<think>首先,用户的问题是:解决org.hibernate.NonUniqueObjectException异常的方法。异常消息是:“a different object with the same identifier value was already associated with the session: [com.hg.entity.RmsPackageClinicalDrugsInfo#2041]”。 关键点是:Hibernate抛出的这个异常,表明在同一个Session中,有两个不同的对象拥有相同的ID(这里是2041)。 现在,回顾用户提供的引用: - 引用[1]: 类似问题描述。 - 引用[2]: 更新对象时出现错误,原因分析。 - 引用[3]: 保存时三个属性冲突,导致错误。 - 引用[4]: Session中出现多个不同状态的对象,保存时报错。 - 引用[5]: 新创建的实体,ID默认为0,Hibernate认为它们是同一个实体的多个对象。 特别地,引用[5]解释了原因:新建的实体ID默认为0,导致Hibernate将它们视为同一个实体。 在用户的具体案例中,对象是com.hg.entity.RmsPackageClinicalDrugsInfo#2041,ID是2041,而不是0。所以不是新创建的问题,而是现有对象的冲突。 常见原因总结: 1. **Session中存在多个相同ID的对象**:在同一个Hibernate Session中,加载了一个对象后,又试图加载或创建另一个具有相同ID的对象。 2. **对象状态不一致**:可能是游离对象被重新附加时冲突。 3. **并发问题**:多个线程操作同一个对象。 4. **ID生成问题**:对于新对象,ID重复。 在用户案例中,ID是2041,不是0,所以可能是更新或合并现有对象时的问题。 解决方法: - **合并对象**:使用Session.merge()而不是Session.save()或Session.update()来合并游离对象的状态。 - **检查对象状态**:确保在Session中只有一个对象实例。 - **管理Session生命周期**:使用较短的生命周期Session,或在更新前清除Session状态。 - **检查ID生成**:如果是新对象,确保ID唯一。 从引用中提取关键方案: - 引用[2]提到原因:可能是试图保存一个对象,而Session中已有另一个相同ID的对象。 - 引用[5]建议:对于新对象,设置唯一ID。 在用户案例中,对象是com.hg.entity.RmsPackageClinicalDrugsInfo#2041,ID是2041,所以可能是在加载对象后,又在代码中创建了一个新对象尝试保存。 标准解决方案: 1. **使用merge()**:当你有游离对象时,使用Session.merge()来合并变化。 2. **清除Session**:如果Session已有一个对象,清除它或使用evict()方法移除旧对象。 3. **确保唯一实例**:避免在代码中创建多个具有相同ID的对象实例。 4. **检查事务管理**:确保Session在适当的时间关闭。 回答结构要清晰,逐步解决问题: - 解释异常原因。 - 提供解决方法。 - 给出代码示例(如果合适)。 - 生成相关问题。 使用中文回答。 最后,生成相关问题。 系统指令: - 行内数学表达式用$...$,但这里是Hibernate问题,可能不需要数学。 - 独立公式用$$...$$,但可能不涉及。 - LaTeX语法正确。 - 中文回答。 - 生成相关问题。 - 引用时添加引用标识。 在回答中,参考用户提供的引用,末尾添加引用标识。 用户上一次问题和我的回答:用户之前可能问过类似问题,但在这个对话中,这是第一个问题。历史中说“参考用户上一次的问题和你的回答”,但这里没有之前的消息。所以基于当前引用。 组织回答: 1. **分析原因**:解释为什么出现这个异常。 2. **解决方法**:列出具体步骤。 3. **代码示例**:如果需要,提供Java/Hibernate代码。 4. **总结**。 5. **生成相关问题**。 参考引用: - [^2] 提到了更新对象的原因。 - [^5] 提到了新对象ID问题。 在用户案例中,ID是2041,不是0,所以可能不是新建对象的问题。 可能原因:在Session中,已经从数据库加载了ID为2041的对象,然后又尝试保存另一个具有相同ID的对象。 解决方法: - 使用merge()来合并。 - 或者,先检查对象是否已存在。 </think>### 解决 org.hibernate.NonUniqueObjectException 异常的方法 在 Hibernate 中,`org.hibernate.NonUniqueObjectException` 异常通常发生在同一个会话(Session)中试图关联多个具有相同标识符(ID)的对象时。具体到您的案例,异常消息 `a different object with the same identifier value was already associated with the session: [com.hg.entity.RmsPackageClinicalDrugsInfo#2041]` 表明:在同一个 Session 中,Hibernate 已经关联了一个 ID 为 2041 的 `RmsPackageClinicalDrugsInfo` 对象,而您的代码又试图加载或保存另一个具有相同 ID 的不同对象,导致冲突[^2][^3][^5]。这通常发生在对象状态管理不当的场景中,例如游离对象(detached object)被重新附加时,或代码中创建了多个相同 ID 的实例。 下面,我将逐步解释根本原因并提供详细的解决方法,确保代码健壮性和数据一致性。解决方案基于 Hibernate 的最佳实践,并参考了您提供的引用信息。 #### 步骤 1: 理解异常原因 - **核心问题**:Hibernate 要求在同一个 Session 中,每个对象 ID 必须是唯一的。如果 Session 中已有一个 ID 为 2041 的对象(例如通过 `session.get()` 或查询加载),而您又试图: - 保存一个新对象(但其 ID 被设置为 2041)。 - 或附加一个游离对象(例如从外部传入的 DTO 或反序列化对象,其 ID 与 Session 中现有对象冲突)。 - **常见场景**: - **游离对象冲突**:当您从 Session 外部获取一个对象(如从缓存或前端传入),并尝试用 `session.save()` 或 `session.update()` 保存时,如果 Session 中已有相同 ID 的对象,就会报错[^2][^4]。 - **ID 重复问题**:新建对象时,如果 ID 手动设置为现有值(如 2041),或 ID 生成策略不当(如 ID 默认为 0 导致多个新对象冲突),Hibernate 会错误地将它们视为同一实体的不同实例[^5]。 - **并发操作**:在多线程环境中,多个线程可能同时操作相同 ID 的对象。 - **您的案例分析**:对象类型为 `com.hg.entity.RmsPackageClinicalDrugsInfo`,ID 为 2041。这表明它不是全新对象(ID 非 0),而是现有实体。可能原因是: - 您在 Session 中加载了 ID 2041 的对象后,又在代码中创建或传递了另一个具有相同 ID 的游离对象。 - 或者,在事务中重复保存了同一对象的不同实例。 #### 步骤 2: 解决方法 以下是针对性解决方案,从简单到高级排列。优先使用 **方法 1** 或 **方法 2**,它们覆盖 90% 的常见情况。 ##### 方法 1: 使用 `merge()` 代替 `save()` 或 `update()` - **适用场景**:当您处理游离对象(如从数据库加载后又被分离的对象)时。 - **原理**:`merge()` 方法会检查 Session 中是否已有相同 ID 的对象。如果有,它会将游离对象的状态合并到现有对象;如果没有,它会自动保存新对象。这避免了 ID 冲突[^2][^5]。 - **代码示例**: ```java // 错误示例:使用 save() 或 update() 可能导致冲突 // session.save(existingObject); // 或 session.update(existingObject); // 正确示例:使用 merge() 确保唯一性 Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); try { // 假设 detachedObject 是一个游离对象,ID 为 2041 RmsPackageClinicalDrugsInfo mergedObject = (RmsPackageClinicalDrugsInfo) session.merge(detachedObject); tx.commit(); } catch (Exception e) { if (tx != null) tx.rollback(); throw e; } finally { session.close(); // 确保 Session 关闭 } ``` - **关键点**:`merge()` 返回一个托管对象(managed object),您应在后续代码中使用它,而不是原始游离对象。 ##### 方法 2: 清除 Session 中的冲突对象 - **适用场景**:如果 Session 中已存在 ID 2041 的对象,但您需要强制替换为新对象。 - **原理**:使用 `evict()` 或 `clear()` 方法移除 Session 中的旧对象,然后附加新对象。但要注意,这可能导致未提交的数据丢失,建议在事务开始时使用[^4]。 - **代码示例**: ```java Session session = sessionFactory.getCurrentSession(); Transaction tx = session.beginTransaction(); try { // 检查 Session 中是否已有 ID 2041 的对象 RmsPackageClinicalDrugsInfo existingObject = session.get(RmsPackageClinicalDrugsInfo.class, 2041); if (existingObject != null) { session.evict(existingObject); // 移除旧对象 } // 现在附加新对象(ID 为 2041) RmsPackageClinicalDrugsInfo newObject = ...; // 您的对象实例 session.saveOrUpdate(newObject); // saveOrUpdate 可处理游离状态 tx.commit(); } catch (Exception e) { if (tx != null) tx.rollback(); throw e; } ``` - **关键点**:`evict()` 只移除特定对象,`clear()` 移除所有 Session 对象。适用于调试或特定更新逻辑。 ##### 方法 3: 确保对象唯一性 - **适用场景**:预防性检查,避免代码创建多个相同 ID 的实例。 - **原理**:在业务逻辑层验证对象 ID 的唯一性,特别是当对象来自外部(如 API 请求)。 - **代码示例**: ```java // 在保存前检查对象是否已在 Session 中 Session session = sessionFactory.openSession(); try { RmsPackageClinicalDrugsInfo inputObject = ...; // 传入的对象 boolean existsInSession = session.contains(inputObject); // 检查对象是否在 Session if (!existsInSession) { // 如果不在 Session,尝试从数据库加载或合并 session.merge(inputObject); } else { // 已在 Session 中,直接操作现有对象 // 例如:inputObject.setProperty(value); } } finally { session.close(); } ``` ##### 方法 4: 优化 Session 和事务管理 - **适用场景**:高并发或复杂事务环境。 - **原理**:缩短 Session 生命周期,每次操作使用新 Session,或使用 "session-per-request" 模式。这减少了对象缓存冲突[^4][^5]。 - **配置建议**: - 在 Spring 框架中,使用 `@Transactional` 注解管理事务,确保 Session 在请求结束时关闭。 - 避免在 Session 中长期持有对象(如缓存实体)。 - **代码示例**(Spring Boot 上下文): ```java @Service public class RmsPackageClinicalDrugsInfoService { @Autowired private SessionFactory sessionFactory; @Transactional // 确保事务边界清晰 public void saveOrUpdateDrugsInfo(RmsPackageClinicalDrugsInfo drugsInfo) { Session session = sessionFactory.getCurrentSession(); session.merge(drugsInfo); // 优先用 merge } } ``` ##### 附加检查和预防措施 - **验证 ID 生成策略**:确保实体类 `RmsPackageClinicalDrugsInfo` 的 ID 生成正确。如果 ID 是手动设置或来自外部,检查是否重复。例如: ```java @Entity public class RmsPackageClinicalDrugsInfo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // 推荐使用数据库自增 private Long id; // 确保 ID 自动生成 // 其他字段... } ``` - **日志调试**:在开发环境中,启用 Hibernate 日志(`hibernate.show_sql=true`)追踪对象加载过程。 - **检查代码逻辑**:审查您的业务逻辑,特别是涉及对象创建和传递的部分。例如: - 避免在多处实例化相同 ID 的对象。 - 确认是否在循环中错误创建对象(如引用[^5]所述,ID 默认为 0 的问题)。 #### 总结 `org.hibernate.NonUniqueObjectException` 异常的根本原因是同一个 Session 中有多个相同 ID 的对象实例。解决方法集中在: - 使用 `merge()` 代替 `save()` 或 `update()` 来合并对象状态。 - 管理 Session 生命周期,避免长期持有对象。 - 确保 ID 唯一性和自动生成策略正确。 实施上述方法后,异常通常能快速解决。如果问题 persist,检查数据库或应用架构(如是否使用了分布式缓存冲突)[^1][^3][^5]。 ### 相关问题 1. 如何在 Hibernate 中正确管理对象状态(托管、游离、瞬态)以避免冲突? 2. Hibernate 的 `merge()` 和 `update()` 方法有什么区别,何时使用它们? 3. 在 Spring Boot 中如何配置 Hibernate Session 工厂来防止 `NonUniqueObjectException`?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值