我在JPA / Hibernate中遇到了一个奇怪的问题,希望能得到一些帮助。我的环境:
我正在使用两个实体:
@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运行两个各自的查询来选择每个实体中的每个字段?
提前感谢!
这是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
它只会获取您告诉它要获取的映射
Hibernate和Spring数据按预期方式工作,当投影中有一个非原始字段时,所有列都包含在查询中。
[已经在此处解释了https://stackoverflow.com/a/53783270/3449039,并在那时https://jira.spring.io/browse/DATAJPA-1218打开了一个jira,并使用以下答案关闭了它:“当前,我们不仅仅通过选择所需的属性来优化对引用实体的查询”。
作为解决方法,您可以使用自定义DTO创建JPQL查询,或尝试像@ christian-beikov提到的Blaze-Persitence之类的东西