Проблема N+1 после миграции с Spring Boot 2.7.2 на 3.2.2. Используется @BatchSize, но размер только в условиях уменьшается.

Вопрос или проблема

@Transactional(readOnly = true)
public void test() {
    Slice<Team> teamSlice = teamRepository.findAllLimit10();
    List<Team> teamList = teamSlice.getContent();
    List<Boolean> booleanList = teamList.stream()
                                        .map(team -> {
                                            team.getUserList().isEmpty(); // точка проблемы
                                        })
                                        .collect(Collectors.toList());

}
@Builder.Default
@Where(clause = "enabled = true")
@BatchSize(size = 600)
@OneToMany(mappedBy = "team", cascade = CascadeType.PERSIST)
private List<User> userList = new ArrayList<>();
Hibernate:
select
    u1_0.user_id,
    u1_0.team_id
from
    user u1_0
where
    u1_0.team_id in(?,?,?,?,?,?,?,?,?,?)
  and (
    u1_0.enabled = 1
    )
Hibernate:
select
    u1_0.user_id,
    u1_0.team_id
from
    user u1_0
where
    u1_0.team_id in(?,?,?,?,?,?,?,?,?)
  and (
    u1_0.enabled = 1
    )
Hibernate:
select
    u1_0.user_id,
    u1_0.team_id
from
    user u1_0
where
    u1_0.team_id in(?,?,?,?,?,?,?,?)
  and (
    u1_0.enabled = 1
    )
Hibernate:
select
    u1_0.user_id,
    u1_0.team_id
from
    user u1_0
where
    u1_0.team_id in(?,?,?,?,?,?,?)
  and (
    u1_0.enabled = 1
    )
Hibernate:
select
    u1_0.user_id,
    u1_0.team_id
from
    user u1_0
where
    u1_0.team_id in(?,?,?,?,?,?)
  and (
    u1_0.enabled = 1
    )
Hibernate:
select
    u1_0.user_id,
    u1_0.team_id
from
    user u1_0
where
    u1_0.team_id in(?,?,?,?,?)
  and (
    u1_0.enabled = 1
    )
Hibernate:
select
    u1_0.userId,
    u1_0.team_id
from
    user u1_0
where
    u1_0.team_id in(?,?,?,?)
  and (
    u1_0.enabled = 1
    )
Hibernate:
select
    u1_0.user_id,
    u1_0.team_id
from
    user u1_0
where
    u1_0.team_id in(?,?,?)
  and (
    u1_0.enabled = 1
    )
Hibernate:
select
    u1_0.user_id,
    u1_0.team_id
from
    user u1_0
where
    u1_0.team_id in(?,?)
  and (
    u1_0.enabled = 1
    )
Hibernate:
select
    u1_0.user_id,
    u1_0.team_id
from
    user u1_0
where
    u1_0.team_id in(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
  and (
    u1_0.enabled = 1
    ) 

После миграции на spring boot возникла проблема.

spring boot 2.7.2 0> 3.2.2 и использование hibernate-core 6.1.4 final.

Количество условий только уменьшается…

@Fetch(FetchMode.SUBSELECT) <- Это эффективно. Это не фундаментальное решение

fetch join <- org.hibernate.orm.query : HHH90003004: firstResult/maxResults указаны с выборкой коллекции; применение в памяти

Ответ или решение

Проблема, с которой вы столкнулись после миграции с Spring Boot 2.7.2 на 3.2.2 и обновления Hibernate до 6.1.4, касается ситуации, когда вы наблюдаете поведение N+1 при использовании аннотации @BatchSize. Это может происходить по нескольким причинам, особенно учитывая изменения в Hibernate в более поздних версиях.

Причины проблемы

  1. Изменения в Hibernate: В Hibernate 6 произошли изменения в способе обработки @BatchSize, которые могли повлиять на количество запросов, генерируемых при загрузке ассоциированных коллекций. Заметьте, что @BatchSize эффективен только при условии, что запросы формируются корректно.

  2. Размер выборки: Как вы заметили, количество запросов уменьшается, но это не всегда соответствует ожидаемому результату. Если коллекции (в вашем случае userList) загружаются в пакетах, но количество запросов остается высоким, это может указывать на неправильное использование методов или конфигурации запроса в репозитории.

  3. Изменения в конфигурации: Возможно, изменения конфигурации или аннотаций между версиями играют роль в этом поведении. Убедитесь, что все аннотации и конфигурации Spring Data JPA корректно настроены.

Решение

Чтобы устранить проблему с избыточным количеством запросов N+1, вы можете рассмотреть следующее:

  1. Использование FetchType.LAZY:
    Убедитесь, что ваша аннотация @OneToMany использует ленивую загрузку:

    @OneToMany(mappedBy = "team", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
    private List<User> userList = new ArrayList<>();
  2. Оптимизация выборки:
    Если вы хотите оптимизировать запросы, можно применять FetchMode.SUBSELECT на уровне связи, что поможет уменьшить количество отдельных запросов:

    @OneToMany(mappedBy = "team", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
    @BatchSize(size = 600)
    @Fetch(FetchMode.SUBSELECT)
    private List<User> userList = new ArrayList<>();
  3. Использование JOIN FETCH:
    Чтобы предотвратить N+1, вы можете использовать JOIN FETCH в запросах:

    @Query("SELECT t FROM Team t JOIN FETCH t.userList WHERE ...")
    List<Team> findAllWithUsers();
  4. Использование проекта DataLoader:
    Если использование JOIN FETCH предполагает слишком узкий границы выборок, рассмотрите использование дополнительных инструментов, таких как DataLoader, которые могут помочь избавиться от избыточных выборок.

  5. Логирование SQL:
    Включите логирование SQL запросов, чтобы понять, как формируются и выполняются запросы. Это может помочь выявить узкие места в оптимизации.

  6. Кэширование:
    Рассмотрите возможность использования кэширования, если ваши данные не изменяются очень часто. Это может помочь сократить количество запросов к базе данных.

Заключение

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

Оцените материал
Добавить комментарий

Капча загружается...