线程中没有可用的事务 - 无法处理删除调用



同一主题有几个问题,但似乎都不适合我的问题。

我们的数据库有一些清理任务,如果我们不手动拆分它们,它们可能会导致非常长的事务(小时!部分原因是需要从实际上不需要在事务中运行的其他位置清除相关数据,部分原因是需要一些查询来断言需要删除哪些数据,部分原因是该操作可能会影响数十万行。

因此,仅使用@Transactional注释并不能解决问题,我必须依靠使用 TransactionTemplates 手动定义事务范围,以获得不会锁定表的漂亮、受限和简短的事务。

在当前的重构过程中,我之前已经看到过与上述错误类似的错误,并且可以识别并消除原因而不会有太大问题(在错误的时间刷新实体管理器,或在其事务上下文之外访问实体,诸如此类(。

但是现在我只剩下一个让我跺脚的例子,我不知道为什么会发生这种情况。注释掉其他所有内容后,我留下的代码非常简单。我正在使用的事务模板实例:

private val readTransaction = org.springframework.transaction.support.TransactionTemplate(
transactionManager,
DefaultTransactionDefinition().apply {
isReadOnly = true
propagationBehavior = TransactionDefinition.PROPAGATION_NEVER
})
private val writeTransaction = org.springframework.transaction.support.TransactionTemplate(
transactionManager,
DefaultTransactionDefinition().apply {
isReadOnly = false
propagationBehavior = TransactionDefinition.PROPAGATION_NEVER
})

然后是产生错误的几行代码:

internal fun emptyTrashedImages(olderThan: LocalDateTime) {
// extract all trashsets
val trashSets = readTransaction.execute {
imageSetRepository.findAllByCameraIdIsNull()
.filter { it.time < olderThan }
.map { it.id }
}!!
// Other operations using trashSets that I commented out and still receive the error
// this is where the error happens  
writeTransaction.execute { imageSetRepository.deleteByIdIn(trashSets) }
}

我正在读出在一个事务中需要删除的内容的 ID,并将它们存储为简单的 Long 列表(如您所见,我没有保留任何实体(。

然后我打开一个新的写入事务,在其中我简单地调用一个存储库方法:

interface ImageSetRepository : CrudRepository<ImageSet, Long> {
// not the only method in here, but the only one invoked to produce the error
fun deleteByIdIn(ids: List<Long>)
}

仅此而已。我在过程中的其他点对其他表做基本上相同的事情,它可以毫无问题地工作。但是当我调用该存储库方法时,我只是得到"没有具有实际事务可用于当前线程的 EntityManager - 无法可靠地处理'删除'调用">

谁能告诉我如何解决这个问题?请不要说"只使用@Transactional",除非你能告诉我一种方法可以将操作干净地分成不同的事务,这些事务在下一个事务之前完成(不是挂起,这只会导致在某处锁定表(。

编辑:

我通过传递我知道数据库中存在的 ID 的简短列表来进一步简化代码,如下所示:

internal fun emptyTrashedImages(olderThan: LocalDateTime) {
writeTransaction.execute {  imageSetRepository.deleteAllByIdIn(listOf(19923415, 19923416)) }
}

仍然出现错误。但是,如果我将该行更改为仅删除一行,例如

writeTransaction.execute { imageSetRepository.deleteById(19923415) }

它有效。因此,关于Spring-Data如何实现deleteAllByIdIn((方法,在这种情况下不知何故不起作用。由于其他操作的结构与使用我自己定义的查询的方式相同,我想我接下来会尝试一下。

结果:不,也不起作用。好吧,措辞略有不同的错误消息(删除需要事务(。换句话说,实际上看起来此时不存在任何事务。这很奇怪,因为我正在一个内部执行查询。什么给?

事实证明,即使您手动管理事务,该方法仍然需要@Transactional注释。在这种情况下,我使用了@Transactional(propagation = Propagation.NEVER,因为我不希望任何人通过正在进行的事务调用它(。

不过,这里还有另一个陷阱,因为调用此方法只是忽略该注释,因为未调用代理。由于我明确希望没有任何正在进行的事务,这意味着必须从另一个类调用执行事务的方法。这个烦人的细节需要我稍微重组我的代码,但它是可以生存的。

它有一定的逻辑,但也有些令人困惑。为什么我需要在手动调用事务模板的方法上进行@Transactional注释?欢迎对此提出任何意见。

最新更新