簡體   English   中英

為什么加載相關實體上的惰性字段

[英]Why are lazy fields on related entities loaded

在我的 REST API 項目(Java 8,Spring Boot 2.3.1)中,我遇到了一些問題,盡管通過加載延遲關系觸發了大量查詢鏈的相關查詢從未被訪問過。

我有一個與ManyToMany關系相關的UserEntity和多態CompanyEntity 我有一個返回所有用戶的端點,我在 JSON 中包含相關公司的 ID。 我期望查詢用戶表和查詢公司表,但是CompanyEntity的一個子實體的所有相關實體總是為每個子實體加載,從而導致大型查詢鏈。

以下是我的課程片段:

用戶實體

@Entity(name = "USERS")
public class UserEntity {

  @Id
  @GeneratedValue
  private UUID id;

  @EqualsAndHashCode.Exclude
  @Fetch(FetchMode.SUBSELECT)
  @ManyToMany(fetch = FetchType.LAZY)
  @JoinTable(
      name = "users_company",
      joinColumns = @JoinColumn(name = "USER_ID"),
      inverseJoinColumns = @JoinColumn(name = "COMPANY_ID")
  )
  private Set<CompanyEntity> companies = new HashSet<>();

  public List<UUID> getCompanyIds() {
    return companies.stream()
        .map(CompanyEntity::getId)
        .collect(Collectors.toList());
  }

}

多態公司實體

@Entity(name = "COMPANY")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class CompanyEntity {

  @Id
  @GeneratedValue
  private UUID id;

  @Fetch(FetchMode.SUBSELECT)
  @ManyToMany(mappedBy = "companies", fetch = FetchType.LAZY)
  private Set<UserEntity> users = new HashSet<>();
}

觸發問題的具體公司子類

@Entity(name = "CUSTOMER")
public class CustomerEntity extends CompanyEntity {

  @NotNull
  @OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
  private ContactPersonEntity contactPerson;

  @Fetch(FetchMode.SUBSELECT)
  @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY, mappedBy = "customer")
  private Set<TransactionEntity> transactions = new HashSet<>();

  public Set<UUID> getTransactionIds() {
    return this.transactions.stream()
        .map(TransactionEntity::getId)
        .collect(Collectors.toSet());
  }
}

在 REST controller 我返回以下映射:

@GetMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE)
public List<UserReadModel> getUsers() {
  return userRepository.findAll().stream()
      .map(userEntity -> new UserReadModel(userEntity))
      .collect(Collectors.toList());
}

其中UserReadModel是 DTO:

@Data
public class UserReadModel {

  private UUID id;
  private List<UUID> companyIds;

}

記錄數據庫查詢會產生以下 output:

// Expected
Hibernate: select userentity0_.id as id1_47_, ... from users userentity0_
Hibernate: select companies0_.user_id ... case when companyent1_1_.id is not null then 1 when companyent1_2_.id is not null then 2 when companyent1_.id is not null then 0 end as clazz_0_ from users_company companies0_ inner join company companyent1_ on companies0_.company_id=companyent1_.id left outer join customer companyent1_1_ on companyent1_.id=companyent1_1_.id left outer join external_editor companyent1_2_ on companyent1_.id=companyent1_2_.id where companies0_.user_id in (select userentity0_.id from users userentity0_)

// Unexpected as they are marked lazy and never accessed
Hibernate: select contactper0_.id ... from contact_person contactper0_ where contactper0_.id=?
Hibernate: select transactio0_.customer_id ... from transactions transactio0_ where transactio0_.customer_id=?
Hibernate: select contactper0_.id ... from contact_person contactper0_ where contactper0_.id=?
Hibernate: select transactio0_.customer_id ... from transactions transactio0_ where transactio0_.customer_id=?
...

我已經閱讀了大量關於實體映射和延遲加載的文章,但我似乎找不到這種行為持續存在的原因。 以前有人遇到過這個問題嗎?

您正在訪問該集合,因此 Hibernate 必須加載該集合。 由於您只需要 id 並且已經擁有 DTO,因此我認為這是Blaze-Persistence Entity Views的完美用例。

我創建了該庫以允許在 JPA 模型和自定義接口或抽象 class 定義的模型之間輕松映射,例如 Spring 類固醇上的數據投影。 這個想法是您以您喜歡的方式定義您的目標結構(域模型),並通過 JPQL 表達式將 map 屬性(吸氣劑)定義為實體 model。

使用 Blaze-Persistence Entity-Views 的 DTO model 可能如下所示:

@EntityView(UserEntity.class)
public interface UserReadModel {
    @IdMapping
    UUID getId();
    @Mapping("companies.id")
    Set<UUID> getCompanyIds();
}

查詢是將實體視圖應用於查詢的問題,最簡單的就是通過 id 進行查詢。

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

Spring 數據集成允許您幾乎像 Spring 數據投影一樣使用它: https://persistence.blazebit.com/documentation/entity-view/manual-html/

Page<UserReadModel> findAll(Pageable pageable);

最好的部分是,它只會獲取實際需要的 state,在您的情況下:將生成如下查詢:

select u.id, uc.company_id
from users u
left join users_company uc on uc.user_id = u.id
left join company c on c.id = uc.company_id

根據 Hibernate 版本,甚至可能會省略公司的連接。

我最終想出了解決方案並想在這里發布,以防有人偶然發現這個問題。 這純粹是我的一個錯誤,並且無法從我發布的示例中重現。

我使用 lombok 注釋在客戶實體(以及所有其他實體)上生成 equals 和 hashcode 方法,並忘記使用@EqualsAndHashcode.Exclude注釋contactPersontransactions字段。 由於在執行過程中某處調用了 equals 方法,它觸發了這些字段的延遲加載。 手動實現 equals 和 hashcode 並使用本文中的指南解決了這個問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM