【问题标题】:JPA - merge() duplicate record issueJPA - 合并()重复记录问题
【发布时间】: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


    【解决方案1】:

    我修改了代码,而不是直接更新子记录,而是创建和更新子实体并添加到父实体列表中并要求 JPA 保存或更新。因此,基于 Id,它是 null 还是具有有效 id,父级将决定是否添加或更新子级。我已将代码移至 Prod 环境,并且从过去 2-3 周开始,我没有看到任何重复行问题。

    如果我再次看到此问题,我会及时通知您。谢谢!

    【讨论】:

      【解决方案2】:

      这是旧 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

      Hibernate inserts duplicates into a @OneToMany collection

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-26
        • 2019-10-14
        • 1970-01-01
        • 1970-01-01
        • 2023-03-05
        相关资源
        最近更新 更多