You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:01:30