使用单向@ManyToOne 关系更新实体时出现问题

Problem in updating the entity with unidirectional @ManyToOne relation

我有两个实体如下,主要问题是我想更新 AccountRequestStatus 实体。我将 AccountRequestStatusEnum 的整数枚举代码保存在数据库中,以在整个应用程序中保留 AcountRequest 状态。

AccountRequestStatusEnum

public enum AccountRequestStatusEnum {

    INITIAL(0),
    SUCCESS(1);

    private final Integer type;

    AccountRequestStatusEnum(Integer type) {
        this.type = type;
    }

    public Integer getType() {
        return type;
    }

    public static AccountRequestStatusEnum of(Integer type) {
        for (AccountRequestStatusEnum accountRequestStatusEnum : AccountRequestStatusEnum.values()) {
            if (type.equals(accountRequestStatusEnum.getType()))
                return accountRequestStatusEnum;
        }
        return null;
    }

}

帐户请求

@Entity
@Table(name = "T_ACCOUNT_REQUEST", uniqueConstraints = {@UniqueConstraint(columnNames = {"ACCOUNT_NO", "MESSAGE_ID"})})
@SequenceGenerator(
        name = "SEQ_T_ACCOUNT_REQUEST",
        sequenceName = "SEQ_T_ACCOUNT_REQUEST",
        allocationSize = 1)
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@ToString
public class AccountRequest extends AbstractAuditingEntity {

    private Long id;
    private String messageId;
    private String issuer;
    private EventType type;
    private EventName name;
    private String accountNo;
    private LocalDateTime dateTime;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_T_ACCOUNT_REQUEST")
    @Column(name = "ID", nullable = true, insertable = true, updatable = true, precision = 0)
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Column(name = "MESSAGE_ID")
    public String getMessageId() {
        return messageId;
    }

    public void setMessageId(String messageId) {
        this.messageId = messageId;
    }

    @Column(name = "ISSUER")
    public String getIssuer() {
        return issuer;
    }

    public void setIssuer(String issuer) {
        this.issuer = issuer;
    }

    @Transient
    public EventType getType() {
        return type;
    }

    public void setType(EventType type) {
        this.type = type;
    }

    @Column(name = "TYPE")
    public Integer getEventTypeCode() {
        if (Objects.nonNull(type)) {
            return type.getType();
        } else return null;
    }

    public void setEventTypeCode(Integer typeCode) {
        type = EventType.of(typeCode);
    }

    @Transient
    public EventName getName() {
        return name;
    }

    public void setName(EventName name) {
        this.name = name;
    }

    @Column(name = "NAME")
    public Integer getEventNameCode() {
        if (Objects.nonNull(name)) {
            return name.getType();
        } else return null;
    }

    public void setEventNameCode(Integer type) {
        name = EventName.of(type);
    }

    @Column(name = "ACCOUNT_NO")
    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }

    @Column(name = "DATE_TIME")
    public LocalDateTime getDateTime() {
        return dateTime;
    }

    public void setDateTime(LocalDateTime dateTime) {
        this.dateTime = dateTime;
    }
}

AccountRequestStatus


