JPA-merge()重复记录问题

时间:2018-09-04 16:01:42

标签: java oracle jpa oracle11g spring-data-jpa

我有三个表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。

2 个答案:

答案 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

Hibernate inserts duplicates into a @OneToMany collection