Spring 应用程序中的多个 entityManager。重复对象问题的持久性

Multiple entityManager in Spring application. Persistence of duplicate objects issue

我的 Spring 组件收到来自客户端的请求,向 Web 服务询问一些数据并将接收到的对象保存到数据库中。 我识别所有对象并且只保存新的对象。

当客户端同时发出两个或更多相同的请求时(或者由于甚至不同的用户请求,我从网络服务接收到相同的对象),就会出现问题

这里用持久性描述问题的一些细节。对于每个客户端请求,我的组件在一个单独的线程中开始执行,我得到一个新的 entityManager,开始一个事务,从网络服务接收数据,然后我识别对象并使用持久化新对象当前事务中给定的 entityManager。

如果在单独的事务中我从网络服务收到相同的对象,并且如果它们是新的尚未在数据库中的对象,我无法识别它们 不-已提交的事务,因此它们会保留在 all 个事务中。 然后所有重复的对象将被提交并保存到数据库。

在这种情况下,什么是好的解决方案?即使在不同的交易中,有什么方法可以正确地 识别 新对象吗?或者可以应用哪些方法?

可能 Spring 提供了一些管理事务或实体管理器的方法,以便它可以帮助解决这个问题...

。当然,我可以使用数据库工具来避免保存重复的对象,但在这种情况下,这不是一个很好的解决方案。

  • 保存前检查数据库中是否存在对象。
  • 使用@UniqueConstraint or @Column(unique = true)防止重复行,适当处理异常。
  • 使用@Version 管理现有实体的并发修改。更多关于乐观和悲观锁定:Chapter 5. Locking. Related discussions: Hibernate Automatic Versioning and When to use @Version and @Audited in Hibernate?
  • 您可以使用线程锁/同步机制来确保对同一用户的请求按顺序发生。但是,如果您的服务在 运行 中超过 1 个节点,这将不起作用。

所以我的解决方案如下:

  1. 使事务非常小并分别提交每个对象。
  2. 在数据库中设置唯一约束以防止重复 对象。这一点对我们没有太大帮助,但需要第 3 点。
  3. 我们在 try-catch 块中插入的每个 commit() 方法。如果我们尝试 在并行事务中提交重复对象然后我们将收到一个异常,在 catch 块中我们可以检查数据库,select 已经存在的对象并进一步使用它。

例子:

boolean reidentifyNeed = false;

try {

    DofinService.getEntityManagerThreadLocal().getTransaction().begin();

    DofinService.getEntityManagerThreadLocal().persist(entity);

    try {

        DofinService.getEntityManagerThreadLocal().getTransaction().commit();

        //if commit is successfull
        entityIdInDB = (long) entity.getId();

        DofinService.getEntityManagerThreadLocal().clear();

    } catch (Exception ex) {

        logger.error("Error committing " + entity.getClass().getSimpleName() + " in DB. Possibly duplicate object. Will try to re-identify object. Error: " + ex.toString());

        reidentifyNeed = true;

    }

    if(reidentifyNeed){
        //need clear entityManager, because if duplicated object was persisted then during *select* an object flush() method will be executed and it will thrown ConstrainViolationException 
        DofinService.getEntityManagerThreadLocal().clear();

        CheckSimilarObject checkSimilarObject = new CheckSimilarObject();

        long objectId = checkSimilarObject.checkObject(dofinObject);

        logger.warn("Re-identifying was done. EntityId = " + objectId);

        entityIdInDB = objectId;
    }

} catch (Exception ex) {
    logger.error("Error persisting and commiting object: " + ex.toString());
}