即使使用 @Fetch(FetchMode.JOIN),JPA + Hibernate 也会出现太多查询问题
Posted
技术标签:
【中文标题】即使使用 @Fetch(FetchMode.JOIN),JPA + Hibernate 也会出现太多查询问题【英文标题】:Too many queries problem with JPA + Hibernate even when using @Fetch(FetchMode.JOIN) 【发布时间】:2019-12-30 09:48:28 【问题描述】:我正在使用 Spring Boot 开发 REST 应用程序,并且正在尝试优化查询的性能。我目前正在使用导致性能问题的存储库中的findAll
。代码如下:
人物实体
@Entity
@Table(name = "cd_person")
@Data
@NoArgsConstructor
public class Person
....
@OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name = "password_id")
@Fetch(FetchMode.JOIN)
private Password password;
....
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH)
@JoinTable(name = "cd_person_role",
joinColumns = @JoinColumn(name = "person_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
@Fetch(FetchMode.JOIN)
private Set<Role> roles = new HashSet<>();
密码实体
@Entity
@Table(name = "cd_password")
@Data
@NoArgsConstructor
public class Password
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", updatable = false, nullable = false)
private Long id;
@Column(name = "password_hash", nullable = false)
private String passwordHash;
.......
角色实体
@Entity
@Table(name = "cd_role")
@Data
@NoArgsConstructor
public class Role
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "role_type")
@Enumerated(EnumType.STRING)
private RoleType roleType;
....
个人资料库
public interface PersonRepository extends CrudRepository<Person, Long>
Optional<Person> findByEmail(String email);
当我执行personRepository.findAll()
时,会针对人员表中的每一行触发选择查询,以便在我访问人员时获取密码和角色。我知道我可以在存储库中使用@Query
注释和JOIN FETCH
来强制生成单个查询,但我想知道是否还有其他方法可以这样做。我正在寻找我们可以在实体级别做的事情以减少查询。
使用spring boot 2.1.5-RELEASE版本及相关依赖。
附言。 @Data
和 @NoArgsConstructor
是 Lombok 注释。
【问题讨论】:
【参考方案1】:这应该可行:
public interface PersonRepository extends CrudRepository<Person, Long>
@Override
@Query("SELECT p FROM Person p JOIN FETCH p.roles JOIN FETCH p.password ")
Iterable<Person> findAll();
【讨论】:
【参考方案2】:您应该将 @BatchSize 放在 Password 类的顶部
@Entity
@Table(name = "cd_password")
@Data
@NoArgsConstructor
@BatchSize(size = 50)
public class Password
...
以下是带有@BatchSize 的查询:
Hibernate:
select
person0_.id as id1_1_,
person0_.password_id as password2_1_
from
cd_person person0_
Hibernate:
select
password0_.id as id1_0_0_,
password0_.password_hash as password2_0_0_
from
cd_password password0_
where
password0_.id in (
?, ?, ?, ?, ?
)
【讨论】:
感谢您的努力,但我想进一步优化它。批处理会更好,但仍然不是最佳的。如果您可以提供任何关于批处理性能如何优于连接的参考,我会这样做并接受答案。【参考方案3】:我的建议是:
-
尝试重构并使用延迟获取。
我可能不太了解这部分,但你为什么需要
personRepository.findAll()
?我认为您只需要personRepository.findById()
之类的东西,这样您就可以轻松获取角色和其他数据。在这里选择所有人似乎是一个巨大的负担。
您以后可能需要JpaRepository 的扩展功能,因此现在可能值得更改它,而不是稍后再工作。
【讨论】:
【参考方案4】:最小的代码更改是使用 spring data 中的 ad-hoc EntityGraph 功能。只需覆盖 PersonRepository
的 findAll()
并使用 @EntityGraph
来配置图形。此图中的所有实体将一起获取。
public interface PersonRepository extends CrudRepository<Person, Long>
@EntityGraph(attributePaths = "password", "roles" )
public List<Person> findAll();
在幕后它的工作方式类似于JOIN FETCH
。只会生成带有 LEFT JOIN 的单个 SQL。
【讨论】:
与 JOIN FETCH 相比有什么性能优势/劣势,还是代码行数更少? 如果您将遍历所有检索到的人员并访问其密码和角色,则性能良好,因为所有这些都已通过单个 SQL select 获取,没有 N+1 问题?? 我在问这个与 JOIN FETCH 相比。使用@Query 好还是定义实体图好? 它们是一样的。它生成与 JOIN FETCH 相同的 SQL【参考方案5】:对您的问题的不满意答案是:不,无法注释/配置实体,以便获取模式也适用于查询。
您正确地找到了自己,您可以manipulate the query itself。对此的替代方法是使用 Hibernate's fetch profiles 或利用 JPA entity graphs - 但它们都需要在查询/会话级别进行编程干预。
【讨论】:
【参考方案6】:我将保留实体并使用@Query
注释覆盖存储库中的findAll
方法。
这样,代码重构最少(仅更改一个存储库而不是更改实体)。
【讨论】:
【参考方案7】:您不能使用延迟提取并删除 @Fetch
吗?在您的实体之上使用 @NamedQuery
并使用休眠会话在自定义服务中调用 session.createNamedQuery
即可。
如果您负担得起不使用默认的personRepository.findAll()
但此自定义服务您将运行优化查询。我知道它并不能完全回答您的问题,但我和我的团队遇到了完全相同的问题,我们就是这样做的。
【讨论】:
我不更改存储库逻辑的唯一原因是我必须进行代码重构,这太费力了。如果我没有其他选择,我可能不得不这样做。以上是关于即使使用 @Fetch(FetchMode.JOIN),JPA + Hibernate 也会出现太多查询问题的主要内容,如果未能解决你的问题,请参考以下文章
即使在 HQL 查询中使用 Join Fetch,休眠延迟初始化异常
Core data multithreading fetch record