很难提供这方面的示例代码。让我试着用一些伪代码来解释它。如果需要的话,我会给出一个可行的例子。
首先,我将演示代码的一个变体,它可以按预期工作。在这个变体中,我迭代了一个实体列表。对于每个实体,我调用一个服务方法,该方法创建一个新事务并向DB写入一些内容。如果发生异常,则在该服务方法中完成的所有工作将按预期保留在数据库中,因为这些工作是在独立于外部事务的新事务中完成的。
下面是类/方法的伪代码,它迭代实体列表并为每个实体调用服务方法。
@Transactional
public myMethod()
{
List<MyEntity> myEntities = myRepo.findAllBySomething("foo");
foreach(MyEntity myEntity : myEntities)
{
myService.doSomeWork()
}
}
下面是创建一个新事务来执行某些工作的服务方法。
@Service
public MyService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomeWork() {
SomeOtherEntity someOtherEntity = new SomeOtherEntity();
...
someOtherRepo.save(someOtherEntity);
}
}
上面的示例按预期工作。如果遇到异常,在MyService.doSomeWork()调用中完成的所有工作都将保留在数据库中,因为这些工作是在独立的事务中完成的。
这就是问题所在。我迭代的实体数量非常大,导致了内存问题。因此,我将代码改为使用Stream,而不是在列表中检索它们。repo被更改为返回Stream而不是List,代码更改为类似于以下内容...
@Transactional
public myMethod()
{
try (Stream<MyEntity> myStream = myRepo.findAllBySomething("foo")) {
// Get an iterator to allow us to step through each row.
Iterator<MyEntity> myStreamIterator = myStream.iterator();
// Loop through each row
while (myStreamIterator.hasNext()) {
// Get the next row
MyEntity myEntity = myStreamIterator.next();
myService.doSomeWork();
}
}
}
现在,如果抛出一个异常,那么所有MyService.doSomeWork()调用中完成的工作都不在数据库中!很奇怪。我已经打开了org. springframework. transaction的跟踪日志记录。在这两个代码示例中,我可以看到每次调用MyService.doSomeWork()时都创建了一个新事务。但是当抛出异常时,由于某种原因,在MyService.doSomeWork()中完成的插入在基于Stream的版本中丢失,而在基于List的版本中没有丢失。
如果在流打开时创建新事务,它以某种方式绑定到外部事务,这是已知的行为吗?或者这可能是一个Spring的bug?
1条答案
按热度按时间g2ieeal71#
我写了一小段代码来说明这个问题。这使我更容易测试和查明问题。我发现在独立的Sping Boot 应用程序中运行此代码时,一切都按预期工作。当我将代码部署到JBoss服务器时,问题出现了。
我能想出一个解决这个问题的办法。我怀疑这更像是一种变通办法,而不是解决办法。但问题似乎出在JBoss的“缓存连接管理器”上。默认情况下,它在数据源上处于启用状态。关闭“缓存连接管理器”解决了这个问题。我发现了一些老问题,Spring的人把一些事务性问题归咎于缓存连接管理器。我可能会向Redhat和Spring提交关于这个问题的罚单,看看是否有人愿意站出来承担责任。我怀疑两者都不会,但至少希望我能为下一个陷入困境的可怜的灵魂提供一些可见性。
在有人找到问题的根源之前,要关闭缓存连接管理器,请更新数据源并将use-ccm设置为“false”,如下所示...
或类似地用于XA数据源...