zoukankan      html  css  js  c++  java
  • 一次 Spring Data JPA 查询返回数据属性为 null 排查

    现象

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

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

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

    排查

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

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

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

    先尝试单步跟一下代码。

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

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

    很明显,这里的域有值而异常的则没有,而且这里得到的是真正的实体对象而非代理。异常的多了一个 $$_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!

  • 相关阅读:
    [Python3网络爬虫开发实战] 3.1.3-解析链接
    pusher-http-go
    gopush-cluster 架构
    消息队列 redis vs nsq
    redis资料
    golang+websocket
    golang之flag.String
    Linux环境下安装mysql
    golang版的crontab
    golang实现wav文件转换为mp3文件
  • 原文地址:https://www.cnblogs.com/seliote/p/15230641.html
Copyright © 2011-2022 走看看