"Row was updated or deleted by another transaction" - 但它在同一个事务中被删除

"Row was updated or deleted by another transaction" - but it's deleted in the same transaction

在我的应用程序中,我使用带 Spring 数据的 Hibernate 将测量数据导入数据库。源可以使用现有的数据库数据为时间戳提供数据,以便进行修改。因此,在再次写入条目之前,将删除该时间段(由源数据给出)内的所有条目。

在现有数据的情况下,我在 saveAll() 上得到了这个异常:

"Row was updated or deleted by another transaction" (or unsaved-value mapping was incorrect)

但是,应该在 相同的 事务中删除该行,我希望 Hibernate 理解它必须重新 INSERT 条目而不是执行 UPDATE.

存储库方法由 Spring Data 提供。这是我的代码的简化版本:

@Transactional(rollbackFor = Exception.class)
private void import(List<Entry> entries) {
    // ...
    this.measurementRepo.removeAllByTimeIsBetween(firstDt, lastDt);
    this.measurementRepo.saveAll(measurements);
}

这里有什么问题?这是不允许的吗?或者这两个操作都属于一个事务是错误的假设吗?但是,它应该可以工作,即使有两个事务。有时间问题吗?是否保证操作按给定顺序执行? Spring数据有问题吗?

我不能在两个单独的事务中执行这些操作(如果这可能是一个解决方案),因为我只想删除行,如果我确定新行也被插入的话。

我针对此问题的解决方法是检测现有行并针对具有匹配时间戳的条目执行更新而不是删除。我不确定这是否可行。

使用 Hibernate 时出现“问题”。 Hibernate 优化语句执行顺序,删除是最后执行的(具体细节我不知道),因此你遇到的问题。

要解决此问题,请使用 deleteInBatch。这样delete会先发生,问题就解决了。我假设你有一个方法 getAllByTimeIsBetween。但请根据您的需要调整以下内容:

@Transactional(rollbackFor = Exception.class)
private void import(List<Entry> entries) {
    // ...
    Object elementsToRemove = this.measurementRepo.getAllByTimeIsBetween(firstDt, lastDt);
    measurementRepo.deleteInBatch(elementsToRemove);

    this.measurementRepo.saveAll(measurements);
}

以下 可以为您提供有关 deleteInBatch 的更多详细信息。这不是同一个问题,但一些答案有一些关于 Spring JPA 和 Hibernate 的细节。

@Transactional 不适用于 bean 内部调用。 Spring 为这些对象创建一个代理,但它无法拦截 class 本身内部的方法调用(例如 this.import() 的调用)。

即使将 import() 移动到单独的 class 之后,它也没有用。那是因为不小心它是 protected 而不是 public。我以为是 public,因为我不记得在 Java 中允许外国 classes 访问其他 classes 的受保护成员(如果它们在同一个包裹)。这就是为什么我错过了 documentation:

中的重点

Another caveat of using proxies is that only public methods should be annotated with @Transactional. Methods of any other visibilities will simply ignore the annotation silently as these are not proxied.

它现在可以工作了,但我仍然不知道为什么它不能用于两个单独的事务。删除数据的一个事务和之后创建新条目的另一个事务应该不是问题。