你有没有想过如果你能解决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<Book> books = new ArrayList<Book>();...

}
复制代码
当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<Object[]> results = q.getResultList();
并且,通过JPA的函数function,你也可以调用数据库特定的或自定义的数据库函数。
TypedQuery q = em.createQuery(
“SELECT b FROM Book b WHERE b.id = function(‘calculate’, 1, 2)”,
Book.class);
Book b = q.getSingleResult();

错误7:无理由地调用flush方法
这是另一个比较普遍的错误。开发人员在持久化一个新实体或更新现有实体后,调用EntityManager的flush方法时经常会出现这个错误。这迫使Hibernate对所有被管理的实体执行脏检查,并为所有未决的插入、更新或删除操作创建和执行SQL语句。这会减慢应用程序,因为它阻止了Hibernate使用一些内部优化。

Hibernate将所有被管理的实体存储在持久性上下文中,并试图尽可能延迟写操作的执行。这允许Hibernate将同一实体上的多个更新操作合并为一个SQL UPDATE语句,通过JDBC批处理绑定多个相同的SQL语句,并避免执行重复的SQL语句,这些SQL语句返回你已在当前Session中使用的实体。

作为一个经验法则,你应该避免任何对flush方法的调用。JPQL批量操作是罕见的例外之一,对此我将在错误9中解释。

错误8:使用Hibernate应付一切
Hibernate的对象关系映射和各种性能优化使大多数CRUD用例的实现非常简单和高效。这使得Hibernate成为许多项目的一个很好的选择。但这并不意味着Hibernate对于所有的项目都是一个很好的解决方案。

我在我之前的一个帖子和视频中详细讨论过这个问题。JPA和Hibernate为大多数创建、读取或更新一些数据库记录的标准CRUD用例提供了很好的支持。对于这些用例,对象关系映射可以大大提升生产力,Hibernate的内部优化提供了一个很优越的性能。

但是,当你需要执行非常复杂的查询、实施分析或报告用例或对大量记录执行写操作时,结果就不同了。所有这些情况都不适合JPA和Hibernate的查询能力以及基于实体管理的生命周期。

如果这些用例只占应用程序的一小部分,那么你仍然可以使用Hibernate。但总的来说,你应该看看其他的框架,比如jOOQ或者Querydsl,它们更接近于SQL,并且可以避免任何对象关系映射。

错误9:逐个更新或删除巨大的实体列表
在你看着你的Java代码时,感觉逐个地更新或删除实体也可以接受。这就是我们对待对象的方式,对吧?

这可能是处理Java对象的标准方法,但如果你需要更新大量的数据库记录,那么,这就不是一个好方法了。在SQL中,你只需一次定义一个影响多个记录的UPDATE或DELETE语句。数据库将会非常高效地处理这些操作。

不幸的是,用JPA和Hibernate操作起来则没有那么容易。每个实体都有自己的生命周期,而你如果要更新或删除多个实体的话,则首先需要从数据库加载它们。然后在每个实体上执行操作,Hibernate将为每个实体生成所需的SQL UPDATE或DELETE语句。因此,Hibernate不会只用1条语句来更新1000条数据库记录,而是至少会执行1001条语句。

很显然,执行1001条语句比仅仅执行1条语句需要花费更多的时间。幸运的是,你可以使用JPQL、原生SQL或Criteria查询对JPA和Hibernate执行相同的操作。

但是它有一些你应该知道的副作用。在数据库中执行更新或删除操作时,将不使用实体。这提供了更佳的性能,但它同时忽略了实体生命周期,并且Hibernate不能更新任何缓存。

简而言之,在执行批量更新之前,你不应使用任何生命周期侦听器以及在EntityManager上调用flush和clear方法。flush方法将强制Hibernate在clear方法从当前持久化上下文中分离所有实体之前,将所有待处理的更改写入数据库。
https://www.jianshu.com/p/5cc7d401bf8b
https://www.jianshu.com/p/e1a1ec79ce87
http://www.dianyuan.com/people/792826
http://www.dianyuan.com/people/792827
http://www.dianyuan.com/people/792828
http://www.dianyuan.com/people/792829
http://www.dianyuan.com/people/792830
http://www.dianyuan.com/people/792831
http://www.dianyuan.com/people/792832
http://www.dianyuan.com/people/792833

em.flush();
em.clear();
Query query = em.createQuery(“UPDATE Book b SET b.price = b.price*1.1”);
query.executeUpdate();

错误10:使用实体进行只读操作
JPA和Hibernate支持一些不同的projections。如果你想优化你的应用程序的性能,那么你应该使用projections。最明显的原因是你应该只选择用例中需要的数据。

但这不是唯一的原因。正如我在最近的测试中显示的那样,即使你读取了相同的数据库列,DTO projections也比实体快得多。

在SELECT子句中使用构造函数表达式而不是实体只是一个小小的改变。但在我的测试中,DTO projections比实体快40%。当然,两者比较的数值取决于你的用例,而且你也不应该通过这样一个简单而有效的方式来提高性能。

了解如何查找和修复Hibernate性能问题
正如你所看到的,一些小小的问题都可能会减慢你的应用程序。但幸运的是,我们可以轻松避免这些问题并构建高性能持久层。

