Home > Enterprise >  Problem in updating the entity with unidirectional @ManyToOne relation
Problem in updating the entity with unidirectional @ManyToOne relation

Time:10-28

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);

}

CodePudding user response:

Your @Transactional annotations at class level are marked as readonly = true. This prevents any database persistence, hence no saving.

However, in your first service you have @Transactional(propagation = Propagation.REQUIRES_NEW), which creates/propagates a net new transaction outside the scope of the readonly transaction. Thus your first service is able to persist to the database and your second is not.

I would suggest removing the readonly = true or potentially adding the propagation = Propagation.REQUIRES_NEW to the second service's transaction.

CodePudding user response:

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);
        }
    }

}
  • Related