Java 即使在使用@Fetch(FetchMode.JOIN)时,JPA+Hibernate也会出现太多查询问题
我正在使用SpringBoot开发REST应用程序,并试图优化查询的性能。我目前正在使用存储库中的findAll,这会导致性能问题。代码如下: 个人实体Java 即使在使用@Fetch(FetchMode.JOIN)时,JPA+Hibernate也会出现太多查询问题,java,hibernate,spring-boot,spring-data-jpa,Java,Hibernate,Spring Boot,Spring Data Jpa,我正在使用SpringBoot开发REST应用程序,并试图优化查询的性能。我目前正在使用存储库中的findAll,这会导致性能问题。代码如下: 个人实体 @Entity @Table(name = "cd_person") @Data @NoArgsConstructor public class Person { .... @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn(
@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_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时,会为person表中的每一行触发select查询,以便在访问person时获取密码和角色。我知道我可以在存储库中使用@Query annotation和JOIN FETCH来强制生成单个查询,但我想知道是否还有其他方法可以这样做。我正在寻找一些我们可以在实体级别做的事情,以减少查询
使用SpringBoot2.1.5版本和相关依赖项
注:@Data和@noargsconstuctor是龙目山注释 您应该将@BatchSize放在密码类的顶部
@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 (
?, ?, ?, ?, ?
)
您不能使用lazy fetch并删除@fetch吗?在实体顶部使用@NamedQuery并在自定义服务中使用hibernate会话调用session.createNamedQuery即可
如果您可以不使用默认的personRepository.findAll而使用此自定义服务,那么您将运行一个优化的查询。我知道它并没有完全回答您的问题,但我的团队和我面临着完全相同的问题,我们就是这样做的。我会让实体保持原样,并用@Query注释覆盖存储库中的findAll方法。
通过这种方式,代码重构只需要一次存储库更改,而不是实体更改。对于您的问题,不满意的答案是:不,没有办法注释/配置实体,以便获取模式也适用于查询
只要你正确地找到了自己,你就可以。替代方法是使用或利用,但所有这些都需要在查询/会话级别进行编程干预。最简单的代码更改是使用spring数据中的ad-hoc EntityGraph功能。只需覆盖PersonRepository的findAll并使用@EntityGraph来配置该图。此图中的所有实体都将一起提取
public interface PersonRepository extends CrudRepository<Person, Long> {
@EntityGraph(attributePaths = { "password", "roles" })
public List<Person> findAll();
}
在幕后,它的工作原理类似于JOIN-FETCH。将只生成具有左联接的单个SQL。我的建议是: 尝试重构并使用延迟抓取。 我可能不太理解这一部分,但为什么您需要personRepository.findAll呢?我认为您只需要personRepository.findById之类的东西,这样就可以轻松地获取角色和其他数据。在这里,选择所有人似乎是一个巨大的负担。 您可能需要以后的扩展函数,因此现在更改它可能值得,而不是以后再工作一点。 这应该是有效的:
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();
}
谢谢你的努力,但我想进一步优化它。批量会更好,但仍然不是最佳的。如果你能提供任何关于批处理比join性能更好的参考,我会这样做并接受答案。我不改变存储库逻辑的唯一原因是因为我必须进行代码重构,这太费劲了。如果我没有任何其他选择,我可能不得不这样做。这种过度连接获取的性能优势/劣势是什么,还是仅仅是代码行数较少?如果您将遍历所有检索到的人员并访问其密码和角色,那么性能良好,因为所有这些都已通过单个SQL select获取,没有N+1问题,我问这个是为了加入FETCH。使用@Query还是定义实体图更好?它们是一样的。它生成与连接获取相同的SQL
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();
}