简体   繁体   English

Spring Boot + Hibernate + Thymeleaf 延迟加载异常

[英]Spring Boot + Hibernate + Thymeleaf Lazy loading exception

I'm working on a Spring Boot web service but had a problem and don't know how to solve it (I have tried for many days...)我正在开发 Spring Boot web 服务,但遇到了问题,不知道如何解决(我已经尝试了很多天......)

In my DB I have AB and C three tables, the relation is A one to many B, B one to many C.在我的数据库中,我有 AB 和 C 三个表,关系是 A 一对多 B,B 一对多 C。

In my spring boot controller, I need to have a findById() function for table A, when I call this endpoint, the data in A, B and C should be retrieved accordingly. In my spring boot controller, I need to have a findById() function for table A, when I call this endpoint, the data in A, B and C should be retrieved accordingly.

The data volume in table C is massive, it has 16K records or so.表 C 中的数据量很大,大约有 16K 条记录。

That is the background.这就是背景。

Now, if I call findById() for table A, the data inside table A loaded perfectly, which is great, however, I can't get data from B and C because of Lazy Loading Exception.现在,如果我为表 A 调用 findById(),则表 A 内的数据会完美加载,这很好,但是,由于延迟加载异常,我无法从 B 和 C 获取数据。

To solve this, I changed the fetch type to EAGER fetch.为了解决这个问题,我将 fetch 类型更改为 EAGER fetch。 However, it will take ages to get the data, which is really bad, (I can tell there is no infinity loop, it just because the data volume is large I guess, it usually takes 40 seconds to get the result when I debug it)但是,获取数据需要很长时间,这真的很糟糕,(我可以说没有无限循环,我猜只是因为数据量很大,我调试时通常需要40秒才能得到结果)

Thus, I tried some other stuffs.因此,我尝试了其他一些东西。 Since I realised that this project I have multiple db connections, so I add another annotation @Transanctional("transactionName") to restrict the service and Repository only scan the data inside a particular transaction.由于我意识到这个项目我有多个数据库连接,所以我添加了另一个注释 @Transanctional("transactionName") 来限制服务和存储库只扫描特定事务中的数据。 And it works!它有效!

However!!!然而!!!

this controller is like below:这个 controller 如下所示:

@Transactional("name")
public String findDataInTableAById(@PathVariable("id") Integer id, Model model)
    {
         ModelA a = tableAService.findById(id);
         model.add("dataInThymeleaf", a);
         return "/path/page.html"
    }

When I put a breakpoint on ModelA a = tableAService.findById(id);当我在 ModelA 上设置断点时 a = tableAService.findById(id); I can definitely see all the data loading perfectly in debug console.我绝对可以在调试控制台中完美地看到所有数据加载。 However, when I give it to my front-end (Thymeleaf), it gives me a Lazy Loading Exception again.但是,当我将它提供给我的前端(Thymeleaf)时,它又给了我一个延迟加载异常。 I did some research, and I guess it's because the session is already closed before I pass it to Thymeleaf.我做了一些研究,我想这是因为 session 在我将其传递给 Thymeleaf 之前已经关闭。

Want to know if anyone had the same problem and how to solve it?想知道是否有人遇到同样的问题以及如何解决?

TL;DR - try forcing the lazy loading to occur within your method by accessing the fields that are marked as lazy. TL;DR - 尝试通过访问标记为延迟的字段来强制延迟加载在您的方法中发生。 That would be somehting like a.getB().size(); a.getB().getC().size();这有点像a.getB().size(); a.getB().getC().size(); a.getB().size(); a.getB().getC().size(); before the model.add() line.model.add()行之前。 This will load the lazy data within the transaction and will avoid the exception, but I'm pretty sure it won't make things any faster.这将在事务中加载惰性数据并避免异常,但我很确定它不会让事情变得更快。 Here's why:原因如下:

First of all, this is a purely JPA matter, Thymeleaf has nothing to do with the problem, and Spring itself probably also does not (although it can make things worse, if used improperly).首先,这纯属JPA的事情,Thymeleaf与问题无关,而Spring本身,如果使用不当可能也不会。

As you already mentioned, this is a LAZY vs. EAGER issue.正如您已经提到的,这是一个 LAZY 与 EAGER 的问题。 Lazy loading means that dependent entities ( @OneToMany , or whatever) are only loaded if needed, at the time they are accessed, instead of at query time.延迟加载意味着依赖实体( @OneToMany或其他)仅在需要时在访问它们时加载,而不是在查询时加载。 There are a couple of important things to note/understand about this:有几件重要的事情需要注意/理解:

  • In sum, lazy loading is actually (marginally) slower that loading eagerly.总而言之,延迟加载实际上(略微)急切加载要慢。 It will make your initial query finish faster, and so you will get some data sooner, but if you then need to lazy-load associated entries, the total loading time will be a little more than if you had loaded them directly in the first place.这将使您的初始查询更快地完成,因此您将更快地获得一些数据,但是如果您随后需要延迟加载相关条目,则总加载时间将比您直接加载它们时多一点.

  • Therefore, lazy loading only makes sense if there is a good chance that you will not need those entries at all for some executions of your code.因此,延迟加载仅在您很有可能某些代码执行中根本不需要这些条目时才有意义。 If you will definitely need them, just later, then don't use lazy loading.如果您以后肯定需要它们,请不要使用延迟加载。

  • Another important restriction - lazy loading can only occur in the same transaction/session in which the parent entities were loaded.另一个重要的限制 - 延迟加载只能发生在加载父实体的同一事务/会话中。 After the transaction is committed/rolled back, you can't load any more data out of that result list.事务提交/回滚后,您不能再从该结果列表中加载任何数据。

In your case, the last point is the problem.就您而言,最后一点是问题所在。 Your GUI/templating engine is trying to access lists that are lazy loaded, however by the time they access it, the session is closed.您的 GUI/模板引擎正在尝试访问延迟加载的列表,但是当他们访问它时,session 已关闭。

There is no way around this than to actually load the data while the session is open, which means within the method you cited above.除了在 session 打开时实际加载数据外,没有其他办法,这意味着在您上面引用的方法内。 Technically it doesn't matter if you load lazily or eagerly, but you won't get any performance improvement if you do it lazily.从技术上讲,延迟加载或急切加载并不重要,但是如果延迟加载,则不会获得任何性能改进。

And one more thing that surprises me - you say your table C is "massive" and has 16K records.还有一件事让我感到惊讶 - 你说你的表 C 是“巨大的”并且有 16K 条记录。 If you mean 16 thousand, then this is nothing.如果你的意思是 16000,那么这不算什么。 A massive table is if it has 16 billion entries.一个巨大的表是如果它有 160 亿个条目。 16 million is also large, but not massive. 1600 万也很大,但不是很大。 Anything below a couple million entries should be handled almost instantaneously by any DB on current hardware.当前硬件上的任何数据库都应几乎立即处理任何低于几百万个条目的内容。

This makes me think that your problem might be somewhere else.这让我觉得你的问题可能出在其他地方。 If you need 40 seconds to run a query on this small scale of data, then this is not normal, no matter lazy or eager loading.如果你需要 40 秒来对这么小规模的数据运行一次查询,那么无论是惰性加载还是急切加载,这都是不正常的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM