I have two entities as below and the main problem is when I want to update the AccountRequestStatus
entity. I save the integer enum code of AccountRequestStatusEnum
in the database to persist the AcountRequest status in the whole application.
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; } }
The first time that an account request comes from MQ my to application, I save the initial
code of AccountRequestStatusEnum
in service like below. This status persists properly and there is no problem, but when I want to update the AccountRequestStatus
and add a new success
code of AccountRequestStatusEnum
(in another service) it won’t be saved in DB.
This is the first service that is called after receiving the account request and saving the initial
code.
@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); } }
This is the second service that should save the success
code of AccountRequestStatus
.
@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); }
Advertisement
Answer
I fixed the issue by adding the saveSuccessfulAccountRequest
to the AccountRequestServiceImpl
service as below, and calling the saveSuccessfulAccountRequest
in SyncLegacyAccountServiceImpl
service. The main point of this approach is that saveSuccessfulAccountRequest
should have propagation = Propagation.REQUIRES_NEW
, without this, it does not work!!! But actually, I am not sure why it should be 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); } } }