简体   繁体   中英

Hibernate creating N+1 queries for @ManyToOne JPA annotated property

I have these classes:

@Entity
public class Invoice implements Serializable {
    @Id
    @Basic(optional = false)
    private Integer number;

    private BigDecimal value;

    //Getters and setters
}

@Entity
public class InvoiceItem implements Serializable {
    @EmbeddedId
    protected InvoiceItemPK invoiceItemPk;

    @ManyToOne
    @JoinColumn(name = "invoice_number", insertable = false, updatable = false)
    private Invoice invoice;

    //Getters and setters
}

When i run this query:

session.createQuery("select i from InvoiceItem i").list();

It executes one query to select the records from InvoiceItem, and if I have 10000 invoice items, it generates 10000 additional queries to select the Invoice from each InvoiceItem.

I think it would be a lot better if all the records could be fetched in a single sql. Actually, I find it weird why it is not the default behavior.

So, how can I do it?

The problem here is not related to Hibernate, but to JPA.

Prior to JPA 1.0, Hibernate 3 used lazy loading for all associations.

However, the JPA 1.0 specification uses FetchType.LAZY only for collection associations:

The @ManyToOne and @OneToOne associations use FetchType.EAGER by default, and that's very bad from a performance perspective.

The behavior described here is called the [N+1 query issue][5], and it happens because Hibernate needs to make sure that the @ManyToOne association is initialized prior to returning the result to the user.

Now, if you are using direct fetching via entityManager.find , Hibernate can use a LEFT JOIN to initialize the FetchTYpe.EAGER associations.

However, when executing a query that does not explicitly use a JOIN FETCH clause, Hibernate will not use a JOIN to fetch the FetchTYpe.EAGER associations, as it cannot alter the query that you already specified how to be constructed. So, it can only use secondary queries.

The fix is simple. Just use FetchType.LAZY for all associations:

   @ManyToOne(fetch = FetchType.LAZY)
   @JoinColumn(name = "invoice_number", insertable = false, updatable = false)
   private Invoice invoice;

More, you should use the db-util project to assert the number of statements executed by JPA and Hibernate.

Try with

session.createQuery("select i from InvoiceItem i join fetch i.invoice inv").list();

It should get all the data in a single SQL query by using joins.

Yes there is setting you need: @BatchSize(size=25) . Check it here:

20.1.5. Using batch fetching

small cite:

Using batch fetching, Hibernate can load several uninitialized proxies if one proxy is accessed. Batch fetching is an optimization of the lazy select fetching strategy. There are two ways you can configure batch fetching: on the class level and the collection level.

Batch fetching for classes/entities is easier to understand. Consider the following example: at runtime you have 25 Cat instances loaded in a Session, and each Cat has a reference to its owner, a Person. The Person class is mapped with a proxy, lazy="true". If you now iterate through all cats and call getOwner() on each, Hibernate will, by default, execute 25 SELECT statements to retrieve the proxied owners. You can tune this behavior by specifying a batch-size in the mapping of Person:

<class name="Person" batch-size="10">...</class>

With this batch-size specified, Hibernate will now execute queries on demand when need to access the uninitialized proxy, as above, but the difference is that instead of querying the exactly proxy entity that being accessed, it will query more Person's owner at once, so, when accessing other person's owner, it may already been initialized by this batch fetch with only a few ( much less than 25) queries will be executed.

So, we can use that annotation on both:

  • collections/sets
  • classes/Entities

Check it also here:

In this Method there are Multiple SQLs fired. This first one is fired for retrieving all the records in the Parent table. The remaining are fired for retrieving records for each Parent Record. The first query retrieves M records from database, in this case M Parent records. For each Parent a new query retrieves Child.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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