@Entity
@Table(name = "T_ACCOUNT_REQUEST_STATUS")
@SequenceGenerator(
        name = "SEQ_T_ACCOUNT_REQUEST_STATUS",
        sequenceName = "SEQ_T_ACCOUNT_REQUEST_STATUS",
        allocationSize = 1
)
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class AccountRequestStatus extends AbstractAuditingEntity {

    private Long id;
    private AccountRequestStatusEnum accountRequestStatusEnum;
    private AccountRequest accountRequest;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_T_ACCOUNT_REQUEST_STATUS")
    @Column(name = "ID", nullable = true, insertable = true, updatable = true, precision = 0)
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Transient
    public AccountRequestStatusEnum getAccountRequestStatusEnum() {
        return accountRequestStatusEnum;
    }

    public void setAccountRequestStatusEnum(AccountRequestStatusEnum accountRequestStatusEnum) {
        this.accountRequestStatusEnum = accountRequestStatusEnum;
    }


    @Column(name = "ACCOUNT_REQUEST_STATUS")
    public Integer getAccountRequestStatusCode() {
        if (Objects.nonNull(accountRequestStatusEnum)) {
            return accountRequestStatusEnum.getType();
        } else return null;
    }

    public void setAccountRequestStatusCode(Integer type) {
        accountRequestStatusEnum = AccountRequestStatusEnum.of(type);
    }

    @ManyToOne(targetEntity = AccountRequest.class)
    @JoinColumn(name = "ACCOUNT_REQUEST", referencedColumnName = "ID")
    public AccountRequest getAccountRequest() {
        return accountRequest;
    }

    public void setAccountRequest(AccountRequest accountRequest) {
        this.accountRequest = accountRequest;
    }
}

帐户请求第一次来自 MQ my 应用程序时,我将 AccountRequestStatusEnuminitial 代码保存在服务中,如下所示。此状态正确持续并且没有问题,但是当我想更新 AccountRequestStatus 并添加新的 success 代码 AccountRequestStatusEnum (在另一个服务中) 它不会保存在数据库中。

这是收到帐户请求并保存 initial 代码后调用的第一个服务。

@Service
@Transactional(readOnly = true)
public class AccountRequestServiceImpl implements IAccountRequestService {

    @Value("${mq.event_argument_key}")
    private String eventArgumentKey;

    private final AccountRequestRepository accountRequestRepository;
    private final AccountRequestStatusServiceImpl mqRequestStatusService;
    private final EventToAccountRequestEntityMapper eventMapper;
    private final AccountRequestMapper accountRequestMapper;

    @Autowired
    public AccountRequestServiceImpl(AccountRequestRepository accountRequestRepository,
                                     AccountRequestStatusServiceImpl mqRequestStatusService,
                                     EventToAccountRequestEntityMapper eventMapper,
                                     AccountRequestMapper accountRequestMapper) {
        this.accountRequestRepository = accountRequestRepository;
        this.mqRequestStatusService = mqRequestStatusService;
        this.eventMapper = eventMapper;
        this.accountRequestMapper = accountRequestMapper;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)// to prevent rollback for whole receive method in mq service
    public void saveAccountRequest(Event event) {
        AccountRequest accountRequest = eventMapper.eventToAccountRequest(event, eventArgumentKey);
        accountRequestRepository.save(accountRequest);
        AccountRequestDto accountRequestDto = accountRequestMapper.toDto(accountRequest);
        saveAccountRequestStatus(accountRequestDto, AccountRequestStatusEnum.INITIAL);
    }

    private void saveAccountRequestStatus(AccountRequestDto accountRequestDto, AccountRequestStatusEnum status) {
        AccountRequestStatusDto accountRequestStatusDto = new AccountRequestStatusDto();
        accountRequestStatusDto.setAccountRequestStatusEnum(status);
        accountRequestStatusDto.setAccountRequestDto(accountRequestDto);
        mqRequestStatusService.saveAccountRequestStatus(accountRequestStatusDto);
    }
}

这是应保存 AccountRequestStatussuccess 代码的第二个服务。


@Service
@Transactional(readOnly = true)
public class SyncLegacyAccountServiceImpl implements ISyncLegacyAccountService {

    @Value("${mq.event_argument_key}")
    private String eventArgumentKey;
    @Value("${range.account_title_code}")
    private String accountTitleCode;

    private static final Logger log = LoggerFactory.getLogger(SyncLegacyAccountServiceImpl.class);

