Робота з базами даних — одна з найважливіших складових практично будь-якого backend-застосунку. Spring пропонує кілька рівнів абстракції для доступу до даних: низькорівневий Spring JDBC, потужний ORM-фреймворк Hibernate та зручну надбудову Spring Data JPA. Розглянемо, чим вони відрізняються, коли варто обирати кожен з них.
1. Spring JDBC
Spring JDBC — це тонка обгортка над стандартним java.sql, яка усуває бойлерплейт-код: ручне відкриття/закриття з’єднань, обробку виключень, керування транзакціями.
Основний інструмент — клас JdbcTemplate:
@Repository
public class UserJdbcRepository {
private final JdbcTemplate jdbcTemplate;
public UserJdbcRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public User findById(Long id) {
String sql = "SELECT * FROM users WHERE id = ?";
return jdbcTemplate.queryForObject(sql,
(rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name")), id);
}
}JavaПереваги:
- Повний контроль над SQL-запитами, максимальна прозорість виконуваного коду.
- Мінімальні накладні витрати — немає прошарку ORM, тож продуктивність зазвичай найвища.
- Простота діагностики: видно точно, який SQL виконується.
Недоліки:
- Багато рутинного коду для мапінгу результатів у об’єкти (
RowMapper). - Немає автоматичного відстеження змін об’єктів, кешування, каскадних операцій.
- Складніше підтримувати при зростанні кількості таблиць і зв’язків.
Spring JDBC добре підходить для простих проєктів, звітних систем зі складними аналітичними запитами або коли потрібна максимальна продуктивність і контроль над SQL.
2. Hibernate
Hibernate — це повноцінна ORM (Object-Relational Mapping) реалізація специфікації JPA. Вона дозволяє працювати з базою даних через об’єкти Java, а не через SQL-рядки.
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Session session = sessionFactory.openSession();
User user = session.get(User.class, 1L);JavaПереваги:
- Автоматичний мапінг об’єктів на таблиці через анотації (
@Entity,@Column,@OneToManyтощо). - Механізм Session з відстеженням змін (dirty checking) — не потрібно вручну писати
UPDATE. - Кешування першого та другого рівня, lazy/eager завантаження зв’язків.
- HQL (Hibernate Query Language) — об’єктно-орієнтована мова запитів.
Недоліки:
- Крута крива навчання:
Session,EntityManager, стани сутностей (transient, persistent, detached). - Можливі проблеми продуктивності через неявні N+1 запити, якщо не налаштувати fetch-стратегії.
- Згенерований SQL іноді складно оптимізувати без глибокого розуміння внутрішньої роботи Hibernate.
3. Spring Data JPA
Spring Data JPA — це надбудова над JPA (і, як правило, над Hibernate як провайдером), яка ще більше скорочує кількість коду. Замість реалізації репозиторіїв вручну достатньо оголосити інтерфейс.
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByName(String name);
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);
}JavaSpring сам генерує реалізацію на основі назви методу (query methods) або анотації @Query.
Переваги:
- Мінімум коду: CRUD-операції доступні “з коробки” (
save,findById,deleteтощо). - Автоматична генерація запитів за іменем методу.
- Підтримка пагінації (
Pageable), сортування, специфікацій (Specification) для динамічних запитів. - Легка інтеграція з транзакційним менеджментом Spring (
@Transactional).
Недоліки:
- Ще один рівень абстракції над Hibernate — менше контролю за фінальним SQL.
- Для складних кастомних запитів іноді доводиться повертатися до
@Queryабо навіть “сирого” JDBC. - Магія іменування методів може ускладнити читання коду новачками.
Порівняльна таблиця
| Критерій | Spring JDBC | Hibernate | Spring Data JPA |
|---|---|---|---|
| Рівень абстракції | Низький | Високий | Найвищий |
| Обсяг коду | Багато | Середньо | Мінімум |
| Контроль над SQL | Повний | Обмежений | Обмежений |
| Швидкість розробки | Низька | Середня | Висока |
| Продуктивність (типово) | Найвища | Залежить від налаштувань | Залежить від налаштувань |
| Крива навчання | Легка | Складна | Середня |
Що обрати саме для себе?
- Spring JDBC — коли потрібна максимальна продуктивність, складні аналітичні SQL-запити або мінімальна залежність від фреймворків.
- Hibernate — якщо потрібен повний контроль над ORM-логікою, кешуванням і складними зв’язками між сутностями, а команда готова інвестувати час у вивчення нюансів.
- Spring Data JPA — оптимальний вибір для більшості типових CRUD-застосунків: швидка розробка, мінімум бойлерплейту, добра інтеграція з екосистемою Spring.
На практиці в реальних проєктах ці підходи часто комбінують: основну бізнес-логіку реалізують через Spring Data JPA, а для складних звітів чи оптимізованих вибірок використовують Spring JDBC або native SQL-запити через @Query(nativeQuery = true).
Усі три підходи в реальних застосунках працюють через connection pool (найчастіше HikariCP — дефолтний у Spring Boot). Різниця в тому, як вони цим пулом користуються:
- JDBC: з’єднання беруться і повертаються в пул явно на кожен виклик
JdbcTemplate-методу. Життєвий цикл короткий і передбачуваний. - Hibernate/JPA: з’єднання прив’язується до
Session/EntityManagerна час транзакції (Connection release mode:AFTER_TRANSACTIONза замовчуванням). Якщо транзакція “довга” (наприклад, включає виклик зовнішнього API), з’єднання може простоювати заблокованим у пулі — це типова причина виснаження пулу (pool exhausted) у production.
# Приклад важливих налаштувань HikariCP
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.connection-timeout=3000
spring.datasource.hikari.leak-detection-threshold=5000PHPN+1 Problem та стратегії fetch
Одна з найвідоміших “пасток” ORM-підходів:
@Entity
public class Order {
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<OrderItem> items;
}JavaЯкщо виконати findAll() для 100 замовлень, а потім у циклі звернутися до order.getItems(), Hibernate виконає 1 запит на замовлення + 100 окремих запитів на позиції — це і є N+1.
Рішення:
JOIN FETCHу JPQL:SELECT o FROM Order o JOIN FETCH o.items.@EntityGraphу Spring Data JPA — декларативне визначення графа завантаження без зміни запиту.@BatchSize(size = 20)(Hibernate-специфічна анотація) — групування підзапитів пачками замість одного запиту на кожен зв’язок.- Проєкції DTO (
interface-based projectionsу Spring Data JPA) — вибірка тільки потрібних полів без завантаження повного графа сутностей.
public interface OrderSummary {
Long getId();
BigDecimal getTotal();
}
List<OrderSummary> findByStatus(String status);JavaДля чистого JDBC проблема N+1 фактично відсутня, оскільки розробник сам пише SQL і контролює кількість запитів (JOIN пишеться явно).





