3 Common Hibernate Performance Issues and How to Find Them in Your Log File – 3个常见的Hibernate性能问题以及如何在日志文件中找到它们

最后修改: 2016年 7月 15日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

You’ve probably read some of the complaints about bad Hibernate performance or maybe you’ve struggled with some of them yourself. I have been using Hibernate for more than 15 years now and I have run into more than enough of these issues.

你可能读过一些关于Hibernate性能不好的抱怨,或者你自己也曾与其中的一些问题斗争过。我使用Hibernate已经超过15年了,我遇到的这些问题已经够多了。

Over the years, I’ve learned that these problems can be avoided and that you can find a lot of them in your log file. In this post, I want to show you how you can find and fix 3 of them.

多年来,我了解到这些问题是可以避免的,你可以在你的日志文件中找到很多问题。在这篇文章中,我想告诉你如何找到并解决其中的3个问题。

2. Find and Fix Performance Problems

2.发现并修复性能问题

2.1. Log SQL Statements in Production

2.1.在生产中记录SQL语句

The first performance issue is extremely easy to spot and often ignored. It’s the logging of SQL statements in a production environment.

第一个性能问题是非常容易发现的,而且经常被忽视。这就是生产环境中的SQL语句的记录。

Writing some log statements doesn’t sound like a big deal, and there are a lot of applications out there which do exactly that. But it is extremely inefficient, especially via System.out.println as Hibernate does it if you set the show_sql parameter in your Hibernate configuration to true:

写一些日志语句听起来不是什么大事,而且有很多应用程序正是这样做的。但这是非常低效的,尤其是通过System.out.println,因为如果你在Hibernate配置中把show_sql参数设置为true,Hibernate就会这样做。

Hibernate: select 
    order0_.id as id1_2_, 
    order0_.orderNumber as orderNum2_2_, 
    order0_.version as version3_2_ 
  from purchaseOrder order0_
Hibernate: select 
    items0_.order_id as order_id4_0_0_, 
    items0_.id as id1_0_0_, 
    items0_.id as id1_0_1_, 
    items0_.order_id as order_id4_0_1_, 
    items0_.product_id as product_5_0_1_, 
    items0_.quantity as quantity2_0_1_, 
    items0_.version as version3_0_1_ 
  from OrderItem items0_ 
  where items0_.order_id=?
Hibernate: select 
    items0_.order_id as order_id4_0_0_, 
    items0_.id as id1_0_0_, 
    items0_.id as id1_0_1_, 
    items0_.order_id as order_id4_0_1_, 
    items0_.product_id as product_5_0_1_, 
    items0_.quantity as quantity2_0_1_, 
    items0_.version as version3_0_1_ 
  from OrderItem items0_ 
  where items0_.order_id=?
Hibernate: select 
    items0_.order_id as order_id4_0_0_, 
    items0_.id as id1_0_0_, 
    items0_.id as id1_0_1_, 
    items0_.order_id as order_id4_0_1_, 
    items0_.product_id as product_5_0_1_, 
    items0_.quantity as quantity2_0_1_, 
    items0_.version as version3_0_1_ 
  from OrderItem items0_ 
  where items0_.order_id=?

In one of my projects, I improved the performance by 20% within a few minutes by setting show_sql to false. That’s the kind of achievement you like to report in the next stand-up meeting 🙂

在我的一个项目中,我通过将show_sql设置为false,在几分钟内将性能提高了20%。这就是你想在下次站立会议上报告的那种成就 🙂

It’s pretty obvious how you can fix this performance issue. Just open your configuration (e.g. your persistence.xml file) and set the show_sql parameter to false. You don’t need this information in production anyways.

你如何解决这个性能问题是很明显的。只要打开你的配置(例如你的persistence.xml文件)并将show_sql参数设置为false。反正你在生产中也不需要这些信息。

But you might need them during development. If you don’t, you use 2 different Hibernate configurations (which you shouldn’t) you deactivated the SQL statement logging there as well. The solution for that is to use 2 different log configurations for development and production which are optimized for the specific requirements of the runtime environment.

但你在开发过程中可能需要它们。如果你不这样做,你使用了2个不同的Hibernate配置(你不应该这样做),你也停用了那里的SQL语句日志。解决这个问题的办法是为开发和生产使用2个不同的日志配置,这些配置是根据运行环境的具体要求而优化的。

