У меня есть три таблицы Account, AccountStatus и AccountStatusCodes. AccountStatusCodes - это главная таблица, в которой есть фиксированные коды статуса учетной записи. Таблица счетов будет иметь другой счет в списке. Таблица AccountStatus - это своего рода таблица истории, когда пользователь выполняет какое-либо действие с учетной записью, старый статус учетной записи обновляется с помощью флага N и будет добавлен новый статус учетной записи. Таким образом, для каждой операции в учетной записи состояние учетной записи будет вести историю с отметкой времени, идентификатором пользователя, который обновил состояние, и флагом Y / N.
Взаимосвязь - отношение многие ко многим из таблицы Account в код AccountStatusCodes нарушается следующим образом
1. Одна учетная запись может иметь несколько AccountStatus - Account -> один ко многим -> AccountStatus
2. Один AccountStatusCodes может иметь несколько AccountStatus - AccountStatusCodes -> один ко многим -> AccountStatus
Код сущности JPA - фактический код не разрешен для совместного использования, следовательно, общий доступ к изменяемому коду объясняет сценарий.
Класс: AccountEntity
@DynamicUpdate(value = true)
public class AccountEntity{
private Long accountKey;
//Skipped other variables and getter setters
}
Класс: AccountStatusEntity
public class AccountStatusEntity{
@Id
@SequenceGenerator(name = "ACCOUNT_STATUSES_DOCUMENT_STATUSKEY_GENERATOR" , sequenceName = "AS_SEQ")
@GeneratedValue(generator = "ACCOUNT_STATUSES_DOCUMENT_STATUSKEY_GENERATOR")
@Column(name = "ACCOUNT_STATUS_KEY" , unique = true , nullable = false , precision = 10)
private Integer accountStatusKey;
@ManyToOne
@JoinColumn(name = "ACCOUNT_STATUS_CODE_KEY" , nullable = false)
private AccountStatusCodeEntity accountStatusCodeEntity;
@ManyToOne
@JoinColumn(name = "ACCOUNT_KEY" , nullable = false)
private AccountEntity accountEntity;
@Column(name = "CURRENT_STATUS_IND" , nullable = false , length = 1)
private String currentStatusInd;
//skipped other variables and getter setters
}
Класс: AccountStatusCodeEntity
public class AccountStatusCodeEntity{
@Id
@Column(name = "ACCOUNT_STATUS_CODE_KEY")
@GeneratedValue(generator = "ASC_SEQ")
@SequenceGenerator(name = "ASC_SEQ", sequenceName = "ASC_SEQ")
private Integer accountStatusCodeKey;
@OneToMany(mappedBy = "accountStatusEntity")
private List<AccountStatusEntity> accountStatuseEntitiess;
}
В приложении каждый пользователь выполняет некоторую операцию с учетной записью, и каждый раз, когда статус учетной записи увеличивается до следующего AccountStatusCode, он сохраняет историю в таблице AccountStatusEntity, изменяя существующее состояние на флаг N и вставляя новый статус с отметкой времени, идентификатором пользователя и флаг Y.
Таким образом, с помощью @Transaction будут выполняться две операции с БД, во-первых, обновить старое состояние на N и вставить новое состояние с Y.
Метод, который делает это, имеет следующий код.
private void moveAccountStatus(final Long accountKey, final String loggedInUserID, final Integer currentStatus,
final Integer nextStatus) {
//Search existing account status with accountKey
// here I have skipped the code which will pull latest status entity from the history table based on date
final AccountStatusEntity accountStatusEntity =
accountDAO.findAccountStatusByAccountKey(accountKey);
AccountEntity accountEntity;
AccountStatusEntity newAccountStatusEntity;
if (accountStatusEntity != null) {
accountEntity = accountDAO.findAccountByAccountKey(accountKey);
accountStatusEntity.setCurrentStatusInd(Constants.NO);
accountStatusEntity.setModifiedBy(loggedInUserID);
accountStatusEntity.setModifiedTs(new Date());
accountStatusEntity.setAccountEntity(accountEntity);
//The update method here is calling the JPA merge() to update the records in the table.
accountDAO.update(accountStatusEntity);
//Create new object of AccountStatusEntity to insert new row with the flag Y
newAccountStatusEntity = new AccountStatusEntity();
//Set the next status
newAccountStatusEntity.setAccountStatusCodeEntity(
(AccountStatusCodeEntity) accountDAO.getById(AccountStatusCodeEntity.class, nextStatus));
newAccountStatusEntity.setCurrentStatusInd(Constants.YES);
newAccountStatusEntity.setCreatedBy(loggedInUserID);
newAccountStatusEntity.setCreatedTs(new Date());
newAccountStatusEntity.setAccountEntity(accountEntity);
//The create() method is also calling the JPA merge() method. The Id is null hence it will consider a insert statement and will insert a new record.
accountDAO.create(newAccountStatusEntity);
}
}
Этот процесс отлично работает на 99% в Production, но иногда этот метод создает дублирующую запись в таблице AccountStatusEntity с той же отметкой времени, идентификатором пользователя и флагом Y. Метод не обновил запись с флагом N или из-за некоторой проблемы старая запись также обновляется с флагом Y.
Table: AccountStatus
___________________________________________________________________________________________________________________
accountStatusKey | accountKey | accountStatusCodeKey | currentStatusInd | Created_TS | Created_BY
___________________________________________________________________________________________________________________
| | | | |
1 | 5 | 3 | Y | A | 4/9/2018
2 | 5 | 3 | Y | A | 4/9/2018
___________________________________________________________________________________________________________________
В приведенной выше таблице показаны записи, которые создаются после выполнения метода. AccountStatusKey 1 должен иметь accountStatusCodeKey 2 (старый код состояния 2, а следующий код состояния 3) и currentStatusInd N. Но каким-то образом метод слияния вставляет сюда две записи.
Одно решение, которое я могу сделать, чтобы создать уникальное ограничение для столбца, чтобы избежать этой ситуации, но я просто хотел знать, почему метод слияния JPA создает эту проблему.
Другое решение, которое я не пробовал, - это использовать метод persist () JPA во время вставки вместо merge ().
Эту проблему трудно воспроизвести в среде разработки, так как она работает 99% времени. Кроме того, пользователи сообщают об этой проблеме очень поздно, поэтому не могут отследить файлы журнала.
Согласно журналам в среде dev, при выполнении транзакции JPA сначала вставляется оператор вставки, а затем в файл журнала записывается запрос на обновление. Я не уверен, как следует порядок выписки в случае транзакции.
Я знаю, что вопрос слишком длинный, но я просто хотел дать точную информацию для понимания этой проблемы. ТИА.