(转)Java程序员注意——扼杀性能的 10 个常见 Hibernate 错误相关推荐

  1. Java程序员最常犯的 10 个错误

    转载自 Java程序员最常犯的 10 个错误 这个列表总结了Java开发人员经常犯的10个错误. 一 .把数组转成ArrayList 为了将数组转换为ArrayList,开发者经常会这样做: List ...

  2. 每个Java程序员都应该Follow的10个Twitter账号

    想让自己在第一时间了解Java世界正在发生什么吗? Twitter绝对是了解最新动态的优质资源.我列举了每个Java程序员都应该关注的10个twitter账号.在评论中可添加你喜欢的. 1 @java ...

  3. Java程序员必须要掌握这10种工具,缺一不可!

    如果你是一位经验丰富的Java开发人员,你可能对这些工具很熟悉,但如果不是,现在就是开始学习这些工具的好时机. NO.1 JIRA Atlassian的JIRA是当前敏捷开发领域最重要的工具之一. 它 ...

  4. Java程序员最值得学习的10大技术

    程序员们是最具有挑战性的工作,我们最大的挑战就是保持自己了解新的技术.技术变化很快,你大概每两年就会看到一个新版本的编程语言和框架. 就拿2017年来说,AR.VR.区块链.人工智能等等已经扑面而来了 ...

  5. 程序员都需要懂的10种常见安全漏洞

    前言 我们日常开发中,很多小伙伴容易忽视安全漏洞问题,认为只要正常实现业务逻辑就可以了.其实,安全性才是最重要的.本文将跟大家一起学习常见的安全漏洞问题,希望对大家有帮助哈.如果本文有什么错误的话,希 ...

  6. 成为优秀Java程序员的10大技巧

    来自:http://geek.csdn.net/news/detail/114500 Java程序员有许多应遵循的守则或最佳实践方式.本文概述了每个开发者最应该遵循的10条守则或戒律,如果不遵循它们, ...

  7. Java程序员应该了解的10个设计原则

    面向对象设计原则是OOPS(Object-Oriented Programming System,面向对象的程序设计系统)编程的核心,但大多数Java程序员追逐像Singleton.Decorator ...

  8. 一般编译器错误_Java程序员最容易犯的10个错误

    人非圣贤,孰能无过.都说Java语言是一门简单的编程语言,基于C++演化而来,剔除了很多C++中的复杂特性,但这并不能保证Java程序员不会犯错. 在开发 Java 软件时可能会遇到许多类型的错误,但 ...

  9. 90 % Java 程序员被误导的一个性能优化策略

    转载自   90 % Java 程序员被误导的一个性能优化策略 我们经常看到一些 Java 性能优化的书或者理念,说不要在循环内定义变量,这样会占用过多的内存影响性能,而要在循环外面定义.接触 Jav ...

  10. 2020年薪30W的Java程序员都要求熟悉JVM与性能调优!

    前言 作为Java程序员,你有没有被JVM伤害过?面试的时候是否碰到过对JVM的灵魂拷问? 一.JVM 内存区域划分 1.程序计数器(线程私有) 程序计数器(Program Counter Regis ...

最新文章

  1. Web自动化测试 六 ----- selector选择
  2. 基于 CNN 特征区域进行目标检测
  3. Go各时间字符串使用详解
  4. Java 工程师必须掌握的 JVM 类加载机制!
  5. 机器学习中的范数规则化之(二)核范数与规则项参数选择以及RPCA
  6. linux 格式化up命令,uptime 命令介绍
  7. 用计算机作业,四川电大计算机办公软件应用第1次形考作业
  8. macOS Big Sur无法安装 Big Sur为什么安装不了?
  9. 非线性系统【三】LaSalle不变原理
  10. html5读delphi数据,Delphi HTML解析器
  11. 微信小程序图片加载错误,显示优化
  12. Qt解决资源文件中无法显示图片的问题
  13. Integer与Int
  14. 超鸿蒙 混希夷 寂寥,下列句子与“故凡为愚者.莫我若也 的句式不同的一项是 A.超鸿蒙.混希夷.寂寥而莫我知也 B.而良人未之知也 C.及长.不省所怙.惟兄嫂是依 D.王语暴以好乐...
  15. 22. 关于定时任务指定的时间间隔内没有完成任务的处理
  16. 土星游戏精灵王2游戏反向工程日志(1)
  17. 基于matlab山脊线,基于Matlab的标记分水岭分割算法
  18. 在WinServer2008下安装SQLServer2014
  19. 关于显式类型转换以及隐式类型转换
  20. Python3之多线程

热门文章

  1. 一部火了20年的音乐剧,到底有何魅力?
  2. Vue实现搜索关键词高亮显示
  3. 工业相机和普通相机的区别详解_工业数字相机和普通相机的区别
  4. svn 命令行使用总结
  5. 桑佛德大学计算机科学,桑佛德大学
  6. python cox模型_Forest plot(森林图) | Cox生存分析可视化
  7. 调用支付宝网页支付被浏览器拦截
  8. 解决登录wordpress时,密码账号正确却出现INSECURE PASSWORD警告的问题
  9. 7-2 哈利·波特的考试 (25 分)(解释的挺详细了吧)
  10. Unity Kinect添加自定义姿势识别