将 spring boot 2.x 升级到 3.x(涉及从 hibernate 5.x 升级到 6.x)后,我收到此错误:
org.springframework.orm.jpa.JpaSystemException: Duplicate row was found and `ASSERT` was specified
(下面的例子中
MyEntity
称为Question
)
https://github.com/RobertHeim/so-duplicate-row
我的测试运行良好,但最后一行抛出异常:
Language lang = langRepository.findOneByLocale(locale).orElse(null);
final var id = new MyEntityI18nId(
em.getReference(MyEntity.class, id),
lang
);
var byIds = myRepo.findAllById(Set.of(id));
assertThat(byIds).hasSize(1);
// fine until here
myRepo.findById(id); // throws org.springframework.orm.jpa.JpaSystemException: Duplicate row was found and `ASSERT` was specified
我不明白这个例外意味着什么。它首先感觉像是一个错误,因为我认为它告诉我在尝试运行时数据库中有重复行
findById(id)
,但我证明在语句之前没有重复行。此外,数据库上有一个独特的约束来保证它,所以不知何故这不可能是真的。
现在我想知道该消息的实际含义。
我尝试创建一个最小的可重现示例,结果发现当我从另一个表中删除数据时错误消失了。从那里我能够理解,这些相关数据是急切地获取的,并且它本身也急切地获取
MyEntity
(参见示例中的QuestionI18n.QuestionI18nId.question
)。我是否理解正确,该错误想告诉我,我现在在结果集中(而不是在数据库中)得到了同一行?或者它想告诉我什么?
因此,通过进一步调试,我发现
ListResultsConsumer
及其定义 UniqueSemantic
的枚举 ASSERT
的定义评论为:
应用内存中的重复检查,当发现重复项时抛出 HibernateException
它已在 hibernate 6.x 中引入,这解释了为什么我没有碰巧在 5.x 上看到该错误。它还让我更有信心,我对错误消息的理解是正确的 - hibernate 尝试告诉我,在查询期间,负载图想要查询一个实体两次。但这是一个严重错误还是只是一个提示?因为我希望 hibernate 能够优化查询,而不是真正加载它两次。不管怎样,我不太有信心合适的修复是什么。
而且我仍然不太明白为什么这个错误发生在
findById
而不是 findByIds
,据我所知,对于相同的输入 id 应该表现完全相同。
我刚刚解决了一个类似的问题,我遇到了同样的错误,由于这里没有答案,我将添加我的解决方案,希望它能为您指明正确的方向。
就我而言,错误已经在
findAll(Pageable pageable)
方法上抛出,并且您可能会注意到我正在使用分页。我的错误仅针对某些 Pageable
抛出,因此有时该方法运行良好,有时会抛出错误。
我的问题出在
MyEntity
。我设置了以下代码:
@Entity
@Table("MyEntity")
public class MyEntity {
...
@OneToOne
@JoinColumn(name = "ID", referencedColumnName = "ID")
private MyOtherEntity otherEntity;
}
问题在于,与 MyOtherEntity 的连接实际上应该是
@OneToMany
。由于 @OneToOne
默认情况下是急切加载的(就像 @OneToMany
一样),这意味着当我执行 myEntityRepo.findAll(pageable)
时,如果任何 MyEntity
包含 2 个或更多 MyOtherEntity
,则会抛出错误。只要页面上的所有 MyEntity
包含 1 个或更少 MyOtherEntity
,就不会引发异常。
所以,我的解决方案是将其更改为以下内容:
@OneToMany(fetch = FetchType.LAZY) //LAZY fetch type for good measure, not necessary
@JoinColumn(name = "ID", referencedColumnName = "ID")
private List<MyOtherEntity> otherEntities;