Development Configuration

开发配置

The development configuration should provide as many useful information as possible so that you can see how Hibernate interacts with the database. You should therefore at least log the generated SQL statements in your development configuration. You can do this by activating DEBUG message for the org.hibernate.SQL category. If you also want to see the values of your bind parameters, you have to set the log level of org.hibernate.type.descriptor.sql to TRACE:

开发配置应该提供尽可能多的有用信息,以便你能看到Hibernate与数据库的交互情况。因此,你至少应该在你的开发配置中记录生成的SQL语句。你可以通过激活DEBUG消息的org.hibernate.SQL类别来实现。如果你还想看到绑定参数的值,你必须将org.hibernate.type.descriptor.sql的日志级别设置为TRACE

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n
log4j.rootLogger=info, stdout

# basic log level for all messages
log4j.logger.org.hibernate=info

# SQL statements and parameters
log4j.logger.org.hibernate.SQL=debug
log4j.logger.org.hibernate.type.descriptor.sql=trace

The following code snippet shows some example log messages which Hibernate writes with this log configuration. As you can see, you get detailed information about the executed SQL query and all set and retrieved parameter values:

下面的代码片断显示了Hibernate用这种日志配置写的一些日志信息的例子。正如你所看到的,你可以得到关于执行的SQL查询和所有设置和检索的参数值的详细信息。

23:03:22,246 DEBUG SQL:92 - select 
    order0_.id as id1_2_, 
    order0_.orderNumber as orderNum2_2_, 
    order0_.version as version3_2_ 
  from purchaseOrder order0_ 
  where order0_.id=1
23:03:22,254 TRACE BasicExtractor:61 - extracted value ([id1_2_] : [BIGINT]) - [1]
23:03:22,261 TRACE BasicExtractor:61 - extracted value ([orderNum2_2_] : [VARCHAR]) - [order1]
23:03:22,263 TRACE BasicExtractor:61 - extracted value ([version3_2_] : [INTEGER]) - [0]

Hibernate provides you with a lot more internal information about a Session if you activate the Hibernate statistics. You can do this by setting the system property hibernate.generate_statistics to true.

如果你激活了Hibernate统计,Hibernate会向你提供更多关于Session的内部信息。你可以通过设置系统属性hibernate.gener_statistics为true来实现。

But please, only activate the statistics on your development or test environment. Collecting all these information slows down your application and you might create your performance problems yourself if you activate it this in production.

但是,请只在你的开发或测试环境中激活统计数据。收集所有这些信息会减慢你的应用程序,如果你在生产中激活它,你可能会自己造成你的性能问题。

You can see some example statistics in the following code snippet:

你可以在下面的代码片断中看到一些统计实例。

23:04:12,123 INFO StatisticalLoggingSessionEventListener:258 - Session Metrics {
 23793 nanoseconds spent acquiring 1 JDBC connections;
 0 nanoseconds spent releasing 0 JDBC connections;
 394686 nanoseconds spent preparing 4 JDBC statements;
 2528603 nanoseconds spent executing 4 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;
 9700599 nanoseconds spent executing 1 flushes (flushing a total of 9 entities and 3 collections);
 42921 nanoseconds spent executing 1 partial-flushes (flushing a total of 0 entities and 0 collections)
}

I regularly use these statistics in my daily work to find performance issues before they occur in production and I could write several posts just about that. So let’s just focus on the most important ones.

我在日常工作中经常使用这些统计数据,以便在生产中出现性能问题之前发现这些问题,我可以就此写几篇文章。所以让我们只关注最重要的那些。

The lines 2 to 5 show you how many JDBC connections and statements Hibernate used during this session and how much time it spent on it. You should always have a look at these values and compare them with your expectations.

第2行到第5行显示了Hibernate在这个会话中使用了多少JDBC连接和语句,以及它在上面花费了多少时间。你应该经常看看这些数值,并将它们与你的预期进行比较。

If there are a lot more statements than you expected, you most likely have the most common performance problem, the n+1 select issue. You can find it in nearly all applications, and it might create huge performance issues on a bigger database. I explain this issue in more details in the next section.

