Hibernate 在使用嵌套封闭 JPA 投影时生成不必要的查询
Hibernate Generating Unnecessary Queries When Using Nested Closed JPA Projections
我 运行 遇到了 JPA/Hibernate 的奇怪问题,希望能得到一些帮助。
我的环境:
- OpenJDK 11.0.2 Spring 引导 2.2.2
- spring-boot-starter-JPA(以上Spring引导版本的默认版本)
- 休眠 5.4.9
- 玛丽亚数据库 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
}
我定义了以下(已关闭)投影接口,每个都在它们自己的 class 文件中:
@Projection(types={ User.class })
interface UserProjection {
Long getId();
EmployeeFirstNameProjection getEmployee();
}
@Projection(types={ Employee.class })
interface EmployeeFirstNameProjection {
String getFirstName();
}
我调用的是这个Repository接口查询方法:
<T> T findUserById(Long id, Class <T> type);
并使用此服务方法调用上述 Repository 方法:
public UserProjection getUser(Long id) {
return userRepository.findUserById(id, UserProjection.class);
}
所以在运行时,这里是单个返回的 UserProjection 的 JSON:
{"employee":{"firstName":"Matt"},"id":1796}
这正是我想要返回的。但是,当代码执行时,Hibernate 在两个查询中 selecting 两个实体中的所有字段,这是我不希望的。我使用 Projections 机制的全部原因是限制 JSON 的线路流量,并且出于性能原因希望希望保持较低的查询数量。
我希望看到单个 Hibernate 生成的查询。
为什么 Hibernate 运行 两个各自的查询 select 每个实体中的每个字段?
提前致谢!
这是 Spring-Data Projections 的限制,是 Blaze-Persistence Entity Views.
的完美用例
Blaze-Persitence 是基于 JPA 的查询构建器,它支持基于 JPA 模型的许多高级 DBMS 功能。我在它之上创建了实体视图,以允许在 JPA 模型和自定义接口定义的模型之间轻松映射,类似于 Spring 类固醇数据投影。这个想法是您按照自己喜欢的方式定义目标结构,并通过 JPQL 表达式将属性(getter)映射到实体模型。由于属性名称用作默认映射,因此您大多不需要显式映射,因为 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 数据集成让您几乎可以像 Spring 数据投影一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
它只会获取您告诉它获取的映射
Hibernate 和 Spring-data 正常工作,当投影中存在非原始字段时,所有列都包含在查询中。
这里已经解释过 and a jira was opened at that moment https://jira.spring.io/browse/DATAJPA-1218 并以这个答案结束 "Currently, we don't optimize the query for referenced entities by only selecting the required properties"。
作为解决方法,您可以使用自定义 DTO 创建 JPQL 查询,或者尝试使用 @christian-beikov 提到的类似 Blaze-Persitence 的方法
我 运行 遇到了 JPA/Hibernate 的奇怪问题,希望能得到一些帮助。 我的环境:
- OpenJDK 11.0.2 Spring 引导 2.2.2
- spring-boot-starter-JPA(以上Spring引导版本的默认版本)
- 休眠 5.4.9
- 玛丽亚数据库 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
}
我定义了以下(已关闭)投影接口,每个都在它们自己的 class 文件中:
@Projection(types={ User.class })
interface UserProjection {
Long getId();
EmployeeFirstNameProjection getEmployee();
}
@Projection(types={ Employee.class })
interface EmployeeFirstNameProjection {
String getFirstName();
}
我调用的是这个Repository接口查询方法:
<T> T findUserById(Long id, Class <T> type);
并使用此服务方法调用上述 Repository 方法:
public UserProjection getUser(Long id) {
return userRepository.findUserById(id, UserProjection.class);
}
所以在运行时,这里是单个返回的 UserProjection 的 JSON:
{"employee":{"firstName":"Matt"},"id":1796}
这正是我想要返回的。但是,当代码执行时,Hibernate 在两个查询中 selecting 两个实体中的所有字段,这是我不希望的。我使用 Projections 机制的全部原因是限制 JSON 的线路流量,并且出于性能原因希望希望保持较低的查询数量。
我希望看到单个 Hibernate 生成的查询。
为什么 Hibernate 运行 两个各自的查询 select 每个实体中的每个字段?
提前致谢!
这是 Spring-Data Projections 的限制,是 Blaze-Persistence Entity Views.
的完美用例Blaze-Persitence 是基于 JPA 的查询构建器,它支持基于 JPA 模型的许多高级 DBMS 功能。我在它之上创建了实体视图,以允许在 JPA 模型和自定义接口定义的模型之间轻松映射,类似于 Spring 类固醇数据投影。这个想法是您按照自己喜欢的方式定义目标结构,并通过 JPQL 表达式将属性(getter)映射到实体模型。由于属性名称用作默认映射,因此您大多不需要显式映射,因为 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 数据集成让您几乎可以像 Spring 数据投影一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
它只会获取您告诉它获取的映射
Hibernate 和 Spring-data 正常工作,当投影中存在非原始字段时,所有列都包含在查询中。
这里已经解释过 and a jira was opened at that moment https://jira.spring.io/browse/DATAJPA-1218 并以这个答案结束 "Currently, we don't optimize the query for referenced entities by only selecting the required properties"。
作为解决方法,您可以使用自定义 DTO 创建 JPQL 查询,或者尝试使用 @christian-beikov 提到的类似 Blaze-Persitence 的方法