简体   繁体   中英

Prevent “n+1 selects” with JPA/Hibernate constructor expression?

I have two entities, Item and Data , and a DTO class ItemData . ItemData consists of Item and Data and has no JPA mapping. To retreive a list of populated ItemDatas, I use a constructor expression in JPQL:

select new my.package.ItemData(i, d)
from Item i, Data d
where i.id = d.itemId

This is what Hibernate is doing: Instead of fetching both the data of Item and Data , it gets their IDs first and the data afterwards in n separate select statements. Is there a way to change this behaviour?

Hibernate:
    select
        item0_.id as col_0_0_,
        data1_.id as col_1_0_ 
    from
        ITEM item0_,
        DATA data1_

Hibernate: 
    select
        item0_.no as no1_0_,
        item0_.description as description1_0_,
        item0_.organic as bio1_0_,
        item0_.gluten as gluten1_0_,
        item0_.laktose as laktose1_0_
    from
        ITEM item0_ 
    where
        item0_.id=?

Hibernate: 
    select
        data0_.amount as amount1_3_0_,
        data0_.avg as avg3_0_,
        data0_.total as total3_0_
    from
        DATA data0_ 
    where
        data0_.id=?

Hibernate: 
    select
        item0_.no as no1_0_,
        item0_.description as description1_0_,
        item0_.organic as bio1_0_,
        item0_.gluten as gluten1_0_,
        item0_.laktose as laktose1_0_
    from
        ITEM item0_ 
    where
        item0_.id=?

Hibernate: 
    select
        data0_.amount as amount1_3_0_,
        data0_.avg as avg3_0_,
        data0_.total as total3_0_
    from
        DATA data0_ 
    where
        data0_.id=?

...and so on...        

What about using left join fetch?
I admit I didn't use CTOR expressions, but when I need to fetch a parent entity and its children, I used left join fetch and it always worked like a charm.
I can only assume that since we're dealing here with construction of objects,
Hibernate does not "want" to leave an object "partially constructed" (or let's say - an object in "lazy evaluation state") , and since you did not perform an "eager fetch" using "left join fetch", it performs N+1 fetches:
At first it fetches all the IDs, and then for each relevant ID it performs another fetch.
Read more about left join fetch here (simply look for "left join fetch" at the page)

This is a perfect use case forBlaze-Persistence Entity Views .

I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.

I suppose your entity model looks something like this?

@Entity
class Item {
  @Id Long id;
  String description;
  @OneToMany(mappedBy = "item")
  Data data;
  ...
}
@Entity
class Data {
  @Id Long id;
  @ManyToOne Item item;
  Long amount;
  BigDecimal avg;
  ...
}

A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:

@EntityView(Item.class)
public interface ItemData {
    @IdMapping
    Long getNo();
    String getDescription();
    @Mapping("SUM(data.amount)")
    Long getAmount();
}

Querying is a matter of applying the entity view to a query, the simplest being just a query by id.

ItemData a = entityViewManager.find(entityManager, ItemData.class, id);

The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

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