英文:
JPA / Hibernate Spring @Transactional vs. JOIN FETCH
问题
我正在面对一个有趣的LazyInitializationException解决方案。
为了防止这种情况(在OneToMany或ManyToMany上),已知的一个解决方案是使用JOIN FETCH查询。
你可以在这里看到其中的几个示例:https://thoughts-on-java.org/best-practices-for-many-to-many-associations-with-hibernate-and-jpa/
另一个更简单的解决方案是使用Spring的@Transactional。
例如像这样:
@DeleteMapping(value = "/product/{tagId}")
@ResponseBody
@Transactional
public String deleteProductWithoutRelation(@PathVariable String product, Model model) {
Optional<Product> pr = productService.selectProduct(product);
if (pr.isPresent()) {
tag.get().getCustomer().size(); //通常会抛出LazyInitializationException,
//没有JOIN-FETCH语句或@Transactional
return deletedTagId;
}
当然,你可以将@Repository服务中的某个方法上使用@Transactional,以封装这个解决方案。
那么这两种解决方案的优缺点是什么呢?
英文:
i'am facing with interesting solution of LazyInitializationException.
To prevent this (on OneToMany oder ManyToMany) one known solution is, to use JOIN FETCH Query.
You can see one of severals examples her: https://thoughts-on-java.org/best-practices-for-many-to-many-associations-with-hibernate-and-jpa/
Other easier solution is, to use @Transactional from Spring.
For example like this:
@DeleteMapping(value ="/product/{tagId}")
@ResponseBody
@Transactional
public String deleteProductWithoutRelation(@PathVariable String product, Model model) {
Optional<Product> pr = productService.selectProduct(product);
if (pr.isPresent()) {
tag.get().getCustomer().size(); //usualy throws LazyInitializationException,
//without JOIN-FETCH Statment or @Transactional
return deletedTagId;
}
Of course, you can place @Transactional of some method from repository service, to encapsulate this solution.
So which Advantages or Disadvantages of both solutions are here?
答案1
得分: 6
有几件事情我们需要梳理一下。
- @Transactional 只是表示 Spring 会确保打开一个数据库连接(+ 事务),然后再关闭它。就是这样。
- 当你选择一个包含延迟加载字段的实体时,实际上是在说:我选择了实体的“一些”字段,除了延迟加载的字段。
- 但是,如果你稍后需要在视图(.html、.ftl、.jsp 等)中访问这个延迟加载字段,你需要向数据库发出另一个查询以获取它。
- 问题是:此时,如果你在 @Transactional 方法之外,就不再有打开的数据库连接了,因此会出现 LazyInitException(延迟初始化异常)。
- 总之:你的查询确保为所有数据发出了一个查询。如果你不这样做,你需要一个打开的数据库连接/事务,而 @Transactional 正是提供了这个。
建议:你应该尝试获取所有需要呈现视图的数据,使用适当的 JPQL/Criteria/SQL 语句,不要过多地依赖重新选择延迟加载字段。
英文:
There's a couple of things we need to untangle here.
- @Transactional means nothing more than Spring makes sure to open up a database connection(+ transaction) and closes it again. That's it.
- When you select an entity containing a lazy field, you are essentially saying: I am selecting "some" fields from my entity, except the lazy one.
- If you, however, need that lazy field later on because you are trying to access it in your views (.html, .ftl, .jsp, whatever) you need to issue another select to the database to fetch it.
- The problem: At that point, if you are outside of a @Transactional method you do not have a database connection open anymore, hence the LazyInitException.
- To sum up: Your fetch makes sure to issue 1 select for all the data. If you do NOT do that, you need an open database connection/transaction, which @Transactional gives you.
Recommendation: You should try and fetch all the data you need to render a view, with appropriate JPQL/Criteria/SQL statements and not rely on re-selecting lazy-fields too much.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论