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 个节点,这将不起作用。
所以我的解决方案如下:
- 使事务非常小并分别提交每个对象。
- 在数据库中设置唯一约束以防止重复
对象。这一点对我们没有太大帮助,但需要第 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());
}
我的 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 个节点,这将不起作用。
所以我的解决方案如下:
- 使事务非常小并分别提交每个对象。
- 在数据库中设置唯一约束以防止重复 对象。这一点对我们没有太大帮助,但需要第 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());
}