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



我有两个实体如下,主要问题是当我想更新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;
}
}

AccountRequest

@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向应用程序发出帐户请求时,我将AccountRequestStatusEnuminitial代码保存在服务中,如下所示。这种状态保持正常,没有问题,但是当我想更新AccountRequestStatus并添加新的success代码AccountRequestStatusEnum(在另一个服务中)时它不会保存在DB中。

这是收到帐户请求并保存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);
}

类级别的@Transactional注释被标记为readonly = true。这将阻止任何数据库持久化,因此不需要保存。

然而,在你的第一个服务中,你有@Transactional(propagation = Propagation.REQUIRES_NEW),它在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);
}
}
}