我有三个表Account,AccountStatus和AccountStatusCodes。 AccountStatusCodes是一个主表,具有固定的帐户状态代码。帐户表将列出不同的帐户。 AccountStatus表是一种历史记录表,每当用户对帐户执行某些操作时,旧帐户状态将用标记N更新,并将插入新帐户状态。因此,在对帐户进行的每次操作中,帐户状态都会保留带有时间戳的历史记录,更新状态的用户ID和Y / N标志。
关系-从客户表到AccountStatusCodes代码的多对多关系像这样被破坏了 1.一个帐户可以有多个AccountStatus-帐户->一对多-> 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和新状态的新状态来维护AccountStatusEntity表中的历史记录。标记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);
}
}
此过程在Production中可以正常工作99%,但有时此方法在表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)和currentStatusIndN。但是不知何故,合并方法会在此处插入两个记录。
我可以采用一种解决方案在列上创建唯一约束,从而避免出现这种情况,但是我只是想知道为什么JPA的merge方法会创建此问题。 我尚未尝试过的另一种解决方案是在插入时使用JPA的persist()方法,而不是merge()。
这个问题很难在开发环境中重现,因为它有99%的工作时间,而且用户报告此问题的时间很晚,因此无法跟踪日志文件。 根据开发环境上的日志记录,当执行JPA事务时,将首先记录insert语句,然后将更新查询记录在日志文件中。我不确定交易案例中的陈述顺序如何。
我知道这个问题太长了,但是我只想提供理解此问题的确切背景。 TIA。
答案 0 :(得分:0)
我已经修改了代码,而不是直接更新和更新子记录,而是创建和更新子实体并将其添加到父实体列表中,并要求JPA保存或更新。因此,根据ID(它是一个null或具有有效的id),父级将决定是添加还是更新子级。我将代码移至Prod环境,从过去2-3周开始,我没有看到任何重复的行问题。
如果我再次看到此问题,我会及时通知你。谢谢!
答案 1 :(得分:0)
这是旧版Hibernate中的已知问题。我的应用程序使用的是休眠版本4.2.8.Final。
报告的问题链接-https://hibernate.atlassian.net/browse/HHH-6776
此问题已在5.0.8.Final版本中修复。我已经更新了我的Maven依赖关系,并将继续进行测试。
链接:https://hibernate.atlassian.net/browse/HHH-5855
有关此问题的更多信息-
https://javacodinggeeks.blogspot.com/2015/05/hibernate-inserts-duplicate-child-on.html