如果语句比你预期的多得多,你很可能有最常见的性能问题,即n+1选择问题。你可以在几乎所有的应用程序中找到它,而且它可能会在一个更大的数据库中产生巨大的性能问题。我将在下一节中更详细地解释这个问题。

The lines 7 to 9 show how Hibernate interacted with the 2nd level cache. This is one of Hibernate’s 3 caches, and it stores entities in a session independent way. If you use the 2nd level in your application, you should always monitor these statistics to see if Hibernate gets the entities from there.

第7行到第9行显示了Hibernate如何与二级缓存进行交互。这是Hibernate的3个缓存之一,它以独立于会话的方式存储实体。如果你在你的应用程序中使用第2级缓存,你应该经常监控这些统计数据,看看Hibernate是否从那里获得实体。

Production Configuration

生产配置[/strong

The production configuration should be optimized for performance and avoid any messages that are not urgently required. In general, that means that you should only log error messages. If you use Log4j, you can achieve that with the following configuration:

生产配置应该对性能进行优化,避免任何不急需的信息。一般来说,这意味着你应该只记录错误信息。如果你使用Log4j,你可以通过以下配置实现。

If you use Log4j, you can achieve that with the following configuration:

如果你使用Log4j,你可以通过以下配置实现。

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n
log4j.rootLogger=info, stdout

# basic log level for all messages
log4j.logger.org.hibernate=error

2.2. N+1 Select Issue

2.2.N+1选择问题

As I already explained, the n+1 select issue is the most common performance problem. A lot of developers blame the OR-Mapping concept for this issue, and they’re not completely wrong. But you can easily avoid it if you understand how Hibernate treats lazily fetched relationships. The developer is, therefore, to blame as well because it’s his responsibility to avoid these kinds of issues. So let me explain first why this issue exists and then show you an easy way to prevent it. If you are already familiar with the n+1 select issues, you can jump directly to the solution.

正如我已经解释的,n+1选择问题是最常见的性能问题。很多开发者把这个问题归咎于OR-Mapping的概念,他们并不完全错误。但是,如果你了解Hibernate如何处理懒惰的取舍关系,你可以很容易地避免它。因此,开发者也应该受到责备,因为避免这类问题是他的责任。因此,让我先解释一下为什么会存在这个问题,然后告诉你一个简单的方法来防止它。如果你已经熟悉了n+1选择问题,你可以直接跳到解决方案

Hibernate provides a very convenient mapping for relationships between entities. You just need an attribute with the type of the related entity and a few annotations to define it:

Hibernate为实体之间的关系提供了一个非常方便的映射。你只需要一个带有相关实体类型的属性和一些注解来定义它。

@Entity
@Table(name = "purchaseOrder")
public class Order implements Serializable {

    @OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
    private Set<OrderItem> items = new HashSet<OrderItem>();

    ...
}

When you now load an Order entity from the database, you just need to call the getItems() method to get all items of this order. Hibernate hides the required database queries to get the related OrderItem entities from the database.

当你现在从数据库加载一个Order实体时,你只需要调用getItems()方法来获取这个订单的所有项目。Hibernate隐藏了从数据库中获取相关OrderItem实体所需的数据库查询。

When you started with Hibernate, you probably learned that you should use FetchType.LAZY for most of the relationships and that it’s the default for to-many relationships. This tells Hibernate to only fetch the related entities if you use the attribute which maps the relationship. Fetching only the data you need is a good thing in general, but it also requires Hibernate to execute an additional query to initialize each relationship. This can result in a huge number of queries, if you work on a list of entities, like I do in the following code snippet:

当你开始使用Hibernate时,你可能了解到你应该对大多数关系使用FetchType.LAZY,并且它是to-many关系的默认值。这告诉Hibernate只有在你使用映射关系的属性时才会获取相关实体。一般来说,只获取你需要的数据是件好事,但它也要求Hibernate执行一个额外的查询来初始化每个关系。如果你像我在下面的代码片断中那样处理一个实体列表,这可能会导致大量的查询。

List<Order> orders = em.createQuery("SELECT o FROM Order o").getResultList();
for (Order order : orders) {
    log.info("Order: " + order.getOrderNumber());
    log.info("Number of items: " + order.getItems().size());
}

You probably wouldn’t expect that this few lines of code can create hundreds or even thousands of database queries. But it does if you use FetchType.LAZY for the relationship to the OrderItem entity:

你可能不会想到这几行代码可以创建数百甚至数千次的数据库查询。但如果你在与OrderItem实体的关系中使用FetchType.LAZY,它就能做到。

22:47:30,065 DEBUG SQL:92 - select 
    order0_.id as id1_2_, 
    order0_.orderNumber as orderNum2_2_, 
    order0_.version as version3_2_ 
  from purchaseOrder order0_
22:47:30,136 INFO NamedEntityGraphTest:58 - Order: order1
22:47:30,140 DEBUG SQL:92 - select 
    items0_.order_id as order_id4_0_0_, 
    items0_.id as id1_0_0_, 
    items0_.id as id1_0_1_, 
    items0_.order_id as order_id4_0_1_, 
    items0_.product_id as product_5_0_1_, 
    items0_.quantity as quantity2_0_1_, 
    items0_.version as version3_0_1_ 
  from OrderItem items0_ 
  where items0_.order_id=?
22:47:30,171 INFO NamedEntityGraphTest:59 - Number of items: 2
22:47:30,171 INFO NamedEntityGraphTest:58 - Order: order2
22:47:30,172 DEBUG SQL:92 - select 
    items0_.order_id as order_id4_0_0_, 
    items0_.id as id1_0_0_, 
    items0_.id as id1_0_1_, 
    items0_.order_id as order_id4_0_1_, 
    items0_.product_id as product_5_0_1_, 
    items0_.quantity as quantity2_0_1_, 
    items0_.version as version3_0_1_ 
  from OrderItem items0_ 
  where items0_.order_id=?
22:47:30,174 INFO NamedEntityGraphTest:59 - Number of items: 2
22:47:30,174 INFO NamedEntityGraphTest:58 - Order: order3
22:47:30,174 DEBUG SQL:92 - select 
    items0_.order_id as order_id4_0_0_, 
    items0_.id as id1_0_0_, 
    items0_.id as id1_0_1_, 
    items0_.order_id as order_id4_0_1_, 
    items0_.product_id as product_5_0_1_, 
    items0_.quantity as quantity2_0_1_, 
    items0_.version as version3_0_1_ 
  from OrderItem items0_ 
  where items0_.order_id=?
22:47:30,176 INFO NamedEntityGraphTest:59 - Number of items: 2

Hibernate performs one query to get all Order entities and an additional one for each of the n Order entities to initialize the orderItem relationship. So you now know why this kind of issue is called n+1 select issue and why it can create huge performance problems.

Hibernate执行一个查询来获取所有的Order实体,并为n个Order实体中的每个实体执行一个额外的查询来初始化orderItem关系。所以你现在知道为什么这种问题被称为n+1选择问题,以及为什么它会产生巨大的性能问题。

What makes it even worse is, that you often don’t recognize it on a small test database, if you haven’t checked your Hibernate statistics. The code snippet requires only a few dozen queries if the test database doesn’t contain a lot of orders. But that will be completely different if you use your productive database which contains several thousand of them.

更糟糕的是,如果你没有检查你的Hibernate统计数据,你往往不能在一个小的测试数据库上识别它。如果测试数据库不包含大量的订单,该代码片段只需要几十个查询。但如果你使用你的生产性数据库,其中包含几千个,那就完全不同了。

I said earlier that you can easily avoid these issues. And that’s true. You just have to initialize the orderItem relationship when you select the Order entities from the database.

我之前说过,你可以轻松地避免这些问题。而这是真的。你只需要在从数据库中选择Order实体时初始化orderItem关系。

But please, only do that, if you use the relationship in your business code and don’t use FetchType.EAGER to always fetch the related entities. That just replaces your n+1 issue with another performance problem.

但是,请只在你在业务代码中使用关系,并且不使用FetchType.EAGER来一直获取相关实体的情况下才这样做。这只是用另一个性能问题取代了你的n+1问题。

Initialize a Relationships with a @NamedEntityGraph

@NamedEntityGraph来初始化一个关系。

There are several different options to initialize relationships. I prefer to use a @NamedEntityGraph which is is one of my favorite features introduced in JPA 2.1. It provides a query independent way to specify a graph of entities which Hibernate shall fetch from the database. In following code snippet, you can see an example of a simple graph that lets Hibernate eagerly fetch the items attribute of an entity:

有几种不同的选择来初始化关系。我更喜欢使用@NamedEntityGraph,这是我最喜欢的JPA 2.1中引入的功能之一。它提供了一种独立于查询的方式来指定Hibernate将从数据库中获取的实体图。在下面的代码片段中,你可以看到一个简单的图的例子,它让Hibernate急切地获取一个实体的项目属性。

@Entity
@Table(name = "purchase_order")
@NamedEntityGraph(
  name = "graph.Order.items", 
  attributeNodes = @NamedAttributeNode("items"))
public class Order implements Serializable {

    ...
}

There isn’t much you need to do to define an entity graph with a @NamedEntityGraph annotation. You just have to provide a unique name for the graph and one @NamedAttributeNode annotation for each attribute Hibernate shall fetch eagerly. In this example, it’s only the items attribute which maps the relationship between an Order and several OrderItem entities.

要用@NamedEntityGraph注解来定义实体图,你不需要做很多事情。你只需要为图提供一个唯一的名字,并为Hibernate将急于获取的每个属性提供一个@NamedAttributeNode注解。在这个例子中,只有items属性映射了一个Order和几个OrderItem实体之间的关系。

Now you can use the entity graph to control the fetching behaviour or a specific query. You, therefore, have to instantiate an EntityGraph based on the @NamedEntityGraph definition and provide it as a hint to the EntityManager.find() method or your query. I do this in the following code snippet where I select the Order entity with id 1 from the database:

现在你可以使用实体图来控制获取行为或特定查询。因此,你必须根据@NamedEntityGraph定义实例化一个EntityGraph,并将其作为提示提供给EntityManager.find()方法或你的查询。我在下面的代码片段中这样做,我从数据库中选择了id为1的Order实体。

EntityGraph graph = this.em.getEntityGraph("graph.Order.items");

Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);

