Hibernate保存复制Claim对象时原对象字段被置空问题
问题原因分析
你碰到的这个问题核心在于 Spring BeanUtils.copyProperties是浅拷贝:它只会复制对象的基本类型字段值,对于引用类型(比如你的ClaimLineItem集合),仅仅是把原对象的引用复制给新对象,并没有创建全新的实例。
这就导致copyClaim.getClaimLineItems()拿到的集合,和原claim对象的集合是同一个内存引用。当你修改copyClaim里的ClaimLineItem字段时,实际修改的是和原claim共享的对象实例。再加上JPA的持久化上下文会自动跟踪托管实体的变化,最终就出现了原claim字段被改成null的情况。
解决方案
下面提供几种可行的深拷贝方案,帮你彻底隔离原对象和拷贝对象:
1. 手动深拷贝集合元素
最直接的方式就是手动为集合里的每个ClaimLineItem创建新实例,避免共享引用:
@Override public void updateClaimLineItemWithExtendedCreditPaidInDraft(Claim claim) { Claim copyClaim = new Claim(); try { // 先拷贝基本类型字段 BeanUtils.copyProperties(claim, copyClaim); // 手动深拷贝ClaimLineItems集合 List<ClaimLineItem> newLineItems = new ArrayList<>(); for(ClaimLineItem originalCl : claim.getClaimLineItems()) { ClaimLineItem newCl = new ClaimLineItem(); // 拷贝原ClaimLineItem的字段到新实例 BeanUtils.copyProperties(originalCl, newCl); // 修改新实例的字段,不会影响原对象 newCl.setDealerNetPrice(null); newCl.setDescription(null); newLineItems.add(newCl); } // 替换copyClaim的集合为全新的集合 copyClaim.setClaimLineItems(newLineItems); // 设置其他字段 copyClaim.setTotalCreditPaid(null); copyClaim.setTotalCreditPaidInUSD(null); copyClaim.setTotalCreditRequested(null); claimRepository.save(copyClaim); } catch(Exception ex) { // 异常处理逻辑 } }
2. 使用支持深拷贝的工具类(比如ModelMapper)
ModelMapper默认支持深拷贝,配置简单,能帮你省去手动拷贝的麻烦:
首先需要引入依赖(以Maven为例):
<dependency> <groupId>org.modelmapper</groupId> <artifactId>modelmapper</artifactId> <version>3.1.1</version> </dependency>
然后修改代码:
@Override public void updateClaimLineItemWithExtendedCreditPaidInDraft(Claim claim) { try { ModelMapper modelMapper = new ModelMapper(); // 确保开启深拷贝配置 modelMapper.getConfiguration().setDeepCopyEnabled(true); // 直接生成深拷贝的对象 Claim copyClaim = modelMapper.map(claim, Claim.class); // 此时修改copyClaim的字段完全不会影响原claim for(ClaimLineItem cl : copyClaim.getClaimLineItems()) { cl.setDealerNetPrice(null); cl.setDescription(null); } copyClaim.setTotalCreditPaid(null); copyClaim.setTotalCreditPaidInUSD(null); copyClaim.setTotalCreditRequested(null); claimRepository.save(copyClaim); } catch(Exception ex) { // 异常处理逻辑 } }
3. 利用Jackson序列化反序列化实现深拷贝
如果你的项目已经引入了Jackson,可以用它的序列化/反序列化来生成完全独立的对象:
@Override public void updateClaimLineItemWithExtendedCreditPaidInDraft(Claim claim) { Claim copyClaim = new Claim(); try { ObjectMapper objectMapper = new ObjectMapper(); // 序列化原对象为JSON,再反序列化为新对象,实现深拷贝 copyClaim = objectMapper.readValue(objectMapper.writeValueAsString(claim), Claim.class); // 后续修改操作完全不影响原对象 for(ClaimLineItem cl : copyClaim.getClaimLineItems()) { cl.setDealerNetPrice(null); cl.setDescription(null); } copyClaim.setTotalCreditPaid(null); copyClaim.setTotalCreditPaidInUSD(null); copyClaim.setTotalCreditRequested(null); claimRepository.save(copyClaim); } catch(Exception ex) { // 异常处理逻辑 } }
额外提醒
因为原claim是JPA托管的实体(处于持久化上下文),如果不做深拷贝,修改拷贝对象的关联元素时,JPA会自动同步这些修改到数据库和原对象。所以只要确保拷贝对象的引用类型字段都是全新的实例,就能彻底避免这个问题。
内容的提问来源于stack exchange,提问作者Karthikeyan
相关产品推荐
相关产品推荐

