Spring JDBC проти Spring Data JPA

Привіт усім! Мене звати Максим Дудка, я Java Engineer. Сьогодні я хочу відслонити завісу над двома ключовими технологіями, які я використовую для доступу до даних — Spring JDBC та Spring Data JPA. Обидві технології мають свої переваги та недоліки, і вибір між ними може суттєво вплинути на архітектуру вашого сервісу, швидкість розробки та легкість підтримки коду.

Статтю також можна прочитати англійською.

Spring Framework став синонімом надійної розробки Java-застосунків, пропонуючи широкий спектр інструментів та бібліотек для задоволення потреб підприємств. В основі багатьох застосунків лежить потреба в надійних механізмах постійного зберігання даних. Spring пропонує два видатні підходи: Spring JDBC та Spring Data JPA. Хоча обидва підходи служать одній меті — доступу до даних — вони роблять це, виходячи з різних філософій і можливостей.

Spring JDBC

Spring JDBC — це прямий підхід, який надається Spring Framework для виконання операцій з базами даних. Він використовує JDBC API, але усуває типовий шаблонний код, пов’язаний з ним.

Основні характеристики:

  • JdbcTemplate для спрощеної взаємодії з базою даних;
  • Перетворення exceptions у DataAccessException.
  • Узгоджений API для роботи з різними базами даних.

Плюси:

  • Прямий контроль над SQL-запитами: Spring JDBC дозволяє вам явно писати ваші SQL-запити, надаючи повний контроль над взаємодією з базою даних.

    String sql = "SELECT * FROM users WHERE email = ?";
    User user = jdbcTemplate.queryForObject(sql, new Object[]{email}, new BeanPropertyRowMapper<>(User.class));
    
  • Висока продуктивність: з Spring JDBC мінімальна абстракція між вашим застосунком та базою даних, що може призвести до потенційно кращої продуктивності.
  • Простота для малих проєктів: для простих застосунків Spring JDBC може бути легшим у використанні без складності ORM.

