【发布时间】:2019-02-09 17:11:45
【问题描述】:
我有三个表 Account、AccountStatus 和 AccountStatusCodes。 AccountStatusCodes 是一个主表,它具有固定的帐户状态代码。帐户表将列出不同的帐户。 AccountStatus 表是一种历史表,每当用户对账户执行一些操作时,旧的账户状态会用标志 N 更新,并插入新的账户状态。因此,在对 Account 的每次操作中,Account 状态都会保留带有时间戳、更新状态的用户 ID 和 Y/N 标志的历史记录。
关系 - 从 Account 表到 AccountStatusCodes 代码的多对多关系像这样被破坏 1.一个Account可以有多个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 时,它通过修改现有状态以标记 N 并插入带有时间戳、用户 ID 和标记 Y。
因此,@Transaction 将执行两个 DB 操作,第一个是将旧状态更新为 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%,但有时这种方法会在表 AccountStatusEntity 中创建具有相同时间戳、用户 ID 和标志 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。但不知何故,merge 方法在这里插入了两条记录。
我可以制定一个解决方案来在列上创建唯一约束,以便避免这种情况,但我只是想知道为什么 JPA 的合并方法会产生这个问题。 我没有尝试过的另一个解决方案是在插入时使用 JPA 的 persist() 方法而不是 merge()。
这个问题很难在开发环境中重现,因为它 99% 的时间都可以工作,而且用户很晚才报告这个问题,因此无法跟踪日志文件。 根据开发环境的日志,当 JPA 事务被执行时,插入语句首先被记录,然后更新查询被记录在日志文件中。我不确定在 Transaction 案例中语句顺序是如何遵循的。
我知道这个问题太长了,但我只是想给出理解这个问题的确切背景。 TIA。
【问题讨论】:
标签: java oracle jpa oracle11g spring-data-jpa