单向@ManyToOne关联下AccountRequestStatus实体更新失败问题咨询
问题根因
- 核心逻辑错误:更新
AccountRequestStatus时未查询已持久化的旧记录,直接新建了无主键ID的全新实体对象,JPA的save()方法会将无ID的实体识别为新数据,执行INSERT操作而非UPDATE操作:- 首次保存INITIAL状态时,AccountRequest先被持久化拿到数据库生成的主键,关联创建的AccountRequestStatus也会被生成主键,正常入库。
- 第二个服务中直接从MQ事件转换得到的AccountRequest没有携带数据库生成的主键ID,基于此新建的AccountRequestStatusDto的ID字段为空,Mapper转换后的实体无ID,执行save只会插入新记录,不会更新原有INITIAL状态的记录。
- 缺少唯一性校验:如果
T_ACCOUNT_REQUEST_STATUS表未对关联的ACCOUNT_REQUEST字段加唯一约束,重复插入不会报错,只会生成多条状态不同的关联记录,看起来就像更新没生效;如果加了唯一约束,会直接抛出主键/唯一键冲突异常,未捕获对应异常就会感知不到报错。 - 第二个服务中未先查询已存在的AccountRequest:直接从事件转换得到的AccountRequest仅包含业务字段,没有数据库主键,无法关联到已有的状态记录。
解决方案
步骤1:新增Repository查询方法
在AccountRequestRepository中新增业务键查询方法:
Optional<AccountRequest> findByMessageIdAndAccountNo(String messageId, String accountNo);
在AccountRequestStatusRepository中新增关联查询方法:
Optional<AccountRequestStatus> findByAccountRequestId(Long accountRequestId);
步骤2:修改第二个服务的状态保存逻辑
先查询已存在的AccountRequest,获取数据库主键:
public void saveSuccessfulAccountStatus(Event event) { AccountRequest temp = eventToAccountRequestMapper.eventToAccountRequest(event, eventArgumentKey); // 先查库拿到已持久化的AccountRequest,获取主键 AccountRequest existAccountRequest = accountRequestRepository.findByMessageIdAndAccountNo(temp.getMessageId(), temp.getAccountNo()) .orElseThrow(() -> new IllegalArgumentException("未找到对应账户申请记录,messageId:" + temp.getMessageId() + ", accountNo:" + temp.getAccountNo())); AccountRequestDto accountRequestDto = accountRequestMapper.toDto(existAccountRequest); saveAccountRequestStatus(accountRequestDto, AccountRequestStatusEnum.SUCCESS); }
步骤3:改造AccountRequestStatus保存逻辑,兼容新增/更新场景
修改AccountRequestStatusServiceImpl的保存方法,自动判断是新增还是更新:
@Override @Transactional public void saveAccountRequestStatus(AccountRequestStatusDto accountRequestStatusDto) { Long accountRequestId = accountRequestStatusDto.getAccountRequestDto().getId(); // 先查询是否已有对应状态记录 AccountRequestStatus status = accountRequestStatusRepository.findByAccountRequestId(accountRequestId) // 无记录则为首次保存,用Mapper转换新建实体 .orElseGet(() -> accountRequestStatusMapper.toAccountRequestStatus(accountRequestStatusDto)); // 已有记录则仅更新状态字段 if (status.getId() != null) { status.setAccountRequestStatusEnum(accountRequestStatusDto.getAccountRequestStatusEnum()); } accountRequestStatusRepository.save(status); }
可选优化
可以给T_ACCOUNT_REQUEST_STATUS表的ACCOUNT_REQUEST字段加唯一约束,避免一个AccountRequest对应多条状态记录,出现数据不一致问题。
内容的提问来源于stack exchange,提问作者Faramarz Afzali
相关产品推荐
相关产品推荐

