JDBC API в Java - огляд та туторіал

На цей раз я хотів би приділити увагу роботі з базами даних за допомогою JDBC API.

Суперечку під назвою Hibernate (точніше JPA) проти JDBC залишимо осторонь. Обидва підходи мають право існування. Якщо хочете почути мою особисту думку щодо цього, то загляньте в кінець статті.

А зараз давайте розглянемо такі питання:

DriverManager та JDBC Driver

У всіх прикладах підключення до бази даних в Інтернеті ви обов'язково зустрінете ці рядки:

Де driverClass - це рядок з повним ім'ям класу JDBC драйвера, наприклад, org.h2.Driver для H2 Database або com.mysql.jdbc.Driver для MySql.

Зазвичай на цьому розповідь про драйвер і DriverManager закінчується. Ну а ми копнемо трохи глибше.

Усі основні сутності в JDBC API, з якими вам належить працювати, є інтерфейсами:

  • Connection;
  • Statement;
  • Закріпленадержавність;
  • CallableStatement;
  • ResultSet;
  • Driver;
  • DatabaseMetaData.

JDBC драйвер конкретної бази даних таки надає реалізації цих інтерфейсів.

DriverManager - це синглтон, який містить інформацію про всі зареєстровані драйвери. Метод getConnection на основі параметра URL знаходить java.sql.Driver відповідної бази даних і викликає метод connect.

Так навіщо ж виклик Class.forName()?

Якщо подивитися вихідний код реалізації будь-якого драйвера він міститиме статичний блок ініціалізації такого виду:

Виклик Class.forName завантажує клас і цим гарантує виконання статичного блоку ініціалізації, а отже, і реєстрацію драйвера в DriverManager.

З'єднання до бази даних

У статті я пропоную скористатися легковажною базою даних, написаною наJava під назвою H2 Database. В даний момент для нас перевага її використання полягає в тому, що скачавши джарку тут ви отримуєте відразу і саму базу даних, і драйвер для підключення до неї, що для навчання дуже зручно.

Таким чином ми отримали реалізацію інтерфейсу java.sql.Connection для нашої бази даних.

Повний код усіх прикладів можна знайти наприкінці статті.

Використовуємо Statement та ResultSet

На підставі з'єднання можна отримати об'єкт java.sql.Statement для виконання запитів до бази.

В результаті виконання цього фрагмента коду буде створено таблицю user із двома колонками id та name.

Statement можна використовувати для виконання будь-яких запитів, будь то DDL, DML або звичайні запити на вибірку даних.

Об'єкт ResultSet – це результат виконання запиту.

Після використання необхідно закривати об'єкти Connection, Statement і ResultSet. Тому наведений вище код необхідно обернути в try-finally та в блоці finally додати закриття ресурсів:

Виглядає не дуже красиво, чи не так. Закриття ResultSet можна прибрати, адже відповідно до контракту:

A ResultSet об'єкт є автоматично closed by the Statement object що generated it when that Statement object is closed, re-executed, or is used to retrieve the next result from sequence of multiple results.

Але все одно не те.

З приходом Java 1.7 ситуація трохи змінилася на краще, так як була додана конструкція try-with-resources, яка гарантує, що всі Closeable ресурси будуть закриті після виконання try блоку. Наш код перетворюється на наступний елегантніший фрагмент:

Підпорядкованийдержав і пакетне виконання запитів

Якщо вам потрібно виконати кілька схожих запитів, тоДоцільним рішенням буде використання попередньогодержави.

PreparedStatement є скомпільованою версією SQL-виразу, виконання якого буде швидше і ефективніше.

PreparedStatement підтримує пакетну (batch) відправку SQL запитів, що значно зменшує трафік між клієнтом та базою даних. Невеликий приклад:

Зауважте, що проставляти параметри в PreparedStatement необхідно через індекси, до того ж відлік йде з одиниці. Якщо параметрів багато і є ймовірність, що вони періодично додаватимуться або видалятимуться, то можна скористатися таким варіантом:

Транзакції у JDBC

Тих, хто знайомився з Hibernate мимо JDBC, зазвичай дуже дивує робота з транзакціями.

За промовчанням кожен SQL-вираз автоматично комітується під час statement.execute та подібних методів. Для того, щоб відкрити транзакцію, спочатку необхідно встановити прапор autoCommit у з'єднання в значення false. Ну а далі нам знадобляться всім знайомі методи commit та rollback.

Використання DatabaseMetaData

За допомогою Connection можна отримати дуже корисну суть DatabaseMetaData. Вона дозволяє отримати метаінформацію про схему бази даних, а саме які у базі даних є об'єкти - таблиці, колонки, індекси, тригери, процедури тощо.

Особисто я часто використовую DatabaseMetaData для модифікації схеми бази даних програмним способом, наприклад:

Після додавання нової функціональності можна перевірити, а чи не створені відповідні об'єкти, і у разі потреби модифікувати схему.

Висновок

Звичайно, JDBC API створено далеко не ідеальним. Наприклад, SQLException є checked винятком і його всюди треба тягнути чи обертати; робота з PreparedStatementдосить незручна, як ми вже бачили.

Але в переважній більшості випадків я використовую саме JDBC для своїх додатків, оскільки JDBC дає максимальну гнучкість та ефективність. Можливо з Hibernate ви заощадите один день, тому що вам не доведеться писати код для створення схеми, а також запити для читання та запису об'єктів. Але що таке один день у порівнянні з часом існування програми? До того ж практика показує, що періодично Hibernate підносить розробникам цікаві челенджі, вирішення яких може забрати не лише час, а й нерви.

Повний вихідний код прикладів статті можна знайти тут SqlExamples.java.

Якщо ви вважаєте, що якомусь питанню я приділив недостатньо уваги, напишіть мені – і я обов'язково доповню статтю.