现象

现象非常奇怪,同一查询,在其他方法中正常,但是在这个方法中 JSR 303 Bean 校验没有通过,查看后发现返回的所有数据域均为 null,见下图。

一次 Spring Data JPA 查询返回数据属性为 null 排查

数据库里数据是存在的,其他地方的调用返回的数据是正常的,比如下面这里。

一次 Spring Data JPA 查询返回数据属性为 null 排查

这两者之前的调用也都是类似的,查询用户信息,其中用户信息实体与错误代码实体以 @ManyToOne 关联并启用了延迟加载,如下:

一次 Spring Data JPA 查询返回数据属性为 null 排查

排查

现象非常奇怪,两个很类似的的操作,一个返回数据正常,一个返回的数据域均为 null。

偶然尝试把用户信息实体中的懒加载替换为立即加载 FetchType.EAGER,问题就不再出现了,但是仍然不知道根本原因。

很明显这里并没有直接用到延迟加载,错误代码是直接加载的数据库数据,日志打印也能证明这一点,但是关掉延迟加载后就正常,理论上报错的代码查出的数据等于延迟加载的这条数据,所以怀疑是不是延迟加载导致缓存中应有的数据未加载,而二次查询时没打到数据库而是直接访问的 Hibernate 缓存,延迟加载也失效了,从而导致二次查询数据域均为 null,但是这么来说的话又解释不了另一个查询为什么是正常的。

先尝试单步跟一下代码。

一次 Spring Data JPA 查询返回数据属性为 null 排查

看到这里就比较有意思了,明明数据已经能拉出来了,但是在 Hibernate Interceptor 中,并未复制给对应的属性。其次,可以看到得到的对象并不是真正的实体对象,而是实体的代理。

一次 Spring Data JPA 查询返回数据属性为 null 排查

我们来对比一个正常的调用

一次 Spring Data JPA 查询返回数据属性为 null 排查

很明显,这里的域有值而异常的则没有,而且这里得到的是真正的实体对象而非代理。异常的多了一个 $$_hibernate_interceptor 属性,该属性内嵌的属性包含了所需要的数据。

问题点就在这个 Hibernate Interceptor 中。

突然想起来,正确的那个调用在拉取用户信息实体时,是根本加载不到实体的,因为这是一个针对注册的接口,拉出的实体直接就是空的。

而下方有问题的调用,是对于已经注册的用户,问题代码前面的操作是能够拉取到用户实体的,并且也包含了我们错误代码所需的数据,并且执行的是懒加载,而因为是懒加载,导致 Hibernate 创建了代理对象,但是并没有实际数据。

这样就能解释为什么第一个调用正常而第二个却有了报错。

改为立即加载后,数据都到了内存,也就解释了最开始推理上的矛盾点。

顺着上面的推理,现在的问题就是,为什么对于不同仓库层的可以说是没什么关联的 SQL,Hibernate 共享了缓存数据,根据实体 ID 吗?

下面就是研究一下 Hibernate 的缓存策略了。

参考一下这位老哥的 博文,重点是通过 ID 来进行缓存的,以及 StackOverflow,重点是 open-in-view 导致返回了 session 缓存中的代理对象。

问题到这里已经很清晰的,打开了 open-in-view 让 Hibernate 共享的 session 缓存导致得到的是个代理对象,JSR 303 又直接用反射拿的域属性,导致校验失败了。

能想到的修改办法:

  • 关闭 open-in-view,之前所有涉及到延迟加载的地方都可能会涉及到 no session 的问题,需要手动添加 @Transactional 注解维持 session。改动量太大,而且一些拆分的方法合到一起会变得很难看,其次对以后的代码结构约束太多
  • 关闭对于实体层的 JSR 303 校验。不可能不做数据校验的,不考虑
  • 所有延迟加载的地方都修改为立即加载。性能影响太大,不考虑
  • 修改对于实体,将 JSR 303 Bean 校验移至 Getter 方法上而非域上。只需修改实体注解,移除 Lombok,手动生成 Getter Setter,并把相应的 JSR 303 注解移到 Getter 上

选择了最后一种。

后话

Williams 老师诚不欺我,很早之前就说再 Getter 上进行各种注解才是一种最佳实践,后面为了方便加上 Lombok 的大行其道,基本都注解在域上了,导致花了整整一晚才找到根本不该出现问题。

已经凌晨一点了,Hello World!

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-12-22
  • 2022-12-23
  • 2021-06-28
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2022-12-23
  • 2022-12-23
  • 2021-05-10
  • 2021-08-17
  • 2021-07-15
  • 2022-01-21
  • 2022-12-23
相关资源
相似解决方案