return this.em.find(Order.class, 1L, hints);

Hibernate uses this information to create one SQL statement which gets the attributes of the Order entity and the attributes of the entity graph from the database:

Hibernate使用这些信息来创建一条SQL语句,从数据库中获取Order实体的属性和实体图的属性。

17:34:51,310 DEBUG [org.hibernate.loader.plan.build.spi.LoadPlanTreePrinter] (pool-2-thread-1) 
  LoadPlan(entity=blog.thoughts.on.java.jpa21.entity.graph.model.Order) 
    - Returns 
      - EntityReturnImpl(
          entity=blog.thoughts.on.java.jpa21.entity.graph.model.Order, 
          querySpaceUid=<gen:0>, 
          path=blog.thoughts.on.java.jpa21.entity.graph.model.Order) 
        - CollectionAttributeFetchImpl(
            collection=blog.thoughts.on.java.jpa21.entity.graph.model.Order.items, 
            querySpaceUid=<gen:1>, 
            path=blog.thoughts.on.java.jpa21.entity.graph.model.Order.items)
          - (collection element) CollectionFetchableElementEntityGraph(
              entity=blog.thoughts.on.java.jpa21.entity.graph.model.OrderItem, 
              querySpaceUid=<gen:2>, 
              path=blog.thoughts.on.java.jpa21.entity.graph.model.Order.items.<elements>) 
            - EntityAttributeFetchImpl(entity=blog.thoughts.on.java.jpa21.entity.graph.model.Product,
                querySpaceUid=<gen:3>, 
                path=blog.thoughts.on.java.jpa21.entity.graph.model.Order.items.<elements>.product) 
    - QuerySpaces 
      - EntityQuerySpaceImpl(uid=<gen:0>, entity=blog.thoughts.on.java.jpa21.entity.graph.model.Order)
        - SQL table alias mapping - order0_ 
        - alias suffix - 0_ 
        - suffixed key columns - {id1_2_0_} 
        - JOIN (JoinDefinedByMetadata(items)) : <gen:0> -> <gen:1> 
          - CollectionQuerySpaceImpl(uid=<gen:1>, 
              collection=blog.thoughts.on.java.jpa21.entity.graph.model.Order.items) 
            - SQL table alias mapping - items1_ 
            - alias suffix - 1_ 
            - suffixed key columns - {order_id4_2_1_} 
            - entity-element alias suffix - 2_ 
            - 2_entity-element suffixed key columns - id1_0_2_ 
            - JOIN (JoinDefinedByMetadata(elements)) : <gen:1> -> <gen:2> 
              - EntityQuerySpaceImpl(uid=<gen:2>, 
                  entity=blog.thoughts.on.java.jpa21.entity.graph.model.OrderItem) 
                - SQL table alias mapping - items1_ 
                - alias suffix - 2_ 
                - suffixed key columns - {id1_0_2_}
                - JOIN (JoinDefinedByMetadata(product)) : <gen:2> -> <gen:3> 
                  - EntityQuerySpaceImpl(uid=<gen:3>, 
                      entity=blog.thoughts.on.java.jpa21.entity.graph.model.Product) 
                    - SQL table alias mapping - product2_ 
                    - alias suffix - 3_ 
                    - suffixed key columns - {id1_1_3_}