    private final AccountMapRepository accountMapRepository;
    private final AccountRequestRepository accountRequestRepository;
    private final CustomerRepository customerRepository;
    private final CustomerPersonRepository customerPersonRepository;
    private final CustomerCompanyRepository customerCompanyRepository;
    private final IMQService iMQService;
    private final AccountRequestStatusServiceImpl accountRequestStatusServiceImpl;
    private final GalaxyApi galaxyApi;
    private final RangeApi rangeApi;
    private final CustomerMapper customerMapper;
    private final InquiryMapper inquiryMapper;
    private final AccountRequestMapper accountRequestMapper;
    private final EventToAccountRequestEntityMapper eventToAccountRequestMapper;

    @Override
    public void handleSyncRequest(Event event) {
        saveSuccessfulAccountStatus(event); // ****** This is the main issue******
        try {
            CustomerAccountResponseDto galaxyData = getGalaxyData(event);
            Optional<AccountMapEntity> optAccountMapEntity = accountMapRepository.findByNewAccountNo(event.getArgument().get(eventArgumentKey).toString());
            if (!optAccountMapEntity.isPresent()) {
                //openAccount(event);
            } else {
                AccountMapEntity accountMapEntity = optAccountMapEntity.get();
                CustomerAccountResponseDto customerData = getCustomerData(accountMapEntity);
                // save in legacy

            }
        } catch (Exception exception) {
            handleEventRequestException(exception, event); 
        }
    }

    private void handleEventRequestException(Exception exception, Event event) {
        if (exception instanceof RangeServiceException) {
            log.error("Something went wrong with the Range service!");
            throw new RangeServiceException();
        } else if (exception instanceof GalaxySystemException) {
            log.error("Something went wrong with the Galaxy service!");
            NotifyAccountChangeResponse notifyAccountChangeResponse = MQUtil.buildAccountChangeResponse(new GalaxySystemException(), null, event.getMessageId());
            iMQService.send(notifyAccountChangeResponse);
            throw new GalaxySystemException();
        }
    }

    public void saveSuccessfulAccountStatus(Event event) {
        AccountRequest accountRequest = eventToAccountRequestMapper.eventToAccountRequest(event, eventArgumentKey);
        AccountRequestDto accountRequestDto = accountRequestMapper.toDto(accountRequest);
        saveAccountRequestStatus(accountRequestDto, AccountRequestStatusEnum.SUCCESS);
    }

    public void saveAccountRequestStatus(AccountRequestDto accountRequestDto, AccountRequestStatusEnum status) {
        AccountRequestStatusDto accountRequestStatusDto = new AccountRequestStatusDto();
        accountRequestStatusDto.setAccountRequestStatusEnum(status);
        accountRequestStatusDto.setAccountRequestDto(accountRequestDto);
        accountRequestStatusServiceImpl.saveAccountRequestStatus(accountRequestStatusDto);
    }
 
}

AccountRequestStatusServiceImpl

@Service
@Transactional(readOnly = true)
public class AccountRequestStatusServiceImpl implements IAccountRequestStatusService {

    private final AccountRequestStatusRepository accountRequestStatusRepository;
    private final AccountRequestStatusMapper accountRequestStatusMapper;

    @Autowired
    public AccountRequestStatusServiceImpl(AccountRequestStatusRepository accountRequestStatusRepository,
                                           AccountRequestStatusMapper accountRequestStatusMapper) {
        this.accountRequestStatusRepository = accountRequestStatusRepository;
        this.accountRequestStatusMapper = accountRequestStatusMapper;
    }

    @Override
    @Transactional
    public void saveAccountRequestStatus(AccountRequestStatusDto accountRequestStatusDto) {
        AccountRequestStatus accountRequestStatus = accountRequestStatusMapper.toAccountRequestStatus(accountRequestStatusDto);
        accountRequestStatusRepository.save(accountRequestStatus);
    }
}

AccountRequestDto

@Data
public class AccountRequestDto {
    private Long id;
    private String messageId;
    private String issuer;
    private EventType type;
    private EventName name;
    private String accountNo;
    private LocalDateTime dateTime;
}

