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

Spring Boot Controller多次调用Save()报父键不存在异常排查

问题场景

Spring Boot项目开发事件详情新增接口时,在标注了@Transactional、@PostMapping("/trans/incidentdetails")的Controller方法updateIncidentCost中实现多表关联数据保存逻辑,执行时抛出ORA-02291 完整性约束(OCCAL.FK_TRX_INCIDENT_DETAILS_1)违反 - 父键不存在错误,触发DataIntegrityViolationException数据完整性异常。

原逻辑执行流程:

  • 根据传入的incidentNumber查询判断事件是否已存在,不存在则先创建IncidentDetails主表实体赋值,调用incidentDetailsRepository.save()保存主表数据,获取持久化返回的主表实体对象
  • 查询全量LobMaster业务线配置,遍历匹配不同业务线,组装对应的IncidentOutage子表实体存入列表,同时组装IncidentOutageSum汇总实体并关联已保存的主表实体
  • 先后调用incidentOutageSumRepository.save()保存汇总数据、incidentOutageRepository.saveAll()批量保存子表列表
问题代码
@Transactional
@PrePersist
@PostMapping("/trans/incidentdetails")
public ResponseEntity<IncidentCreationResponse> updateIncidentCost(@Valid @RequestBody IncidentCreation incidentCreation
                                      ) {


    IncidentOutageSum incidentOutageSum = new IncidentOutageSum();
    System.out.println("incidentNumber: " + incidentCreation.getIncidentNumber());
    System.out.println("incidentDate: " + incidentCreation.getIncidentDate());
    System.out.println("coeed : "+incidentCreation.getSumOfCoed());
    Long checkIncidentId = incidentDetailsRepository.getIncidentDetailsByIncidentNumber(incidentCreation.getIncidentNumber());
    System.out.println(checkIncidentId + " : checkIncidentId");
    String incidentDate = incidentCreation.incidentDate;
    LocalDate dtFmt = null;


try{
     dtFmt = LocalDate.parse(incidentDate,
            DateTimeFormatter.ofPattern("uuuu-M-d")
                    .withResolverStyle(ResolverStyle.STRICT));

    System.out.println("dtFmt : "+dtFmt);
} catch (DateTimeParseException e) {
    IncidentCreationResponse customResponse = new IncidentCreationResponse();

    customResponse.setResponseCode(String.valueOf(HttpStatus.OK.value()));

    customResponse.setMessage("Incorrect Date Format for Incident Date");


    return new ResponseEntity<IncidentCreationResponse>(customResponse, HttpStatus.OK);

}

    if (checkIncidentId == null) {

        System.out.println("IF");

        IncidentDetails incidentDetails = new IncidentDetails();
        incidentDetails.setIncidentNumber(incidentCreation.getIncidentNumber());
        incidentDetails.setIncidentDate(java.sql.Date.valueOf(dtFmt));



        IncidentDetails incidentDetails1 =
                incidentDetailsRepository.save(incidentDetails);

        List<LobMaster> lobMaster = (List<LobMaster>) lobMasterRepository.findAll();

        List<IncidentOutage> objectList = new ArrayList<>();
        for(int i =0 ; i<lobMaster.size() ; i++) {
            System.out.println(incidentDetails1.getId());
           if(lobMaster.get(i).getLobName().toUpperCase().trim().toString().equals("PREPAID") ) {
               System.out.println("PREPAID");

               IncidentOutage incidentOutage = new IncidentOutage();
               incidentOutage.setIncidentDetails(incidentDetails1);
               incidentOutage.setLobMaster(lobMaster.get(i));
               incidentOutage.setCodOrderDegrade(incidentCreation.getIncidentOutageData().prepaid.codOrderDegrade);
               incidentOutage.setCoedServiceCallDegraded(incidentCreation.getIncidentOutageData().prepaid.coedServiceDegrade);
               incidentOutage.setColTransactionDip(incidentCreation.getIncidentOutageData().prepaid.colTransactionDip);
               incidentOutage.setCoplTransactionDip(incidentCreation.getIncidentOutageData().prepaid.coplTransactionDip);
               System.out.println("incidentOutage : "+incidentOutage);
               objectList.add(incidentOutage);

           }
            else if(lobMaster.get(i).getLobName().toUpperCase().trim().toString().equals("POSTPAID") ) {
                System.out.println("POSTPAID");

                IncidentOutage incidentOutage = new IncidentOutage();
                incidentOutage.setIncidentDetails(incidentDetails1);
                incidentOutage.setLobMaster(lobMaster.get(i));
                incidentOutage.setCodOrderDegrade(incidentCreation.getIncidentOutageData().postpaid.codOrderDegrade);
                incidentOutage.setCoedServiceCallDegraded(incidentCreation.getIncidentOutageData().postpaid.coedServiceDegrade);
                incidentOutage.setColTransactionDip(incidentCreation.getIncidentOutageData().postpaid.colTransactionDip);
                incidentOutage.setCoplTransactionDip(incidentCreation.getIncidentOutageData().postpaid.coplTransactionDip);
                System.out.println("incidentOutage : "+incidentOutage);
                objectList.add(incidentOutage);

            }

           else if(lobMaster.get(i).getLobName().toUpperCase().trim().toString().equals("BROADBAND") ) {
                System.out.println("BROADBAND");

                IncidentOutage incidentOutage = new IncidentOutage();
                incidentOutage.setIncidentDetails(incidentDetails1);
                incidentOutage.setLobMaster(lobMaster.get(i));
                incidentOutage.setCodOrderDegrade(incidentCreation.getIncidentOutageData().broadband.codOrderDegrade);
                incidentOutage.setCoedServiceCallDegraded(incidentCreation.getIncidentOutageData().broadband.coedServiceDegrade);
                incidentOutage.setColTransactionDip(incidentCreation.getIncidentOutageData().broadband.colTransactionDip);
                incidentOutage.setCoplTransactionDip(incidentCreation.getIncidentOutageData().broadband.coplTransactionDip);
                System.out.println("incidentOutage : "+incidentOutage);
                objectList.add(incidentOutage);

            }
           else if(lobMaster.get(i).getLobName().toUpperCase().trim().toString().equals("BLACK") ) {
               System.out.println("BLACK");

               IncidentOutage incidentOutage = new IncidentOutage();
               incidentOutage.setIncidentDetails(incidentDetails1);
               incidentOutage.setLobMaster(lobMaster.get(i));
               incidentOutage.setCodOrderDegrade(incidentCreation.getIncidentOutageData().black.codOrderDegrade);
               incidentOutage.setCoedServiceCallDegraded(incidentCreation.getIncidentOutageData().black.coedServiceDegrade);
               incidentOutage.setColTransactionDip(incidentCreation.getIncidentOutageData().black.colTransactionDip);
               incidentOutage.setCoplTransactionDip(incidentCreation.getIncidentOutageData().black.coplTransactionDip);
               System.out.println("incidentOutage : "+incidentOutage);
               objectList.add(incidentOutage);

           }
           else if(lobMaster.get(i).getLobName().toUpperCase().trim().toString().equals("B2B") ) {
               System.out.println("B2B");

               IncidentOutage incidentOutage = new IncidentOutage();
               incidentOutage.setIncidentDetails(incidentDetails1);
               incidentOutage.setLobMaster(lobMaster.get(i));
               incidentOutage.setCodOrderDegrade(incidentCreation.getIncidentOutageData().b2B.codOrderDegrade);
               incidentOutage.setCoedServiceCallDegraded(incidentCreation.getIncidentOutageData().b2B.coedServiceDegrade);
               incidentOutage.setColTransactionDip(incidentCreation.getIncidentOutageData().b2B.colTransactionDip);
               incidentOutage.setCoplTransactionDip(incidentCreation.getIncidentOutageData().b2B.coplTransactionDip);
               System.out.println("incidentOutage : "+incidentOutage);
               objectList.add(incidentOutage);

           }
           else if(lobMaster.get(i).getLobName().toUpperCase().trim().toString().equals("DTH") ) {
               System.out.println("DTH");

               IncidentOutage incidentOutage = new IncidentOutage();
               incidentOutage.setIncidentDetails(incidentDetails1);
               incidentOutage.setLobMaster(lobMaster.get(i));
               incidentOutage.setCodOrderDegrade(incidentCreation.getIncidentOutageData().dTH.codOrderDegrade);
               incidentOutage.setCoedServiceCallDegraded(incidentCreation.getIncidentOutageData().dTH.coedServiceDegrade);
               incidentOutage.setColTransactionDip(incidentCreation.getIncidentOutageData().dTH.colTransactionDip);
               incidentOutage.setCoplTransactionDip(incidentCreation.getIncidentOutageData().dTH.coplTransactionDip);
               System.out.println("incidentOutage : "+incidentOutage);
               objectList.add(incidentOutage);

           }else{
               System.out.println("No matching Lob");
           }

        }

        incidentOutageSum.setIncidentDetails(incidentDetails1);
        System.out.println("incidentCreation.sumOfCod: "+incidentCreation.getSumOfCod());
        incidentOutageSum.setSumOfCod(incidentCreation.getSumOfCod());
        incidentOutageSum.setSumOfCoed(incidentCreation.getSumOfCoed());
        incidentOutageSum.setSumOfCopl(incidentCreation.getSumOfCopl());
        incidentOutageSum.setSumOfCol(incidentCreation.getSumOfCol());

        Float totalCost = incidentCreation.getSumOfCod() + incidentCreation.getSumOfCoed() +
                incidentCreation.getSumOfCopl() + incidentCreation.getSumOfCol();

        IncidentCreationResponse customResponse = new IncidentCreationResponse();

        customResponse.setResponseCode(String.valueOf(HttpStatus.CREATED.value()));
        customResponse.setTotalCost(totalCost);
        customResponse.setMessage("Record Saved Successfully");

        IncidentOutageSum incidentOutageSum1 =  incidentOutageSumRepository.save(incidentOutageSum);
        Iterable<IncidentOutage> incidentOutage = incidentOutageRepository.saveAll(objectList);

        return new ResponseEntity<IncidentCreationResponse>(customResponse, HttpStatus.CREATED);

    }else{
        System.out.println("ELSE");
        IncidentCreationResponse customResponse = new IncidentCreationResponse();

        customResponse.setResponseCode(String.valueOf(HttpStatus.OK.value()));
        customResponse.setMessage("Record Already Exists");

        return new ResponseEntity<IncidentCreationResponse>(customResponse, HttpStatus.OK);
    }
}
报错日志
Hibernate: insert into trx_incident_details (incident_date, incident_number, modified_date, id) values (?, ?, ?, ?)
Hibernate: insert into trx_outage_sum (incident_id, modified_date, sum_of_cod, sum_of_coed, sum_of_col, sum_of_copl, id) values (?, ?, ?, ?, ?, ?, ?)
2022-06-11 00:31:51.612  WARN 20637 --- [nio-8080-exec-7] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 2291, SQLState: 23000
2022-06-11 00:31:51.612 ERROR 20637 --- [nio-8080-exec-7] o.h.engine.jdbc.spi.SqlExceptionHelper   : ORA-02291: integrity constraint (OCCAL.FK_TRX_INCIDENT_DETAILS_1) violated - parent key not found
2022-06-11 00:31:51.612  INFO 20637 --- [nio-8080-exec-7] o.h.e.j.b.internal.AbstractBatchImpl     : HHH000010: On release of batch it still contained JDBC statements
2022-06-11 00:31:51.618  WARN 20637 --- [nio-8080-exec-7] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [OCCAL.FK_TRX_INCIDENT_DETAILS_1]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement]
根因分析
  1. 注解使用错误:Controller接口方法上错误添加了@PrePersist注解,该注解是JPA实体生命周期回调注解,仅能用于JPA实体类的内部方法,标注在Controller方法上会扰乱JPA持久化逻辑和Spring AOP代理逻辑。
  2. 外键映射配置错误:IncidentOutageSum或IncidentOutage实体中关联IncidentDetails的@JoinColumn配置不正确,大概率是referencedColumnName没有指向主表主键id,导致插入从表时传入的外键值在主表中不存在。
  3. 事务配置不生效:@Transactional标注在Controller方法上,多数Spring Boot项目默认事务拦截仅配置在Service层,Controller层方法不会被事务代理,会出现多个独立事务提交、主表插入未完成就执行从表插入的问题。
  4. 主键生成策略不匹配:Oracle12c之前的版本不支持IDENTITY自增主键,若IncidentDetails主键配置为IDENTITY生成策略,会导致save返回的主键值和数据库实际插入值不一致,从表关联的外键值无效。
修复方案
  • 删除Controller方法上的@PrePersist注解,该注解不能用于Web接口方法。
  • 调整主键生成策略适配Oracle,使用序列生成主表主键,参考配置:
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "incident_details_seq")
@SequenceGenerator(name = "incident_details_seq", sequenceName = "TRX_INCIDENT_DETAILS_SEQ", allocationSize = 1)
private Long id;
  • 检查所有关联实体的外键映射,确保IncidentOutageSum、IncidentOutage中关联主表的字段配置正确,参考配置:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "incident_id", referencedColumnName = "id", nullable = false)
private IncidentDetails incidentDetails;
  • 重构代码分层:将多表保存逻辑迁移到Service层,在Service层的public方法上添加@Transactional注解,Controller层仅处理参数校验和请求响应转发,保证事务正常生效。
  • 若仍存在SQL执行顺序问题,可在主表save操作后手动调用incidentDetailsRepository.flush(),强制将主表数据刷入数据库后再执行后续从表保存操作。

内容的提问来源于stack exchange,提问作者ankush dhingan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 15:15:35