17:34:51,311 DEBUG [org.hibernate.loader.entity.plan.EntityLoader] (pool-2-thread-1) 
  Static select for entity blog.thoughts.on.java.jpa21.entity.graph.model.Order [NONE:-1]: 
  select order0_.id as id1_2_0_, 
    order0_.orderNumber as orderNum2_2_0_, 
    order0_.version as version3_2_0_, 
    items1_.order_id as order_id4_2_1_, 
    items1_.id as id1_0_1_, 
    items1_.id as id1_0_2_, 
    items1_.order_id as order_id4_0_2_, 
    items1_.product_id as product_5_0_2_, 
    items1_.quantity as quantity2_0_2_, 
    items1_.version as version3_0_2_, 
    product2_.id as id1_1_3_, 
    product2_.name as name2_1_3_, 
    product2_.version as version3_1_3_ 
  from purchase_order order0_ 
    left outer join OrderItem items1_ on order0_.id=items1_.order_id 
    left outer join Product product2_ on items1_.product_id=product2_.id 
  where order0_.id=?

Initializing only one relationship is good enough for a blog post but in a real project, you will most likely want to build more complex graphs. So let’s do that.
You can, of course, provide an array of @NamedAttributeNode annotations to fetch multiple attributes of the same entity and you can use @NamedSubGraph to define the fetching behaviour for an additional level of entities. I use that in the following code snippet to fetch not only all related OrderItem entities but also the Product entity for each OrderItem:

