Cascade="all-delete-orphan"以及java.util.ConcurrentModificationException问题

本文探讨了Hibernate中cascade=all-delete-orphan的应用,以及如何在使用Collection或List等集合进行迭代删除操作时避免java.util.ConcurrentModificationException异常。

为以下两个问题提供解决:

 

1. Hibernate cascade="all-delete-orphan"应用;

2. 用Collection or List 等集合时,迭代remove操作引起的java.util.ConcurrentModificationException问题

 

 

 

	/** 修改業務系統 */
	public String updateBuinessSys() {
		modify = true;
		String transId = (String) getRequestMap().get("transId");
		if (StringUtil.isNullOrBlank(transId)) {
			context.addMessage(null, new FacesMessage("不能獲取業務系統對象"));
			return null;
		}
		Transaction transaction = transactionsManager.getTransactionById(transId);
		if (transaction != null) {
			transaction.setTransName(transName);
			transaction.setTransCode(transCode);
			transaction.setTransDesc(transDesc);
			transaction.setEnterpriseCodes(enterpriseCodes);
			transaction.setBankCodes(bankCodes);

			String transFuncXml = context.getExternalContext().getRequestParameterMap().get("transFuncXml");
			assembleTransFuncs(transFuncXml);

			Set<String> newFunIdSet = new HashSet<String>();//新增的列表id set
			Set<String> dbFunIdSet = new HashSet<String>();//DB中的列表id set
			List<TransFunction> funList = transaction.getTransFunctions();
			for (Iterator iterator = funList.iterator(); iterator.hasNext();) {
				TransFunction fun = (TransFunction) iterator.next();
				dbFunIdSet.add(fun.getId());
			}
			for (Iterator iterator = transFunList.iterator(); iterator.hasNext();) {
				TransFunction fun = (TransFunction) iterator.next();
				if (StringUtil.isNotNullOrBlank(fun.getId())) {
					newFunIdSet.add(fun.getId());
				}
			}
			/* 先remove,後add則不需要判斷fun.id為null的情況 */
			for (Iterator iterator = funList.iterator(); iterator.hasNext();) {
				TransFunction fun = (TransFunction) iterator.next();
				if (!newFunIdSet.contains(fun.getId()))//如果保存的list中的ID在DB中不存在了,即已經被刪除了,則remove from list,使之成為orphan
					//funList.remove(fun); 這一句會引起java.util.ConcurrentModificationException
					iterator.remove();
			}
			for (Iterator iterator = transFunList.iterator(); iterator.hasNext();) {
				TransFunction fun = (TransFunction) iterator.next();
				if (StringUtil.isNullOrBlank(fun.getId()) || !dbFunIdSet.contains(fun.getId()))
					funList.add(fun);//新增的記錄在DB中沒有,則認為是新增的記錄,加入到list當中去
			}

			//transaction.setTransFunctions(transFunList); 不需要重新設置,設定了all-delete-orphan即可
			transactionsManager.saveOrUpdate(transaction);
			return "viewBusinessState";
		}
		context.addMessage(null, new FacesMessage("無效的表單參數"));
		return Constants.ERROR;
	}

 

Transaction - TransFunction是一对多的关系: hbm.xml配置档如下:

 

<?xml version="1.0"  encoding='UTF-8'?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
	"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.test.domain">
	<class name="Transaction" table="SYS_TRANS">
		<id name="id" column="ID" length="32" type="string">
			<generator class="uuid" />
		</id>
		<property name="transName" column="NAME" length="30" />
		<property name="transCode" column="CODE" length="10" unique="true" />
		<property name="transDesc" column="DESCR" length="200" />
		<property name="enterpriseCodes" column="ENTP_CODES" length="1000" />
		<property name="bankCodes" column="BANK_CODES" length="1000" />
		<bag name="transFunctions" cascade="all-delete-orphan" inverse="false" lazy="false">
			<key column="TRANS_ID" />
			<one-to-many class="TransFunction" />
		</bag>
	</class>

</hibernate-mapping>

 

<?xml version="1.0"  encoding='UTF-8'?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
	"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.test.domain">
	<class name="TransFunction" table="SYS_TRANS_FUNC">
		<id name="id" column="ID" length="32" type="string">
			<generator class="uuid" />
		</id>
		<property name="funcName" column="NAME" length="30" />
		<property name="funcCode" column="CODE" length="20" unique="true" />
		<many-to-one name="transaction" column="TRANS_ID"/>
	</class>

</hibernate-mapping>

 

 

 

