简体   繁体   English

为什么加载相关实体上的惰性字段

[英]Why are lazy fields on related entities loaded

In my REST API project (Java 8, Spring Boot 2.3.1) I have a problem with some queries triggering massive query chains by loading lazy relations, even though the related objects are never accessed.在我的 REST API 项目(Java 8,Spring Boot 2.3.1)中,我遇到了一些问题,尽管通过加载延迟关系触发了大量查询链的相关查询从未被访问过。

I have a UserEntity and a polymorphic CompanyEntity that are related with a ManyToMany relationship.我有一个与ManyToMany关系相关的UserEntity和多态CompanyEntity I have an endpoint that returns all users and I include the IDs of the related companies in the JSON.我有一个返回所有用户的端点,我在 JSON 中包含相关公司的 ID。 I excpect a query to the user table and a query to the company table, however all related entities of one sub-entity of CompanyEntity are always loaded for each of those sub-entities resulting in large query chains.我期望查询用户表和查询公司表,但是CompanyEntity的一个子实体的所有相关实体总是为每个子实体加载,从而导致大型查询链。

Here are snippets of my classes:以下是我的课程片段:

User entity用户实体

@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());
  }

}

Polymorphic company entity多态公司实体

@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<>();
}

Concrete company subclass that triggers the problem触发问题的具体公司子类

@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());
  }
}

In the REST controller I return the following mapping:在 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());
}

Where the UserReadModel is a DTO:其中UserReadModel是 DTO:

@Data
public class UserReadModel {

  private UUID id;
  private List<UUID> companyIds;

}

Logging the database queries results in the following output:记录数据库查询会产生以下 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=?
...

I've read through loads of articles on entity mapping and lazy loading but I can't seem to find a reason why this behavior persists.我已经阅读了大量关于实体映射和延迟加载的文章,但我似乎找不到这种行为持续存在的原因。 Did anyone have this problem before?以前有人遇到过这个问题吗?

You are accessing the collection, so Hibernate has to load the collection.您正在访问该集合,因此 Hibernate 必须加载该集合。 Since you only need the ids and already have a DTO, I think this is a perfect use case forBlaze-Persistence Entity Views .由于您只需要 id 并且已经拥有 DTO,因此我认为这是Blaze-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.我创建了该库以允许在 JPA 模型和自定义接口或抽象 class 定义的模型之间轻松映射,例如 Spring 类固醇上的数据投影。 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.这个想法是您以您喜欢的方式定义您的目标结构(域模型),并通过 JPQL 表达式将 map 属性(吸气剂)定义为实体 model。

A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:使用 Blaze-Persistence Entity-Views 的 DTO model 可能如下所示:

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

Querying is a matter of applying the entity view to a query, the simplest being just a query by id.查询是将实体视图应用于查询的问题,最简单的就是通过 id 进行查询。

UserReadModel a = entityViewManager.find(entityManager, UserReadModel.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 Spring 数据集成允许您几乎像 Spring 数据投影一样使用它: https://persistence.blazebit.com/documentation/entity-view/manual-html/

Page<UserReadModel> findAll(Pageable pageable);

The best part is, it will only fetch the state that is actually necessary, In your case: a query like the following will be generated:最好的部分是,它只会获取实际需要的 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

Depending on the Hibernate version, the join for the company might even be omitted.根据 Hibernate 版本,甚至可能会省略公司的连接。

I eventually figured out the solution and want to post it here, in case anyone stumbles upon this question.我最终想出了解决方案并想在这里发布,以防有人偶然发现这个问题。 This was purely a mistake on my side and is not reproducible from the examples I posted.这纯粹是我的一个错误,并且无法从我发布的示例中重现。

I used lombok annotations to generate equals and hashcode methods on the customer entity (and all other entities for that matter) and forgot to annotate the contactPerson and transactions fields with @EqualsAndHashcode.Exclude .我使用 lombok 注释在客户实体(以及所有其他实体)上生成 equals 和 hashcode 方法,并忘记使用@EqualsAndHashcode.Exclude注释contactPersontransactions字段。 As the equals method was called somewhere along the execution, it triggered the lazy loading of those fields.由于在执行过程中某处调用了 equals 方法,它触发了这些字段的延迟加载。 Implementing equals and hashcode manually and using the guidelines from this article for that solved the problem.手动实现 equals 和 hashcode 并使用本文中的指南解决了这个问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 懒惰的字段总是加载 - Lazy fields always loaded 为什么加载惰性集合 - why the lazy collection is loaded 等于具有延迟加载字段的bean - Equals on beans with lazy loaded fields 使用Jersey和Jackson序列化(延迟加载)实体 - Serializing (lazy loaded) entities with Jersey and Jackson 覆盖休眠映射文件中相关实体的延迟加载设置 - Override lazy loading setting for related entities in a hibernate mapping file 通过Spring将字段注入由Hibernate加载的实体 - Injecting fields via Spring into entities loaded by Hibernate 处理延迟加载的EJB JPA实体的JAX-WS代理对象的最佳方法是什么? - What's the best way to deal with JAX-WS proxy objects for EJB JPA entities that are Lazy Loaded? 使用 JPQL 或标准 API 在省略某些字段的同时恢复具有相关实体的实体列表 - Recover a list of entities with related entities while omitting some fields using JPQL or criteria API 在Hibernate中,当对象的字段被延迟加载时,我只能更改其中一个字段并执行update(object)吗? - In Hibernate, when an object's fields are lazy loaded, can I only change one of the fields and do update(object)? 为什么我不会在每个延迟加载的关系中使用@BatchSize? - Why would I not use @BatchSize on every lazy loaded relationship?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM