JpaRepository save()创建新子对象而非更新,求更优处理方案
优化JPA更新关联实体的方案,摆脱手动设置ID的麻烦
我完全懂你现在的困扰——每次更新都要手动给父子对象设ID才能避免创建新条目,确实挺繁琐的。这里有几种更优雅的处理方式,帮你彻底摆脱这个麻烦:
方案一:操作现有持久化对象 + 双向@OneToOne关联(最推荐)
核心思路是直接更新从数据库查询出来的持久化对象,而不是用新创建的对象覆盖。JPA会自动追踪这些对象的变化,执行UPDATE而非INSERT操作。同时通过双向关联让JPA正确识别父子实体的关系。
第一步:修改实体类,完善关联关系
Company实体(修改@OneToOne注解)
@Data @Entity @Table(name = "companies") public class Company { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 标记Accounts为关联的维护方,开启孤儿删除(当Accounts被移除时自动删除数据库记录) @OneToOne(cascade=CascadeType.ALL, mappedBy = "company", orphanRemoval = true) private Accounts accounts; @NotNull @SerializedName("company_number") @Column(unique = true) private String companyNumber; @SerializedName("company_name") private String companyName; public Company() {} public Company(String companyNumber) { this.companyNumber = companyNumber; } }
Accounts实体(添加反向关联)
@Data @Entity @Table(name = "accounts") public class Accounts { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @SerializedName("next_due") private Date nextDue; @SerializedName("next_made_up_to") private Date nextMadeUpTo; // 关联回Company,指定外键字段 @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "company_id") private Company company; }
第二步:重构更新方法
@Scheduled(fixedRate = 50000) public void updateAllCompanies() { System.out.println("Starting update!"); for(Company existingCompany : companyRepository.findAll()) { // 获取最新的公司数据(从外部接口/JSON转换而来) Company updatedCompany = getCompany(existingCompany.getCompanyNumber()); // 更新Company的基本属性 existingCompany.setCompanyName(updatedCompany.getCompanyName()); // 处理Accounts关联 Accounts existingAccounts = existingCompany.getAccounts(); Accounts newAccounts = updatedCompany.getAccounts(); if (existingAccounts != null) { // 更新现有Accounts的属性,无需手动设ID existingAccounts.setNextDue(newAccounts.getNextDue()); existingAccounts.setNextMadeUpTo(newAccounts.getNextMadeUpTo()); } else { // 如果之前没有Accounts,新建并关联到当前Company newAccounts.setCompany(existingCompany); existingCompany.setAccounts(newAccounts); } // 直接保存现有对象,JPA自动执行UPDATE操作 companyRepository.save(existingCompany); } }
方案二:用BeanUtils简化属性复制 + @DynamicUpdate
如果觉得手动复制属性太麻烦,可以用Spring的BeanUtils.copyProperties()批量复制,同时搭配@DynamicUpdate让JPA只更新变化的字段,提升性能。
第一步:给实体添加@DynamicUpdate注解
@Data @Entity @Table(name = "companies") @DynamicUpdate // 只更新变化的字段 public class Company { // ... 原有代码 }
@Data @Entity @Table(name = "accounts") @DynamicUpdate public class Accounts { // ... 原有代码 }
第二步:简化更新方法
@Scheduled(fixedRate = 50000) public void updateAllCompanies() { System.out.println("Starting update!"); for(Company existingCompany : companyRepository.findAll()) { Company updatedCompany = getCompany(existingCompany.getCompanyNumber()); // 复制基本属性,忽略ID和关联字段(避免覆盖) BeanUtils.copyProperties(updatedCompany, existingCompany, "id", "accounts"); Accounts existingAccounts = existingCompany.getAccounts(); Accounts newAccounts = updatedCompany.getAccounts(); if (existingAccounts != null) { // 复制Accounts属性,忽略ID和关联字段 BeanUtils.copyProperties(newAccounts, existingAccounts, "id", "company"); } else { newAccounts.setCompany(existingCompany); existingCompany.setAccounts(newAccounts); } companyRepository.save(existingCompany); } }
方案三:使用JPA的merge()方法(谨慎使用)
merge()方法会将新对象的属性合并到数据库中已有的实体上,但要注意如果新对象的关联属性为null,可能会清空现有关联,需要额外处理:
@Scheduled(fixedRate = 50000) public void updateAllCompanies() { System.out.println("Starting update!"); for(Company existingCompany : companyRepository.findAll()) { Company updatedCompany = getCompany(existingCompany.getCompanyNumber()); // 设置现有Company的ID,让merge识别要更新的实体 updatedCompany.setId(existingCompany.getId()); if (existingCompany.getAccounts() != null) { updatedCompany.getAccounts().setId(existingCompany.getAccounts().getId()); // 关联回现有Company,避免创建新条目 updatedCompany.getAccounts().setCompany(existingCompany); } // merge自动合并属性到现有实体 companyRepository.save(updatedCompany); } }
总结
最推荐方案一:操作现有持久化对象+双向关联的方式,既不用手动设置ID,又能保证JPA正确执行更新,同时逻辑清晰,不易出错。
内容的提问来源于stack exchange,提问作者user3371750
相关产品推荐
相关产品推荐