AccountRequestStatusDto

@Data
public class AccountRequestStatusDto {

    private Long id;
    private AccountRequestStatusEnum accountRequestStatusEnum;
    private AccountRequestDto accountRequestDto;

}

AccountRequestStatusMapper

@Mapper(componentModel = "spring")
public interface AccountRequestStatusMapper extends EntityToDtoMapper<AccountRequestStatusDto, AccountRequestStatus>, DtoToEntityMapper<AccountRequestStatusDto, AccountRequestStatus> {
 

    @Mapping(target = "accountRequest.id", source = "accountRequestDto.id")
    @Mapping(target = "accountRequest.messageId", source = "accountRequestDto.messageId")
    @Mapping(target = "accountRequest.issuer", source = "accountRequestDto.issuer")
    @Mapping(target = "accountRequest.type", source = "accountRequestDto.type")
    @Mapping(target = "accountRequest.name", source = "accountRequestDto.name")
    @Mapping(target = "accountRequest.accountNo", source = "accountRequestDto.accountNo")
    @Mapping(target = "accountRequest.dateTime", source = "accountRequestDto.dateTime")
    @Named(value = "toAccountRequestStatus")
    AccountRequestStatus toAccountRequestStatus(AccountRequestStatusDto accountRequestStatusDto);

    @Mapping(target = "accountRequestDto.id", source = "accountRequest.id")
    @Mapping(target = "accountRequestDto.messageId", source = "accountRequest.messageId")
    @Mapping(target = "accountRequestDto.issuer", source = "accountRequest.issuer")
    @Mapping(target = "accountRequestDto.type", source = "accountRequest.type")
    @Mapping(target = "accountRequestDto.name", source = "accountRequest.name")
    @Mapping(target = "accountRequestDto.accountNo", source = "accountRequest.accountNo")
    @Mapping(target = "accountRequestDto.dateTime", source = "accountRequest.dateTime")
    @Named(value = "toAccountRequestStatusDto")
    AccountRequestStatusDto toAccountRequestStatusDto(AccountRequestStatus accountRequestStatus);

}

您在 class 级别的 @Transactional 注释被标记为 readonly = true。这会阻止任何数据库持久性,因此不会保存。

但是,在您的第一项服务中,您有 @Transactional(propagation = Propagation.REQUIRES_NEW),其中 creates/propagates 是 readonly 交易范围之外的净新交易。因此,您的第一个服务能够持久保存到数据库中,而您的第二个则不能。

我建议删除 readonly = true 或可能将 propagation = Propagation.REQUIRES_NEW 添加到第二个服务的事务中。

我通过如下方式将 saveSuccessfulAccountRequest 添加到 AccountRequestServiceImpl 服务并在 SyncLegacyAccountServiceImpl 服务中调用 saveSuccessfulAccountRequest 来解决问题。这种做法的要点是saveSuccessfulAccountRequest应该有propagation = Propagation.REQUIRES_NEW,没有这个不行!!!但实际上,我不确定为什么它应该是 propagation = Propagation.REQUIRES_NEW :)))

@Service
@Transactional(readOnly = true)
public class AccountRequestServiceImpl implements IAccountRequestService {

    @Value("${mq.event_argument_key}")
    private String eventArgumentKey;

    private final AccountRequestRepository accountRequestRepository;
    private final AccountRequestStatusServiceImpl mqRequestStatusService;
    private final EventToAccountRequestEntityMapper eventMapper;
    private final AccountRequestMapper accountRequestMapper;

