Почему @Fetch(FetchMode.SUBSELECT) тригерит 2 SQL вместо 1?

Есть простое энтити Restaurant, которое имеет список Vote ( Ненужные методы и филды не показаны )

public class Restaurant extends AbstractNamedEntity {
@OneToMany(mappedBy = "restaurant", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonManagedReference
@Fetch(FetchMode.SUBSELECT)
private List<Vote> votes;

} Вот другая сторона отношений

public class Vote extends AbstractBaseEntity {
@ManyToOne
@JsonBackReference
private Restaurant restaurant;

}

Когда я получаю данные при помощи метода findAll() от Spring Data JPA и конверчу их через ДТО маппер вот так

@Cacheable(value = "restaurantDTOList", key = "-1")
public List<RestaurantResponseDTO> getAll() {
    List<Restaurant> restaurantList = restaurantRepository.findAll();
    return restaurantList.stream()
            .map(RestaurantMapper::toRestaurantDto)
            .collect(Collectors.toList());
}

public static RestaurantResponseDTO toRestaurantDto(Restaurant restaurant) {

    return new RestaurantResponseDTO(restaurant.getId(), restaurant.getName(),
            restaurant.getAddress(), getRestaurantVoteCount(restaurant));
}
public static long getRestaurantVoteCount(Restaurant restaurant) {
var votes = restaurant.getVotes();
if (votes == null) return 0;
return  votes.stream().filter(vote -> vote.getVoteDate().equals(LocalDate.now())).count();

} Я получаю эти SQL

    Hibernate: 
select
    restaurant0_.id as id1_1_,
    restaurant0_.name as name2_1_,
    restaurant0_.address as address3_1_ 
from
    restaurant restaurant0_

Hibernate: 
select
    votes0_.restaurant_id as restaura3_4_1_,
    votes0_.id as id1_4_1_,
    votes0_.id as id1_4_0_,
    votes0_.restaurant_id as restaura3_4_0_,
    votes0_.user_id as user_id4_4_0_,
    votes0_.vote_date as vote_dat2_4_0_ 
from
    vote votes0_ 
where
    votes0_.restaurant_id in (
        select
            restaurant0_.id 
        from
            restaurant restaurant0_
    )

Буду крайне благодарен, если поможете разобраться, почему 2 SQL, а не одно.


Ответы (1 шт):

Автор решения: Roman Konoval

Это ожидаемое поведение.

Вот что говорит документация про FetchMode.SUBSELECT:

When accessing a non-initialized collection, this fetch mode will trigger loading all elements of all collections of the same role for all owners associated with the persistence context using a single secondary select.

Вольный перевод:

При доступе к неинициализированной коллекции этот режим инициирует загрузку всех элементов этой ассоциации для всех сущностей в контексте используя один дополнительный запрос.

Этот режим есть смысл использовать, когда у вас основной запрос возвращает несколько сущностей (и они большие, т.е. содержат много данных), а ассоциированных с ними сущностей много. В этом случае при FetchMode.SELECT сделает N+1 запрос, чего хочется избежать. Использование FetchMode.JOIN приведет к тому, что колонки из основной сущности будут дублироваться в результате. Это дублирование привет к тому, что будет передано много данных (т.е. для каждой ассоциированной сущности данные в родителе будут повторятся, и напоминаю, мы говорим о ситуации, когда родитель - большой), которые можно не передавать, если не делать join, а доставать ассоциированные сущности отдельным запросом.

Вот именно для этой ситуации и годится FetchMode.SUBSELECT.

→ Ссылка