JpaRepository 抛出 EntityNotFoundException 而不是返回 null

JpaRepository throwing EntityNotFoundException instead of returning null

我遇到了这个非常不寻常的错误。 我有这个 JpaRepository<SomeObject, Long>

public interface SomeRepository extends JpaRepository<SomeObject, Long> {
      @Query("select someObject from SomeObject someObject where someObject.id = ?1")
     public SomeObject getSomeObject(int id);
}

它工作正常,当我尝试获取 ID 不存在的 SomeObject 时,它只是 return 为空,我处理它并继续进行。

但是,当我引入我的应用程序的多个实例(比如 2 个),并将它们隐藏在负载均衡器后面时。我执行此操作(作为脚本),其中 retrieves/creates/deletes SomeObject 重复。

当我只有一个实例时,我 运行 执行以下操作的脚本:检索 (returns null) -> 创建 SomeObject,删除 SomeObject 并重复检索 (returns null) -> ...等

一切正常,符合预期^

在我的多实例设置中,负载均衡器将请求重新路由到可互换的实例。 意思是,操作现在按以下顺序进行:

实例(1)检索(returns null)

实例(2)创建SomeObject

实例(1)删除SomeObject

并且在下一次迭代中,观察到一些奇怪的行为!

实例(2)检索(这里不是returning null,Spring突然抛出以下异常):

Caused by: org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find somePackage.SomeObject with id 1; nested exception is javax.persistence.EntityNotFoundException: Unable to find somePackage.SomeObject with id 1
 at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:389) ~[spring-orm-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
 at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:246) ~[spring-orm-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
 at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:488) ~[spring-orm-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
 at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59) ~[spring-tx-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
 at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213) ~[spring-tx-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
 at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147) ~[spring-tx-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
 at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133) ~[spring-data-jpa-1.11.3.RELEASE.jar!/:?]
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
 at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
 at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57) ~[spring-data-commons-1.13.3.RELEASE.jar!/:?]
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
 at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
 at com.sun.proxy.$Proxy201.getSomeObject(Unknown Source) ~[?:?]
 at mypackage.getSomeObject(MyClass.java:111) ~[]

几周来我一直在用头撞墙试图解决这个问题,但我不明白为什么会抛出这个异常 EntityNotFoundException。

异常是正确的,我不明白为什么它不像往常一样 return null。

更新:

public class SomeObject {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;

}

不是现成的完整答案,而是一些关于如何调试此类问题的说明(对于评论来说太长了)。

  1. 确保您拥有 Spring Data JPA、Spring Data Commons 和 Spring Data ORM 的源代码并在 [=47] 中正确配置=].

  2. 获取您遇到的异常的完整堆栈跟踪(不仅仅是您发布的部分)。

  3. 根据堆栈跟踪,优雅地在代码中放置断点。

  4. 运行调试器不抛出异常的简单场景。记下你命中了哪个断点,没有命中哪个断点。

  5. 删除除最后一个以外的所有断点。在您命中的最后一个和错过的第一个之间添加更多断点。

  6. 重复直到这两个断点仅相隔一个堆栈帧。

  7. 调试两个场景下两个断点之间的剩余代码,观察差异。

  8. 回到这里,告诉我们您发现了什么,作为问题的更新或答案可能包括 link 问题 and/or 拉取请求。

如评论中所述,启用了二级缓存(EhCache?)。

这有这样的效果,在问题的例子中,instance(2) 认为它在第二次检索进来时记住了创建请求,但随后惊讶地没有在数据库中找到匹配项。