JAVA_FILLTHEGAPS Telegram 597
Как найти и починить in-memory пагинацию в Spring Data JPA

N+1 - самая известная проблема в Spring Data JPA/Hibernate, но не единственная. В этом посте расскажу об in-memory пагинации.

В чем суть.

Пагинацию логично делать на уровне БД с помощью limit и offset. Но иногда Hibernate делает пагинацию на стороне клиента. Выгружает все записи из память, выбирает из них заданное количество и возвращает.

Чем плохо:
Запрос выполняется долго, тк по сети передается куча данных
Забивается оперативная память. В худшем случае получаем OutOfMemoryError

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

Простой пример. У постов есть комментарии со связью OneToMany:
@Entity
public class Post {
   @OneToMany
   private List<Comment> comments;
}

Мы хотим получить 5 постов с комментариями. Чтобы избежать N+1, загружаем комментарии через EntityGraph:
@EntityGraph
List<Post> findAllPosts(PageRequest.of(0, 5));

Метод findAllPosts вернёт 5 постов, как мы и просили. Но в логах увидим SQL запрос без лимитов, в память загрузятся все посты.

Как найти места со скрытой пагинацией?

Hibernate пишет в консоль ворнинг, который легко пропустить. Лучше сделать так:
🌷 Ставим свойство
spring.jpa.properties.hibernate.query.fail_on_pagination_over_collection_fetch=true

🌷 Запускаем интеграционные тесты
🌷 Смотрим, где падают исключения

Как починить скрытую пагинацию?

Способов много, самое простое - добавить над списком @BatchSize. Либо поставить свойство
spring.jpa.properties.hibernate.default_batch_fetch_size=50

Тогда BatchSize будет по умолчанию применяться для всех списков.
Про другие решения и перфоманс читайте в этой статье на Хабре

Зачем Hibernate использует in-memory пагинацию?

При запросе с джойном из нескольких таблиц получается декартово произведение, для которого некорректно применить limit. Но контракт соблюдать надо хоть как-то, поэтому была выбрана пагинация на стороне клиента.

Сразу возникает вопрос. Почему в таких случаях не сделать 2 запроса?
▫️ select * from posts limit 5;
▫️ Извлечь айдишники постов
▫️ select * from comments where post_id in (?,?,?,?,?);
▫️ Связать сущности

Решение выглядит просто, и вариант с BatchSize примерно так и работает. Почему оно не используется по умолчанию - непонятно.

Hibernate - яркий пример “протекающих абстракций”. Куча нюансов и проблем, о которых нужно знать. В документации по поводу in-memory панинации есть такая фраза:

Possibility of terrible performance is left as a problem for the client to avoid.
Проблема ужасного перформанса - это проблема клиента.

Вся суть хибернейта🤌

По возможности не тащите его в проект. Есть более удобные и прозрачные альтернативы. Например, Spring Data JDBC💚



tgoop.com/java_fillthegaps/597
Create:
Last Update:

Как найти и починить in-memory пагинацию в Spring Data JPA

N+1 - самая известная проблема в Spring Data JPA/Hibernate, но не единственная. В этом посте расскажу об in-memory пагинации.

В чем суть.

Пагинацию логично делать на уровне БД с помощью limit и offset. Но иногда Hibernate делает пагинацию на стороне клиента. Выгружает все записи из память, выбирает из них заданное количество и возвращает.

Чем плохо:
Запрос выполняется долго, тк по сети передается куча данных
Забивается оперативная память. В худшем случае получаем OutOfMemoryError

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

Простой пример. У постов есть комментарии со связью OneToMany:

@Entity
public class Post {
   @OneToMany
   private List<Comment> comments;
}

Мы хотим получить 5 постов с комментариями. Чтобы избежать N+1, загружаем комментарии через EntityGraph:
@EntityGraph
List<Post> findAllPosts(PageRequest.of(0, 5));

Метод findAllPosts вернёт 5 постов, как мы и просили. Но в логах увидим SQL запрос без лимитов, в память загрузятся все посты.

Как найти места со скрытой пагинацией?

Hibernate пишет в консоль ворнинг, который легко пропустить. Лучше сделать так:
🌷 Ставим свойство
spring.jpa.properties.hibernate.query.fail_on_pagination_over_collection_fetch=true

🌷 Запускаем интеграционные тесты
🌷 Смотрим, где падают исключения

Как починить скрытую пагинацию?

Способов много, самое простое - добавить над списком @BatchSize. Либо поставить свойство
spring.jpa.properties.hibernate.default_batch_fetch_size=50

Тогда BatchSize будет по умолчанию применяться для всех списков.
Про другие решения и перфоманс читайте в этой статье на Хабре

Зачем Hibernate использует in-memory пагинацию?

При запросе с джойном из нескольких таблиц получается декартово произведение, для которого некорректно применить limit. Но контракт соблюдать надо хоть как-то, поэтому была выбрана пагинация на стороне клиента.

Сразу возникает вопрос. Почему в таких случаях не сделать 2 запроса?
▫️ select * from posts limit 5;
▫️ Извлечь айдишники постов
▫️ select * from comments where post_id in (?,?,?,?,?);
▫️ Связать сущности

Решение выглядит просто, и вариант с BatchSize примерно так и работает. Почему оно не используется по умолчанию - непонятно.

Hibernate - яркий пример “протекающих абстракций”. Куча нюансов и проблем, о которых нужно знать. В документации по поводу in-memory панинации есть такая фраза:

Possibility of terrible performance is left as a problem for the client to avoid.
Проблема ужасного перформанса - это проблема клиента.

Вся суть хибернейта🤌

По возможности не тащите его в проект. Есть более удобные и прозрачные альтернативы. Например, Spring Data JDBC💚

BY Java: fill the gaps


Share with your friend now:
tgoop.com/java_fillthegaps/597

View MORE
Open in Telegram


Telegram News

Date: |

It’s yet another bloodbath on Satoshi Street. As of press time, Bitcoin (BTC) and the broader cryptocurrency market have corrected another 10 percent amid a massive sell-off. Ethereum (EHT) is down a staggering 15 percent moving close to $1,000, down more than 42 percent on the weekly chart. Image: Telegram. Judge Hui described Ng as inciting others to “commit a massacre” with three posts teaching people to make “toxic chlorine gas bombs,” target police stations, police quarters and the city’s metro stations. This offence was “rather serious,” the court said. Public channels are public to the internet, regardless of whether or not they are subscribed. A public channel is displayed in search results and has a short address (link). Commenting about the court's concerns about the spread of false information related to the elections, Minister Fachin noted Brazil is "facing circumstances that could put Brazil's democracy at risk." During the meeting, the information technology secretary at the TSE, Julio Valente, put forward a list of requests the court believes will disinformation.
from us


Telegram Java: fill the gaps
FROM American