Представим что мы реализуем собственный репозиторий для какой-то сложной сущности (ну не хотим по каким-то причинам использовать JPA). Под сложной я имею ввиду сущность у которой есть связанные сущности. Пусть это будет сущность Entity
и связанные с ний NotificationRecipient
- какие-то получатели уведомлений, которых для каждой Entity
может быть несколько. Ну и наша сущность будет выглядеть примерно так:
public class Entity {
private Long id;
private String name;
private List<NotificationRecipient> recipients;
// getters and setters
}
Теперь мы хотим получить список сущностей со всеми получателями уведомлений. Для этого нам нужно выполнить несколько запросов к БД:
- получить список всех сущностей
- для каждой сущности получить список всех получателей уведомлений
Но мы хотим получить этот список одним обращением к репозиторию вызвав какой-нибудь findAllEntitiesWithRecipients()
. Не долго думаю собственно идем и реализуем его:
public final class EntityRepository {
private final JdbcClient jdbcClient;
public EntityRepository(JdbcClient jdbcClient) {
this.jdbcClient = jdbcClient;
}
public List<Entity> findAllEntitiesWithRecipients() {
return jdbcClient.query("SELECT * FROM entity", Entity.class)
.map(this::addNotificationRecipients)
.collect(Collectors.toList());
}
private Entity addNotificationRecipients(Entity entity) {
List<NotificationRecipient> recipients = jdbcClient
.query("SELECT * FROM notification_recipient WHERE entity_id = ?", entity.getId(), NotificationRecipient.class);
entity.setRecipients(recipients);
return entity;
}
}
И вот тут может вылезти неожиданный побочный эффект. Так как мы используем jdbcClient.query()
возвращающий Stream
и при обработке этого же Stream
мы делаем дополнительные запросы для получения получателей уведомлений. В случае если список наших сущностей довольно большой, в какой-то момент у нас просто закончатся доступные соединения с базой данных.
Потому что наш Stream<Entity>
не закрывается и соотвественно транзакция с БД тоже и наше соединение не возвращается в пул. При этом для каждой сущности с не пустым списком получателей уведомлений происходит тоже самое, активное соединение не возвращается в пул до тех пор пока не будет вызван терминальный оператор на основном Stream
.
Чтобы избежать этой ситуации достаточно просто добавить к нашему публичному методу аннотацию @Transactional
, так же можно пометить его как readOnly
для дополнительных оптимизаций на стороне Spring
. Это позволит нам выполнять все запросы к БД в рамках одной транзакции и одного активного соединения из пула, которое после выполнения всех операций будет возвращено в пул.