Вопрос или проблема
@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 в более поздних версиях.
Причины проблемы
-
Изменения в Hibernate: В Hibernate 6 произошли изменения в способе обработки
@BatchSize
, которые могли повлиять на количество запросов, генерируемых при загрузке ассоциированных коллекций. Заметьте, что@BatchSize
эффективен только при условии, что запросы формируются корректно. -
Размер выборки: Как вы заметили, количество запросов уменьшается, но это не всегда соответствует ожидаемому результату. Если коллекции (в вашем случае
userList
) загружаются в пакетах, но количество запросов остается высоким, это может указывать на неправильное использование методов или конфигурации запроса в репозитории. -
Изменения в конфигурации: Возможно, изменения конфигурации или аннотаций между версиями играют роль в этом поведении. Убедитесь, что все аннотации и конфигурации Spring Data JPA корректно настроены.
Решение
Чтобы устранить проблему с избыточным количеством запросов N+1, вы можете рассмотреть следующее:
-
Использование
FetchType.LAZY
:
Убедитесь, что ваша аннотация@OneToMany
использует ленивую загрузку:@OneToMany(mappedBy = "team", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY) private List<User> userList = new ArrayList<>();
-
Оптимизация выборки:
Если вы хотите оптимизировать запросы, можно применятьFetchMode.SUBSELECT
на уровне связи, что поможет уменьшить количество отдельных запросов:@OneToMany(mappedBy = "team", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY) @BatchSize(size = 600) @Fetch(FetchMode.SUBSELECT) private List<User> userList = new ArrayList<>();
-
Использование
JOIN FETCH
:
Чтобы предотвратить N+1, вы можете использоватьJOIN FETCH
в запросах:@Query("SELECT t FROM Team t JOIN FETCH t.userList WHERE ...") List<Team> findAllWithUsers();
-
Использование проекта DataLoader:
Если использованиеJOIN FETCH
предполагает слишком узкий границы выборок, рассмотрите использование дополнительных инструментов, таких как DataLoader, которые могут помочь избавиться от избыточных выборок. -
Логирование SQL:
Включите логирование SQL запросов, чтобы понять, как формируются и выполняются запросы. Это может помочь выявить узкие места в оптимизации. -
Кэширование:
Рассмотрите возможность использования кэширования, если ваши данные не изменяются очень часто. Это может помочь сократить количество запросов к базе данных.
Заключение
После внесения изменений обязательно протестируйте приложение и проверьте, улучшилось ли количество запросов. Если проблема все еще проявляется, возможно, потребуется глубокий анализ конфигурации и логики запроса. Обратите внимание, что не существует универсального решения, поэтому важно протестировать несколько подходов и выбрать оптимальный для вашей архитектуры и требований.