对于一篇博客文章来说,只初始化一个关系已经足够了,但在一个真正的项目中,你很可能想要建立更复杂的图。因此,让我们来做这件事。
当然,你可以提供一个@NamedAttributeNode注解的数组来获取同一实体的多个属性,你可以使用@NamedSubGraph来定义额外级别实体的获取行为。我在下面的代码片段中使用了这一点,不仅可以获取所有相关的OrderItem实体,还可以获取每个OrderItem的Product实体:

@Entity
@Table(name = "purchase_order")
@NamedEntityGraph(
  name = "graph.Order.items", 
  attributeNodes = @NamedAttributeNode(value = "items", subgraph = "items"), 
  subgraphs = @NamedSubgraph(name = "items", attributeNodes = @NamedAttributeNode("product")))
public class Order implements Serializable {

    ...
}

As you can see, the definition of a @NamedSubGraph is very similar to the definition of a @NamedEntityGraph. You can then reference this subgraph in a @NamedAttributeNode annotation to define the fetching behaviour for this specific attribute.

正如你所看到的,@NamedSubGraph的定义与@NamedEntityGraph的定义非常相似。然后你可以在@NamedAttributeNode注解中引用这个子图来定义这个特定属性的获取行为。

The combination of these annotations allows you to define complex entity graphs which you can use to initialize all relationships you use in your use case and avoid n+1 select issues. If you want to specify your entity graph dynamically at runtime, you can do this also via a Java API.

这些注释的组合允许你定义复杂的实体图,你可以用它来初始化你在用例中使用的所有关系,并避免n+1选择问题。如果您想在运行时动态地指定您的实体图,您也可以通过Java API来做到这一点。

2.3. Update Entities One by One

2.3.逐一更新实体

Updating entities one by one feels very natural if you think in an object oriented way. You just get the entities you want to update and call a few setter methods to change their attributes like you do it with any other object.

如果你以面向对象的方式思考,一个一个地更新实体感觉非常自然。你只需要得到你想更新的实体,然后调用一些setter方法来改变它们的属性,就像你对其他对象那样。

