Hibernate 5.6克隆列时触发EntityExistsException异常求助
问题场景
使用Hibernate 5.6,数据结构为Profile -> Groups -> Columns -> ColumnProperties,需要克隆某个Profile的Column并附加到指定列组中,执行时出现如下错误:
10:58:39,793 ERROR
[com.myApp.core.rmgt.profile.service.RmgtProfileServiceImpl] (default
task-2) javax.persistence.EntityExistsException: A different object
with the same identifier value was already associated with the session
:
[com.myapp.core.rmgt.profile.business.object.RmgtColumnPropertyBVOImpl#124320]
映射文件
<hibernate-mapping> <class name="com.myapp.core.rmgt.profile.business.object.RmgtProfileBVOImpl" table="rmgt_t_profile" proxy="com.myapp.core.rmgt.profile.common.business.object.RmgtProfileBVO"> <id name="rowguid" column="rowguid" type="java.lang.Long"> <generator class="sequence"> <param name="sequence_name">rmgt_t_profile_rowguid_seq</param> </generator> </id> ... <set name="groups" table="rmgt_t_group" lazy="false" cascade="all-delete-orphan" order-by="rmgtg_group_order asc"> <key column="rmgtg_fk_profile_id" not-null="true"/> <one-to-many class="com.myapp.core.rmgt.profile.business.object.RmgtGroupBVOImpl"/> </set> </class> </hibernate-mapping> <hibernate-mapping> <class name="com.myapp.core.rmgt.profile.business.object.RmgtGroupBVOImpl" table="rmgt_t_group" proxy="com.myapp.core.rmgt.profile.common.business.object.RmgtGroupBVO"> <id name="rowguid" column="rowguid" type="java.lang.Long"> <generator class="sequence"> <param name="sequence_name">rmgt_t_group_rowguid_seq</param> </generator> </id> ... <set name="columns" table="rmgt_t_column" lazy="false" cascade="all-delete-orphan" order-by="rmgtc_column_order asc"> <key column="rmgtc_fk_group_id" not-null="true"/> <one-to-many class="com.myapp.core.rmgt.profile.business.object.RmgtColumnBVOImpl"/> </set> </class> </hibernate-mapping> <hibernate-mapping> <class name="com.myapp.core.rmgt.profile.business.object.RmgtColumnBVOImpl" table="rmgt_t_column" proxy="com.myapp.core.rmgt.profile.common.business.object.RmgtColumnBVO"> <id name="rowguid" column="rowguid" type="java.lang.Long"> <generator class="sequence"> <param name="sequence_name">rmgt_t_column_rowguid_seq</param> </generator> </id> ... <set name="columnProperties" table="rmgt_t_column_property" lazy="false" cascade="all-delete-orphan" order-by="rmgtcp_order asc"> <key column="rmgtcp_fk_column_id" not-null="true"/> <one-to-many class="com.myapp.core.rmgt.profile.business.object.RmgtColumnPropertyBVOImpl"/> </set> </class> </hibernate-mapping> <hibernate-mapping> <class name="com.myapp.core.rmgt.profile.business.object.RmgtColumnPropertyBVOImpl" table="rmgt_t_column_property" proxy="com.myapp.core.rmgt.profile.common.business.object.RmgtColumnPropertyBVO"> <id name="rowguid" column="rowguid" type="java.lang.Long"> <generator class="sequence"> <param name="sequence_name">rmgt_t_column_property_rowguid_seq</param> </generator> </id> <property name="key" type="java.lang.String" column="rmgtcp_key" not-null="true" /> <property name="value" type="java.lang.String" column="rmgtcp_value" not-null="true" /> <property name="order" type="java.lang.Integer" column="rmgtcp_order" not-null="true" /> </class> </hibernate-mapping>
原操作步骤
- 获取目标Column并evict其所有ColumnProperties,克隆Column并清空rowguid:
RmgtColumnBVO column = columnService.findLazyById(columnId); // Evict all properties to force hibernate write new instances to DB column.getColumnProperties().stream().forEach(x -> profileDAO.getSession().evict(x)); RmgtColumnBVO columnClone = new RmgtColumnBVOImpl(); BeanUtils.copyProperties(column, columnClone); columnClone.setRowguid(null); columnClone.setProfileId(profileId); columnClone.setGroupId(groupId);
- 克隆ColumnProperties并清空其rowguid:
// clone properties Set<RmgtColumnPropertyBVO> propertys = column.getColumnProperties(); Set<RmgtColumnPropertyBVO> propertysClone = new HashSet<RmgtColumnPropertyBVO>(); for (RmgtColumnPropertyBVO property : propertys) { RmgtColumnPropertyBVO propertyClone = new RmgtColumnPropertyBVOImpl(); BeanUtils.copyProperties(property, propertyClone); propertyClone.setRowguid(null); propertysClone.add(propertyClone); } columnClone.setColumnProperties(propertysClone);
- 获取Profile,附加克隆的Column并保存:
RmgtProfileBVO profile = profileService.findById(column.getProfileId()); profile.getGroup(groupId).getColumns().add(columnClone); profileService.saveOrUpdate(profile, object.getCreationUser());
问题原因
当前Hibernate Session中仍存在一个ID为124320的RmgtColumnPropertyBVOImpl对象,即便evict了原属性对象,但原Column对象仍在Session管理范围内,BeanUtils复制对象时可能附带了Hibernate内部的代理状态或引用,导致克隆对象和Session中的原对象产生ID冲突。
解决方法
1. 让原Column完全脱离Session管理
获取原Column后,直接evict整个对象,彻底断开和Session的关联,避免后续操作时Session持有原对象的引用:
RmgtColumnBVO column = columnService.findLazyById(columnId); // 直接evict整个Column对象,包括其关联的属性 profileDAO.getSession().evict(column); RmgtColumnBVO columnClone = new RmgtColumnBVOImpl(); BeanUtils.copyProperties(column, columnClone); // 清空克隆对象的集合引用,避免复用原集合 columnClone.setColumnProperties(new HashSet<>()); columnClone.setRowguid(null); columnClone.setProfileId(profileId); columnClone.setGroupId(groupId);
2. 手动克隆属性,避免BeanUtils复制Hibernate内部状态
不要依赖BeanUtils复制属性对象,手动复制业务属性,确保克隆对象是完全干净的新实例:
Set<RmgtColumnPropertyBVO> propertysClone = new HashSet<>(); for (RmgtColumnPropertyBVO property : column.getColumnProperties()) { RmgtColumnPropertyBVO propertyClone = new RmgtColumnPropertyBVOImpl(); // 手动复制业务属性,跳过Hibernate内部字段 propertyClone.setKey(property.getKey()); propertyClone.setValue(property.getValue()); propertyClone.setOrder(property.getOrder()); propertyClone.setRowguid(null); propertysClone.add(propertyClone); } columnClone.setColumnProperties(propertysClone);
3. 优化保存逻辑
由于映射配置了cascade="all-delete-orphan",可以直接保存克隆的Column对象,Hibernate会自动级联保存其关联的ColumnProperties:
// 直接保存克隆的Column,级联保存属性 columnService.saveOrUpdate(columnClone, object.getCreationUser()); // 若需通过Profile关联保存,确保Profile是Session中获取的实例 // RmgtProfileBVO profile = profileService.findById(profileId); // profile.getGroup(groupId).getColumns().add(columnClone); // profileService.saveOrUpdate(profile, object.getCreationUser());
内容的提问来源于stack exchange,提问作者d00d

