有一个实体具有id
和其他一些唯一键 ( extId
)。许多线程(应用程序)将实体插入到数据库中,id
但extId
根据以下逻辑已知:
- 如果
extId
已在数据库中找到,则使用新数据更新实体。 - 如果未找到,则插入一个新条目。
@Transactional(propagation = REQUIRED_NEW)
public A createOrUpdate(A a) {
A b = findByExtId(a.extId, LockModeType.PESSIMISTIC_WRITE);
if (b != null) {
a.id = b.id
}
return save(a); // hibernate merge
}
更新:
@Transactional(propagation = Propagation.SUPPORTS)
public A findByExtId(final Long extId,
final LockModeType lockMode) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<A> cq = cb.createQuery(A.class);
Root<A> root = cq.from(A.class);
cq.where(cb.equal(root.get("extId"), cb.parameter(Long.class, "extId")));
TypedQuery<A> query = entityManager.createQuery(cq);
query.setParameter("extId", extId);
query.setLockMode(lockMode);
List<A> list = query.getResultList();
if (!list.isEmpty()) {
return list.get(0);
}
return null;
}
@Transactional(propagation = Propagation.SUPPORTS)
public A save(A t) {
return entityManager.merge(t);
}
问题是这段代码在极少数情况下仍会extId
多次插入相同的实体。可能是什么原因?
PostgreSQL 数据库。
如果两个线程(具有相同的 extID)执行 A b = findByExtId(a.extId, LockModeType.PESSIMISTIC_WRITE); 并且数据库还没有具有此 extID 的行,那么它们都将插入一个新行。
如果你有很多线程,那么你将无法使用保存、更新、合并函数来解决这个问题,因为每个线程都有自己的 Hibernate 会话缓存。
如果您有许多线程,但只有一个应用程序,那么您可以阻止该方法或使用聚合休眠会话的二级休眠缓存。
如果你有很多线程和很多应用程序,那么你可能需要连接数据库级别 - 在表中对 extID 字段的值的唯一性进行约束,如果尝试插入双精度,则约束将引发错误,在 java 中捕获错误,找到现有行并更新它。
因为使用不同的会话命令进行更新。
session.update(a);
或者session.merge(a);