This approach works fine if you only change a few entities. But it gets very inefficient when you work with a list of entities and is the third performance issues you can easily spot in your log file. You just have to look for a bunch SQL UPDATE statements that look completely the same, as you can see in the following log file:

如果你只改变几个实体,这种方法很有效。但是当你处理一个实体列表时,它的效率就会变得非常低,这也是你在日志文件中可以轻易发现的第三个性能问题。你只需寻找一堆看起来完全相同的SQL UPDATE语句,正如你在下面的日志文件中看到的那样。

22:58:05,829 DEBUG SQL:92 - select 
  product0_.id as id1_1_, 
  product0_.name as name2_1_, 
  product0_.price as price3_1_, 
  product0_.version as version4_1_ from Product product0_
22:58:05,883 DEBUG SQL:92 - update Product set name=?, price=?, version=? where id=? and version=?
22:58:05,889 DEBUG SQL:92 - update Product set name=?, price=?, version=? where id=? and version=?
22:58:05,891 DEBUG SQL:92 - update Product set name=?, price=?, version=? where id=? and version=?
22:58:05,893 DEBUG SQL:92 - update Product set name=?, price=?, version=? where id=? and version=?
22:58:05,900 DEBUG SQL:92 - update Product set name=?, price=?, version=? where id=? and version=?

The relational representation of the database records is a much better fit for these use cases than the object oriented one. With SQL, you could just write one SQL statement that updates all the records you want to change.

数据库记录的关系表示法比面向对象的表示法更适合于这些用例。使用SQL,你可以只写一条SQL语句来更新所有你想改变的记录。

You can do the same with Hibernate if you use JPQL, native SQL or the CriteriaUpdate API. All 3 of very similar, so let’s use JPQL in this example.

如果您使用JPQL、原生SQLCriteriaUpdate API,您可以用Hibernate做同样的事情。这3种方式都非常相似,所以在这个例子中我们使用JPQL。

You can define a JPQL UPDATE statement in a similar way as you know it from SQL. You just define which entity you want to update, how to change the values of its attributes and limit the affected entities in the WHERE statement.
You can see an example of it in the following code snippet where I increase the price of all products by 10%:

你可以用类似于你从SQL中知道的方式来定义一个JPQL UPDATE语句。你只需定义你要更新的实体,如何改变其属性值,并在WHERE语句中限制受影响的实体。
你可以在下面的代码片断中看到一个例子,我将所有产品的价格提高了10%。

em.createQuery("UPDATE Product p SET p.price = p.price*0.1").executeUpdate();

Hibernate creates an SQL UPDATE statement based on the JPQL statement and sends it to the database which performs the update operation.

Hibernate根据JPQL语句创建一个SQL UPDATE语句,并将其发送到数据库,由数据库执行更新操作。

It’s pretty obvious that this approach is a lot faster if you have to update a huge number entities. But it also has a drawback. Hibernate doesn’t know which entities are affected by the update operation and doesn’t update its 1st level cache. You should, therefore, make sure not to read and update an entity with a JPQL statement within the same Hibernate Session or you have to detach it to remove it from the cache.

很明显,如果你要更新大量的实体,这种方法会快很多。但它也有一个缺点。Hibernate不知道哪些实体会受到更新操作的影响,也不会更新它的一级缓存。因此,你应该确保不要在同一个Hibernate会话中用JPQL语句读取和更新一个实体,否则你就必须把它从缓存中分离出来,删除它。

3. Summary

3.总结

Within this post, I’ve shown you 3 Hibernate performance issues which you can find in your log files.

在这篇文章中,我向你展示了3个Hibernate性能问题,你可以在日志文件中找到。

2 of them were caused by a huge number of SQL statements. This a common reason for performance issues, if you work with Hibernate. Hibernate hides the database access behind its API and that makes it often difficult to guess the actual number of SQL statements. You should therefore always check the executed SQL statements when you make a change to your persistence tier.

其中2个是由大量的SQL语句引起的。如果你使用Hibernate,这就是性能问题的一个常见原因。Hibernate将数据库访问隐藏在其API后面,这使得我们通常很难猜测SQL语句的实际数量。因此,当你对持久层进行修改时,你应该始终检查所执行的SQL语句。