繁体   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