使用嵌套的封闭式JPA投影时休眠生成不必要的查询

问题描述 投票:1回答:2

我在JPA / Hibernate中遇到了一个奇怪的问题,希望能得到一些帮助。我的环境:

  • OpenJDK 11.0.2 Spring Boot 2.2.2
  • spring-boot-starter-JPA(以上Spring Boot版本的默认版本)
  • 休眠5.4.9
  • Maria DB 2.3.0
  • Windows 10

我正在使用两个实体:

@Entity
@Table(name = "\"accUser\"") 
public class User implements Serializable {

    private static final long serialVersionUID = -5750077342980986498L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "UserID")
    private Long id;

    // NOTE: Below is an embedded object!
    @OneToOne
    @JoinColumn(name = "EmpNum")
    private Employee employee;

    //...other fields are wrapped primitives omitted for brevity
}

@Entity
@Table(name = "\"hrmEmployee\"")
public class Employee implements Serializable {
    private static final long serialVersionUID = 5471137977607643256L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "EmpNum")
    private Long employeeNumber;

    @Column(name = "FirstName")
    private String firstName;

    //...other fields are wrapped primitives omitted for brevity

}

我定义了以下(封闭的)Projection接口,每个在其自己的类文件中:

@Projection(types={ User.class })
interface UserProjection {
    Long getId();
    EmployeeFirstNameProjection getEmployee();
}

@Projection(types={ Employee.class })
interface EmployeeFirstNameProjection {
    String getFirstName();
}

我正在调用此存储库接口查询方法:

<T> T findUserById(Long id, Class <T> type);

并使用此Service方法调用上述Repository方法:

public UserProjection getUser(Long id) {
    return userRepository.findUserById(id, UserProjection.class);
} 

因此,在运行时,这是单个返回的UserProjection的JSON:

{"employee":{"firstName":"Matt"},"id":1796}

这正是我想退回的东西。但是,当代码执行时,Hibernate会在两个查询中选择两个实体中的所有字段,这是我不想要的。我使用Projections机制的全部原因是为了限制JSON的通信量,并且出于性能方面的考虑,也希望希望保持较低的查询数量。

我希望看到的是单个Hibernate生成的查询。

为什么Hibernate运行两个各自的查询来选择每个实体中的每个字段?

提前感谢!

hibernate jpa projection
2个回答
0
投票

这是Spring-Data Projections的局限,是Blaze-Persistence Entity Views的完美用例。

Blaze-Persitence是JPA之上的查询生成器,它支持JPA模型之上的许多高级DBMS功能。我在其顶部创建了实体视图,以方便在JPA模型和自定义接口定义的模型之间进行映射,例如类固醇上的Spring Data Projections。想法是您以自己喜欢的方式定义目标结构,并通过JPQL表达式将属性(获取器)映射到实体模型。由于属性名称用作默认映射,因此大多数情况下不需要显式映射,因为80%的用例都是将DTO作为实体模型的子集。

模型的映射可能看起来像下面的一样简单

@EntityView(User.class)
interface UserProjection {
    Long getId();
    EmployeeFirstNameProjection getEmployee();
}

@EntityView(Employee.class)
interface EmployeeFirstNameProjection {
    String getFirstName();
}

查询是将实体视图应用于查询的问题,最简单的方法就是按ID查询。

UserProjection dto = entityViewManager.find(entityManager, UserProjection.class, id);

但是,Spring Data集成使您可以像使用Spring Data Projections一样使用它:https://persistence.blazebit.com/documentation/1.4/entity-view/manual/en_US/#spring-data-features

它只会获取您告诉它要获取的映射


0
投票

Hibernate和Spring数据按预期方式工作,当投影中有一个非原始字段时,所有列都包含在查询中。

[已经在此处解释了https://stackoverflow.com/a/53783270/3449039,并在那时https://jira.spring.io/browse/DATAJPA-1218打开了一个jira,并使用以下答案关闭了它:“当前,我们不仅仅通过选择所需的属性来优化对引用实体的查询”。

作为解决方法,您可以使用自定义DTO创建JPQL查询,或尝试像@ christian-beikov提到的Blaze-Persitence之类的东西

© www.soinside.com 2019 - 2024. All rights reserved.