扼杀性能的10个常见Hibernate错误
你有没有想过如果你能解决Hibernate问题,那么你的应用程序可以更快?
那么请阅读这篇文章!
我在很多应用程序中修复过性能问题,其中大部分都是由同样的错误引起的。修复之后,性能变得更溜,而且其中的大部分问题都很简单。所以,如果你想改进应用程序,那么可能也是小菜一碟。
这里列出了导致Hibernate性能问题的10个最常见的错误,以及如何修复它们。
错误1:使用Eager Fetching
FetchType.EAGER的启示已经讨论了好几年了,而且有很多文章对它进行了详细的解释。我自己也写了一篇。但不幸的是,它仍然是性能问题最常见的两个原因之一。
FetchType定义了Hibernate何时初始化关联。你可以使用@OneToMany,@ManyToOne,@ManyToMany和@OneToOneannotation注释的fetch属性进行指定。
@Entity public class Author{ @ManyToMany(mappedBy="authors", fetch=FetchType.LAZY) private List
当Hibernate加载一个实体的时候,它也会即时加载获取的关联。例如,当Hibernate加载Author实体时,它也提取相关的Book实体。这需要对每个Author进行额外的查询,因此经常需要几十甚至数百个额外的查询。
这种方法是非常低效的,因为Hibernate不管你是不是要使用关联都会这样做。最好改用FetchType.LAZY代替。它会延迟关系的初始化,直到在业务代码中使用它。这可以避免大量不必要的查询,并提高应用程序的性能。
幸运的是,JPA规范将FetchType.LAZY定义为所有对多关联的默认值。所以,你只需要确保你不改变这个默认值即可。但不幸的是,一对一关系并非如此。
错误2:忽略一对一关联的默认FetchType
接下来,为了防止立即抓取(eager fetching),你需要做的是对所有的一对一关联更改默认的FetchType。不幸的是,这些关系在默认情况下会被即时抓取。在一些用例中,那并非一个大问题,因为你只是加载了一个额外的数据库记录。但是,如果你加载多个实体,并且每个实体都指定了几个这样的关联,那么很快就会积少成多,水滴石穿。
所以,最好确保所有的一对一关联设置FetchType为LAZY。
@Entity public class Review { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "fk_book") private Book book; ... }
错误3:不要初始化所需的关联
当你对所有关联使用FetchType.LAZY以避免错误1和错误2时,你会在代码中发现若干n+1选择问题。当Hibernate执行1个查询来选择n个实体,然后必须为每个实体执行一个额外的查询来初始化一个延迟的获取关联时,就会发生这个问题。
Hibernate透明地获取惰性关系,因此在代码中很难找到这种问题。你只要调用关联的getter方法,我想我们大家都不希望Hibernate执行任何额外的查询吧。
List authors = em.createQuery("SELECT a FROM Author a", Author.class).getResultList(); for (Author a : authors) { log.info(a.getFirstName() + " " + a.getLastName() + " wrote " + a.getBooks().size() + " books."); }
如果你使用开发配置激活Hibernate的统计组件并监视已执行的SQL语句的数量,n+1选择问题就会更容易被发现。
15:06:48,362 INFO [org.hibernate.engine.internal.StatisticalLoggingSessionEventListener] - Session Metrics { 28925 nanoseconds spent acquiring 1 JDBC connections; 24726 nanoseconds spent releasing 1 JDBC connections; 1115946 nanoseconds spent preparing 13 JDBC statements; 8974211 nanoseconds spent executing 13 JDBC statements; 0 nanoseconds spent executing 0 JDBC batches; 0 nanoseconds spent performing 0 L2C puts; 0 nanoseconds spent performing 0 L2C hits; 0 nanoseconds spent performing 0 L2C misses; 20715894 nanoseconds spent executing 1 flushes (flushing a total of 13 entities and 13 collections); 88175 nanoseconds spent executing 1 partial-flushes (flushing a total of 0 entities and 0 collections) }
正如你所看到的JPQL查询和对12个选定的Author实体的每一个调用getBooks方法,导致了13个查询。这比大多数开发人员所以为的还要多,在他们看到如此简单的代码片段的时候。
如果你让Hibernate初始化所需的关联,那么你可以很容易地避免这种情况。有若干不同的方式可以做到这一点。最简单的方法是添加JOIN FETCH语句到FROM子句中。
Author a = em.createQuery( "SELECT a FROM Author a JOIN FETCH a.books WHERE a.id = 1", Author.class).getSingleResult();
错误4:选择比所需的更多记录
当我告诉你选择太多的记录会减慢应用程序的速度时,我敢保证你一定不会感到惊讶。但是我仍然经常会发现这个问题,当我在咨询电话中分析应用程序的时候。
其中一个原因可能是JPQL不支持你在SQL查询中使用OFFSET和LIMIT关键字。这看起来似乎不能限制查询中检索到的记录数量。但是,你可以做到这一点。你只需要在Query接口上,而不是在JPQL语句中设置此信息。
我在下面的代码片段中做到这一点。我首先通过id排序选定的Author实体,然后告诉Hibernate检索前5个实体。
List authors = em.createQuery("SELECT a FROM Author a ORDER BY a.id ASC", Author.class) .setMaxResults(5) .setFirstResult(0) .getResultList();
错误5:不使用绑定参数
绑定参数是查询中的简单占位符,并提供了许多与性能无关的好处:
它们非常易于使用。
Hibernate自动执行所需的转换。
Hibernate会自动转义Strings,防止SQL注入漏洞。
而且也可以帮助你实现一个高性能的应用程序。
大多数应用程序执行大量相同的查询,只在WHERE子句中使用了一组不同的参数值。绑定参数允许Hibernate和数据库识别与优化这些查询。
你可以在JPQL语句中使用命名的绑定参数。每个命名参数都以“:”开头,后面跟它的名字。在查询中定义了绑定参数后,你需要调用Query接口上的setParameter方法来设置绑定参数值。
TypedQuery q = em.createQuery( "SELECT a FROM Author a WHERE a.id = :id", Author.class); q.setParameter("id", 1L); Author a = q.getSingleResult();
错误6:执行业务代码中的所有逻辑
对于Java开发人员来说,在业务层实现所有的逻辑是自然而然的。我们可以使用我们最熟悉的语言、库和工具。
但有时候,在数据库中实现操作大量数据的逻辑会更好。你可以通过在JPQL或SQL查询中调用函数或者使用存储过程来完成。
让我们快速看看如何在JPQL查询中调用函数。如果你想深入探讨这个话题,你可以阅读我关于存储过程的文章。
你可以在JPQL查询中使用标准函数,就像在SQL查询中调用它们一样。你只需引用该函数的名称,后跟一个左括号,一个可选的参数列表和一个右括号。
Query q = em.createQuery("SELECT a, size(a.books) FROM Author a GROUP BY a.id"); List
时间:2018-10-09 22:38 来源: 转发量:次
声明:本站部分作品是由网友自主投稿和发布、编辑整理上传,对此类作品本站仅提供交流平台,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,不为其版权负责。如果您发现网站上有侵犯您的知识产权的作品,请与我们取得联系,我们会及时修改或删除。
相关文章:
相关推荐:
网友评论: