OneToMany(fetch=FetchType.EAGER) 是否执行 N+1 个查询

Akr*_*OUN 4 database spring hibernate jpa

我正在使用 spring boot、spring data 和 Hibernate 开发一个应用程序。我有一个实体“Client”,它与另一个实体“Reservation”具有“onetomany”关系,如下所示:

@Entity
public class Client implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany( mappedBy = "client", fetch=FetchType.EAGER)
    @Fetch(value = FetchMode.JOIN)
    private Set<Reservation> reservations = new HashSet<>();
}
Run Code Online (Sandbox Code Playgroud)

我已经实现了 JpaRepository 接口,所以我可以操作客户端数据:

public interface ClientRepository extends JpaRepository<Client, Long> {}
Run Code Online (Sandbox Code Playgroud)

在服务类中,我实现了一种方法来查找数据库中的所有客户端:

@Service
public class ClientService {

@Autowired
private ClientRepository clientRepository;

@Transactional(readOnly = true)
public List<Client> findAll() {

    return clientRepository.findAll();
}}
Run Code Online (Sandbox Code Playgroud)

findAll() 的执行运行良好并返回所有客户端及其保留。但是,在我的 sql 日志中,我已经执行了 N+1 个查询(N 是客户端的计数),尽管我已经fetch=FetchType.EAGER在我的客户端实体中设置了这些查询。我认为 hibernate 将创建一个单一的查询,在其中加入所有必要的数据以检索客户及其预订。

因此,我被迫通过查询明确加入客户和预订:

    @Query("select client from Client client left join fetch client.reservations")
public List<Client> findAllEager();
Run Code Online (Sandbox Code Playgroud)

我还发现了另一种替代方法,它只允许执行两个查询来检索所有客户及其预订:

    @OneToMany(mappedBy = "client", fetch = FetchType.EAGER)
@Fetch(value = FetchMode.SUBSELECT)
Run Code Online (Sandbox Code Playgroud)

我注意到从这个讨论中JPA 热切取不加入,它似乎@OneToMany(fetch=FetchType.EAGER) @Fetch(value = FetchMode.JOIN)只会执行一个查询来检索结果,这不是我的情况。

是否OneToMany(fetch=FetchType.EAGER)执行 N+1 个查询来获取数据?

Ken*_*han 7

@OneToMany(fetch=FetchType.EAGER)仅定义何时获取相关实体,不定义如何获取实体。(即它们是否由单个连接的 SQL 或多个分离的 SQL 获取),因此它可以执行 N+1 个查询。

@Fetch(value = FetchMode.JOIN)定义如何获取相关实体 但是,它仅在使用 ID 直接获取实体时才有效(即通过entityManager.find(Client.class, 100)但不会影响 JPQL 查询或 Criteria API。(如下面的Hibernate 用户指南所述):

我们不使用 JPQL 查询来获取多个 Department 实体的原因是因为 FetchMode.JOIN 策略将被查询获取指令覆盖。

要使用 JPQL 查询获取多个关系,必须改用 JOIN FETCH 指令。

因此, FetchMode.JOIN 在实体通过其标识符或自然 ID 直接获取时很有用。

Spring 数据内部使用 Criteria API 进行findAll()查询,因此@Fetch(value = FetchMode.JOIN)不会产生任何影响。您必须显式使用JOIN FETCH查询来避免 N+1 查询。