Мінуси:

  • Більше шаблонного коду: навіть зі спрощеннями JdbcTemplate вам усе ще потрібно писати та керувати SQL-запитами та вручну відображати результати.

    jdbcTemplate.query(
        "SELECT id, name, email FROM users",
        (rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name"), rs.getString("email"))
    ).forEach(user -> System.out.println(user.getName()));
    
  • Ризик SQL-ін’єкції: якщо не бути уважним з обробкою вводу, можна легко її пропустити.

    String unsafeSql = "SELECT * FROM users WHERE email = '" + userEmail + "'"; // Це небезпечно, якщо userEmail не перевірено належним способом
    
  • Менше абстракції: обробка відносин та складних запитів може стати обтяжливою, оскільки складність доменної моделі зростає.

Spring Data JPA

Spring Data JPA — це шар абстракції, який розташований над Java Persistence API. Він спрощує доступ до даних в екосистемі застосунків Spring.

Основні характеристики:

  • Прості операції CRUD з CrudRepository.
  • Автоматичне створення запитів на основі назв методів.
  • Розширена пагінація та динамічне виконання запитів.

Плюси:

  • Зменшення шаблонного коду: репозиторії в Spring Data JPA можуть значно зменшити кількість необхідного шаблонного коду.

    public interface UserRepository extends JpaRepository<User, Long> {
        User findByEmail(String email);
    }
    
  • Вищий рівень абстракції: Spring Data JPA впорюється зі складністю відображення об’єктів на таблиці бази даних, що полегшує роботу зі складними даними моделями.
  • Потенційно легше підтримувати: абстракція, надана Spring Data JPA, полегшує підтримку та оновлення шару доступу до даних вашого застосунку.

Мінуси:

  • Менше контролю над SQL-запитами: іноді абстракція може бути надто великою, і так приховувати необхідні деталі для оптимізації.
  • Потенційно знижує продуктивність: шар абстракції може знизити продуктивність, особливо якщо використовувати його необережно.

    // Це може згенерувати складний запит з кількома join
    List<User> users = userRepository.findAllWithDetails();
    
  • Більше знань про те, як воно працює під капотом: розуміння тонкощів JPA та Hibernate може бути складним для новачків.
Основні відмінності. Виклик stored procedure (далі — sp), яка приймає таблицю як вхідний параметр, є більш складним випадком використання та зазвичай специфічний для бази даних. Розглянемо приклад, де у нас є sp , яка приймає параметр типу таблиці в SQL Server. Ми порівняємо, як викликати цю збережену процедуру, використовуючи Spring JDBC та Spring Data JPA.

Виклик sp Spring JDBC з параметром типу таблиці:

У Spring JDBC потрібно буде розширити клас StoredProcedure та відповідно налаштувати параметри. Однак оскільки JDBC не підтримує нативно типи таблиць як параметри, зазвичай потрібно використовувати масив або структурований об’єкт, який підтримує драйвер конкретної бази даних.

Ось приклад використання SQL Server з типом SqlServerDataTable з драйвера Microsoft JDBC:


public class UserStoredProcedure extends StoredProcedure {
    
    public UserStoredProcedure(DataSource dataSource) {
        super(dataSource, "dbo.updateUserEmails");
        declareParameter(new SqlParameter("userEmails", Types.STRUCT));
        setFunction(false);
        compile();
    }

    public Map<String, Object> execute(SqlServerDataTable userEmailsTable) {
        Map<String, Object> inParams = new HashMap<>();
        inParams.put("userEmails", userEmailsTable);
        return super.execute(inParams);
    }
}

У цьому прикладі SqlServerDataTable використовується для передачі таблиці електронних адрес користувачів до sp. Потім метод execute викликається із цією таблицею як параметром.

Плюси:

  • Прямий контроль над виконанням sp.
  • Можливість використання специфічних для бази даних функцій і типів.

Мінуси:

  • Складність у налаштуванні та використанні специфічних для бази даних типів.
  • Більш об’ємний та менш портативний код через специфічні для бази даних розширення.

Виклик sp Spring Data JPA з параметром типу таблиці:

Spring Data JPA не підтримує безпосередньо параметри типу таблиці для sp. Це тому, що JPA розроблено так, щоб не мати залежності від конкретної бази даних, а типи таблиць є функцією, специфічною для певних баз даних, як-от SQL Server.

Однак ви все ще можете викликати sp, використовуючи нативні запити, або за допомогою EntityManager для створення нативного SQL-запиту. Ось приклад використання EntityManager:

@Repository
public class UserRepository {
    @PersistenceContext
    private EntityManager entityManager;
    
    public void updateUserEmails(SqlServerDataTable userEmailsTable) {
        // Конвертування SqlServerDataTable у формат, який можна передати до збереженої процедури
        // Цей крок сильно залежить від бази даних та можливостей постачальника JPA
        Object convertedTable = convertSqlServerDataTable(userEmailsTable);
        StoredProcedureQuery query = entityManager.createStoredProcedureQuery("dbo.updateUserEmails");
        query.registerStoredProcedureParameter(1, Object.class, ParameterMode.IN);
        query.setParameter(1, convertedTable);
        query.execute();
    }
    
    private Object convertSqlServerDataTable(SqlServerDataTable table) {
        // Логіка конвертації тут
        return ...;
    }
}

У цьому прикладі потрібно буде конвертувати SqlServerDataTable у формат, який JPA-реалізація може зрозуміти. Це може відбутися не прямолінійно та досить складно.

Плюси:

  • Узгодженість з підходом репозиторію Spring Data JPA.

Мінуси:

  • Відсутність прямої підтримки параметрів типу таблиці в JPA.
  • Потенційно складна логіка конвертації для обходу обмежень JPA.

Інші ключові відмінності

Підхід та філософія

Spring JDBC — це підхід нижчого рівня, який надає тонкий шар над стандартним JDBC, що означає тісну роботу з SQL та ручне відображення об’єктів. Натомість Spring Data JPA — це абстракція вищого рівня, яка працює з об’єктами та їхніми метаданими для автоматичного створення SQL та відображення результатів.

Складність коду та швидкість розробки

З Spring JDBC розробники повинні писати більше коду для виконання запитів та відображення результатів у об’єкти. Це може сповільнити розробку, але надає більше контролю. Spring Data JPA значно зменшує кількість шаблонного коду, що може прискорити розробку, особливо в складних застосунках з багатьма доменними об’єктами.

Конфігурація та шаблонний код

Spring JDBC вимагає явної конфігурації для кожного запиту та ручного відображення результатів у доменні об’єкти. Spring Data JPA з його шаблоном репозиторію зменшує цю потребу, надаючи стандартні методи та створення SQL-запитів через назви методів.

Продуктивність

Spring JDBC може бути більш продуктивним завдяки прямому використанню SQL. Spring Data JPA може додати деякий оверхед через свою абстракцію, але це зменшується з правильним використанням можливостей JPA, наприклад lazy-loading.

Транзакції

Як Spring JDBC, так і Spring Data JPA інтегровані з управлінням транзакцій Spring. Однак Spring JDBC може вимагати більше ручного втручання для правильного управління транзакціями, тоді як Spring Data JPA отримує вигоду від декларативного управління транзакціями, яке надається шаром репозиторію JPA.

Інтеграція з іншими проєктами Spring

Spring Data JPA є частиною більшої сім’ї Spring Data, яка забезпечує доступ до даних через широкий спектр баз даних та сховищ даних. Spring JDBC можна використовувати з іншими проектами Spring, але він не має такого ж рівня інтеграції, як Spring Data JPA.

Сценарії використання

Сценарії використання Spring JDBC:

  • Застосунки, яким потрібні добре налаштовані SQL-запити для оптимізації продуктивності.
  • Старі системи, де міграція на ORM не є можливою.
  • Проєкти, де розробники мають сильні навички SQL і віддають перевагу тісній взаємодії з базою даних.

Сценарії використання Spring Data JPA:

  • Застосунки зі складною доменною моделлю, де автоматичні CRUD-операції можуть заощадити час.
  • Системи, яким потрібна легка інтеграція з іншими сховищами даних та механізмами кешування.

Висновок

Вибір між Spring JDBC та Spring Data JPA часто зводиться до конкретних потреб проєкту та вимог розробницької команди. Для тих, хто віддає перевагу повному контролю над SQL та має експертизу для його керування, Spring JDBC є відмінним вибором. З іншого боку, Spring Data JPA пропонує вищий рівень абстракції, який може значно прискорити час розробки для складних застосунків, хоча потенційно за рахунок продуктивності.

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

👍ПодобаєтьсяСподобалось9
До обраногоВ обраному6
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Коротко — jdbc template ви пишите sql, spring data — sql генерується автоматично

Дякую за пост, але дуже багато недоліків та неточностей

Spring пропонує два видатні підходи: Spring JDBC та Spring Data JPA.

А чому Spring JDBC видатний підхід, а Spring Data JDBC чи Spring Data R2DBC не видатні?

Прямий контроль над SQL-запитами: Spring JDBC дозволяє вам явно писати ваші SQL-запити, надаючи повний контроль над взаємодією з базою даних

Ну так і Spring Data JPA дозволяє використовувати native (SQL) запити

Мінуси:

Менше контролю над SQL-запитами: іноді абстракція може бути надто великою, і так приховувати необхідні деталі для оптимізації.

Це не так. У Spring Data JPA можна також писати SQL і JPQL запити.

Потенційно знижує продуктивність: шар абстракції може знизити продуктивність, особливо якщо використовувати його необережно.

Spring Data JPA не може знизити продуктивність сам по собі, він просто транслює код у запити JPQL, а що там з ними робитиме JPA провайдер (Hibernate) ніхто не знає.

Spring JDBC вимагає явної конфігурації для кожного запиту та ручного відображення результатів у доменні об’єкти. Spring Data JPA з його шаблоном репозиторію зменшує цю потребу, надаючи стандартні методи та створення SQL-запитів через назви методів.

Черговий набір слів. Плюс автор не сказав найголовніше — що Spring Data JPA вимагає mapping для моделі даних і без неї працювати не може.

Які ще sql інʼєкції? все ж, навіть хитро зібрані квері на `?` та `:param`, ні?

А як що до Spring Data JDBC? Мінімум шаблонного коду.

Мабудь можна зміксувати та використати десь у проекті jdbc для специфічних потреб роботи з бд, навіть, якщо здебільшого досить jpa? Здається, було б добре розглянути цей аспект також, а не виключно «ложка чи виделка»?

так, я неодноразово поєднував обидва підходи (наприклад для круд оперцій data jpa та для виклику stored procedure jdbc). але ціль статті була в порівнянні цих підходів, що б коли була задача написати сервіс що якось взаємодіє з бд, то можна було обрати більш підходящий інструмент.

є @NativeQuery, все зміксовано за нас )

Ну те саме що JDBC vs JPA

Загалом ця «незалежність» від конкретної БД ніколи всерйоз не оплачує себе, крім найпростіших легеньких CRUD, де різниця між БД не грає особливо ролі для додатка.

Серйозний додаток завжди залежний від нюансів, переваг та недоліків конкретної БД так чи інак

Справжня перевага JPA мені скоріше бачиться в швидкості кліпання. Сучасні ORM генерують цілком good enough запити навіть у досить складних ситуаціях, де й самому складно нормальний запит зробити

Нема ніякої незалежності від БД, то міф. Вжик! і міняєм mysql на pg буває тільки в хелоу ворлд левел, в реальності такого ніхто ніколи не робить.
і так, коли в нас через поле зʼявляється columnDefinition в @Column, то ніяка міграція на іншу базу вже не спрацює ))

Скажіть, будь ласка, а для чого у вас

зʼявляється columnDefinition в @Column

?

Підписатись на коментарі