<think>我们正在解决Hibernate中的一个特定错误:"A collection with cascade=\"all-delete-orphan\" was no longer referenced by the owning entity instance"。根据提供的引用,我们可以总结出以下关键信息和解决方法: 关键问题: 当父实体(owning entity)有一个集合(例如一对多关系),并且该集合的级联操作设置为`all-delete-orphan`(或`CascadeType.ALL`加上`orphanRemoval=true`)时,如果我们直接将一个新的集合引用赋值给该属性(而不是修改现有集合),Hibernate就会抛出这个异常。 原因: Hibernate要求对这样的集合进行修改(添加、删除元素)必须通过操作原始集合来实现,而不能用一个新的集合对象替换。因为Hibernate需要跟踪集合的变化(例如删除了哪些元素,以便删除孤儿记录)。 解决方法: 1. 不要重新设置(new)集合,而是通过修改现有集合来更新。 错误做法:`parent.setChildren(new HashSet<>());` 正确做法:先清空现有集合,然后加入新元素。 `parent.getChildren().clear();` `parent.getChildren().addAll(newChildren);` 2. 在实体类中,确保在构造函数中初始化集合,以避免空指针异常。 例如:`private List<Child> children = new ArrayList<>();` 3. 根据引用[2]和[5]中的示例,我们在更新集合时,不要直接赋值,而是操作原有的集合引用。 实例代码修正(参考引用[5]): 修改前: tempItem.setTempItemCats(set); // 直接赋值新的集合 修改后: tempItem.getTempItemCats().clear(); tempItem.getTempItemCats().addAll(set); 4. 另外,在实体类的映射注解中,确保正确设置了级联和孤儿删除。例如引用[2]中的: `@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)` 5. 注意:在更新操作中,我们首先从数据库加载父实体(包括其集合),然后修改这个加载的集合(清空并添加新元素),最后保存父实体。 总结步骤: - 从数据库中加载父实体(包含要修改的集合)。 - 获取该集合,使用`clear()`方法清空。 - 使用`addAll()`方法将新元素加入集合(注意:新元素必须是实体对象,并且已经设置了必要的关系)。 - 保存父实体。 这样,Hibernate就能正确地处理级联操作和孤儿删除。 注意:如果集合中的元素有改变,我们不应该创建一个新的集合实例,而是修改原有的集合实例。 引用[3]也明确指出了这一点。 因此,解决此错误的关键在于:不要重新给集合属性赋值,而是操作原有集合对象。 参考代码(基于引用[5]的修正): ```java // 错误示例:直接设置一个新的集合 // parent.setChildren(new HashSet<>(children)); // 正确做法: parent.getChildren().clear(); // 清空原有集合 parent.getChildren().addAll(children); // 将新集合中的所有元素加入 ``` 另外,在实体类中,确保集合被正确初始化(避免NullPointerException): ```java @Entity public class Parent { @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true) private List<Child> children = new ArrayList<>(); // 初始化 // ... 其他属性和方法 } ``` 最后,保存父实体时,Hibernate会自动处理级联操作:删除不再被引用的子实体(孤儿)以及新增或更新的子实体。 以上方法应该能解决该异常。 相关问题: 1.Hibernate中,orphanRemoval=true的作用是什么? 2. Hibernate中常见的级联操作有哪些? 3. 如何正确更新Hibernate中一对多关联的集合? </think>### 解决 Hibernate "A collection with cascade=all-delete-orphan was no longer referenced" 错误的方法 该错误发生在 **Hibernate/JPA 中更新关联集合时直接替换了集合引用**,而非修改现有集合对象。以下是完整解决方案: #### 🔧 根本原因 当实体使用 `cascade=CascadeType.ALL + orphanRemoval=true` 时,Hibernate 要求: 1. **必须操作原始集合对象**(不能新建集合赋值) 2. **需通过集合修改操作触发孤儿删除**(如 `clear()`/`remove()`) 3. ⚠️ 直接 `setCollection(new ArrayList())` 会导致原始引用丢失,触发此异常 #### ✅ 解决方法(代码示例) ```java // ✅ 正确做法:修改现有集合(非替换引用) parentEntity.getChildren().clear(); // 清空旧数据 parentEntity.getChildren().addAll(newList); // 添加新数据 // ❌ 错误做法(导致异常): // parentEntity.setChildren(new ArrayList<>()); ``` #### ⚙️ 实体配置要点 ```java @Entity public class Parent { @OneToMany( mappedBy = "parent", cascade = CascadeType.ALL, // 级联所有操作 orphanRemoval = true // 启用孤儿删除 ) private List<Child> children = new ArrayList<>(); // 必须初始化集合! } ``` #### 📝 关键步骤 1. **初始化集合** 实体类中直接初始化集合(避免 `null`): ```java private List<Child> children = new ArrayList<>(); // 关键! ``` 2. **更新操作规范** - 始终通过 `getCollection().clear()` 和 `getCollection().addAll()` 更新 - 禁止直接调用 `setCollection()` 方法赋新集合 3. **双向关联维护**(如适用) 在子实体中同步维护父引用: ```java child.setParent(parent); // 添加子项时需设置反向关联 ``` #### 💡 实际场景示例 用户信息更新权限(参考引用[^1][^5]): ```java // 从数据库加载用户 User user = userRepository.findById(userId); // 错误:user.setAuthorities(new HashSet<>()); // 正确: user.getAuthorities().clear(); user.getAuthorities().addAll(newAuthorities); userRepository.save(user); // 级联删除旧权限 ``` #### ⚠️ 注意事项 - **孤儿删除逻辑**:`orphanRemoval=true` 会删除集合中**被移除的子实体** - **性能优化**:大数据集时建议分批操作 - **事务管理**:确保操作在同一个事务中 通过遵循 **操作现有集合而非替换引用** 的原则,可彻底解决此异常[^2][^3][^5]。 --- ### 📚 相关问题 1. Hibernate 中 `orphanRemoval=true` 和 `cascade=CascadeType.REMOVE` 有何区别? 2. 如何优化 JPA 中一对多关联的批量删除性能? 3. 在 Spring Data JPA 中更新实体时如何避免 `LazyInitializationException`? 4. 双向关联 vs 单向关联:在 JPA 中应如何选择? 5. JPA 实体生命周期中的常见陷阱有哪些?如何避免?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值