    @Autowired
    public AccountRequestServiceImpl(AccountRequestRepository accountRequestRepository,
                                     AccountRequestStatusServiceImpl mqRequestStatusService,
                                     EventToAccountRequestEntityMapper eventMapper,
                                     AccountRequestMapper accountRequestMapper) {
        this.accountRequestRepository = accountRequestRepository;
        this.mqRequestStatusService = mqRequestStatusService;
        this.eventMapper = eventMapper;
        this.accountRequestMapper = accountRequestMapper;
    }


    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)// to prevent rollback for whole receive method in mq service
    public void saveAccountRequest(Event event) {
        AccountRequest accountRequest = eventMapper.eventToAccountRequest(event, eventArgumentKey);
        accountRequestRepository.save(accountRequest);
        AccountRequestDto accountRequestDto = accountRequestMapper.toDto(accountRequest);
        saveAccountRequestStatus(accountRequestDto, AccountRequestStatusEnum.INITIAL);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveSuccessfulAccountRequest(Event event) {
        AccountRequest accountRequestByMessageId = accountRequestRepository.findByMessageId(event.getMessageId());
        AccountRequestDto accountRequestDto = accountRequestMapper.toDto(accountRequestByMessageId);
        saveAccountRequestStatus(accountRequestDto, AccountRequestStatusEnum.SUCCESS);
    }

    private void saveAccountRequestStatus(AccountRequestDto accountRequestDto, AccountRequestStatusEnum status) {
        AccountRequestStatusDto accountRequestStatusDto = new AccountRequestStatusDto();
        accountRequestStatusDto.setAccountRequestStatusEnum(status);
        accountRequestStatusDto.setAccountRequestDto(accountRequestDto);
        mqRequestStatusService.saveAccountRequestStatus(accountRequestStatusDto);
    }
}


@Service
@Transactional(readOnly = true)
public class SyncLegacyAccountServiceImpl implements ISyncLegacyAccountService {

    @Value("${mq.event_argument_key}")
    private String eventArgumentKey;
    @Value("${range.account_title_code}")
    private String accountTitleCode;

    private static final Logger log = LoggerFactory.getLogger(SyncLegacyAccountServiceImpl.class);

    private final CustomerRepository customerRepository;
    private final CustomerPersonRepository customerPersonRepository;
    private final CustomerCompanyRepository customerCompanyRepository;
    private final AccountMapRepository accountMapRepository;
    private final IMQService iMQService;
    private final IAccountRequestService iAccountRequestService;
    private final GalaxyApi galaxyApi;
    private final RangeApi rangeApi;
    private final CustomerMapper customerMapper;
    private final InquiryMapper inquiryMapper;

    public SyncLegacyAccountServiceImpl(CustomerRepository customerRepository,
                                        CustomerPersonRepository customerPersonRepository,
                                        CustomerCompanyRepository customerCompanyRepository,
                                        AccountMapRepository accountMapRepository,
                                        @Lazy IMQService iMQService,
                                        IAccountRequestService iAccountRequestService,
                                        GalaxyApi galaxyApi,
                                        RangeApi rangeApi,
                                        CustomerMapper customerMapper,
                                        InquiryMapper inquiryMapper) {
        this.customerRepository = customerRepository;
        this.customerPersonRepository = customerPersonRepository;
        this.customerCompanyRepository = customerCompanyRepository;
        this.accountMapRepository = accountMapRepository;
        this.iMQService = iMQService;
        this.iAccountRequestService = iAccountRequestService;
        this.galaxyApi = galaxyApi;
        this.rangeApi = rangeApi;
        this.customerMapper = customerMapper;
        this.inquiryMapper = inquiryMapper;
    }


    @Override
    public void handleSyncRequest(Event event) {
        saveSuccessfulAccountRequestStatus(event);
        try {
            CustomerAccountResponseDto galaxyData = getGalaxyData(event);
            Optional<AccountMapEntity> optAccountMapEntity = accountMapRepository.findByNewAccountNo(event.getArgument().get(eventArgumentKey).toString());
            if (optAccountMapEntity.isPresent()) {
                //openAccount(event);
            }
        } catch (Exception exception) {
            handleEventRequestException(exception, event);
        }
    }

}