6. [Cours]: Вступ до API JDBC
Ключові слова: реляційні бази даних, API JDBC, SQLException.
6.1. Support
![]() | ![]() | ![]() |
Папка [support / chap-06] містить проекти Eclipse з цього розділу.
6.2. Architecture
![]() |
Рівень JDBC (Java DataBase Connectivity) є універсальним інтерфейсом доступу до баз даних. Він завжди надає однаковий інтерфейс для рівня [DAO]. Якщо змінюється SGBD, достатньо змінити драйвер JDBC. Рівень [DAO] не змінюється.
6.3. Етапи експлуатації бази даних
![]() |
У наведеній вище архітектурі експлуатація бази даних консольною програмою включає такі етапи:
- завантаження драйвера JDBC бази даних;
- відкриття з'єднання з базою даних;
- виконання команди SQL у базі даних та обробка результатів команди SQL;
- закриття з'єднання;
Крок 1 виконується лише один раз. Кроки 2–4 виконуються повторно. Слід зауважити, що з’єднання не залишається відкритим. Його закривають, щойно воно більше не потрібне.
6.3.1. крок 1 — завантаження драйвера JDBC у пам’ять
Код
// завантаження драйвера JDBC
try {
Class.forName(nom de la classe du pilote JDBC);
} catch (ClassNotFoundException e1) {
// обробка винятку
}
Операція в рядку 3 має на меті завантажити в пам'ять драйвер JDBC із бази даних. Цю операцію потрібно виконати лише один раз. Однак її повторення не спричиняє помилки. Клас драйвера JDBC шукається в Classpath проекту. Тому в проєкті Eclipse файл [jar], що містить клас драйвера JDBC, має бути включений до Classpath проєкту.
6.3.2. Крок 2 — встановлення з’єднання
Після встановлення драйвера JDBC йому потрібно відкрити з’єднання з BD:
Код
package spring.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class IntroJdbc01 {
...
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// відкриття з'єднання
connexion = DriverManager.getConnection(url, user, passwd);
...
} catch (SQLException e1) {
// обробка винятку
...
} finally {
// закриття з'єднання
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e2) {
// обробити виняток
...
}
}
}
- рядки 3–7: класи реалізації інтерфейсу JDBC знаходяться у пакеті [java.sql]. Крім того, у разі помилки всі вони генерують виняток типу [SQLException] (рядки 19, 27). Цей виняток походить від класу [Exception] і є так званим контрольованим винятком: для його обробки необхідно використовувати блок try/catch або, як альтернатива, не обробляти його та вказати, що метод дозволяє винятковій ситуації вийти, доповнивши сигнатуру методу [throws SQLException];
- рядок 17, [DriverManager.getConnection] — це статичний метод, який очікує три параметри:
- [url]: URL з бази даних. Це рядок символів, що залежить від використовуваного BD. Для MySQL вона має вигляд [jdbc:mysql://localhost:3306/nom_de_la_bd];
- [user]: власник з’єднання;
- [passwd]: його пароль;
- рядки 24–30: з’єднання має бути закрите у клаузулі [finally], щоб воно закривалося незалежно від того, чи виникло виключення.
6.3.3. крок 3 — видача команд SQL та [SELECT]
Після встановлення з’єднання можна відправляти команди SQL. Спосіб обробки команд читання [SELECT] відрізняється від того, що використовується для операцій оновлення [UPDATE, INSERT, DELETE]. Почнемо з команд SQL та [SELECT]:
Код
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// початок сеансу
connexion = DriverManager.getConnection(url, user, passwd);
// початок транзакції
connexion.setAutoCommit(false);
// у режимі «тільки для читання»
connexion.setReadOnly(true);
// зчитування таблиці [PRODUITS]
ps = connexion.prepareStatement("SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS");
rs = ps.executeQuery();
System.out.println("Liste des produits : ");
while (rs.next()) {
System.out.println(new Produit(rs.getInt(1), rs.getString(2), rs.getInt(3), rs.getDouble(4), rs.getString(5)));
}
// фіксація транзакції
connexion.commit();
} catch (SQLException e1) {
// обробка винятку
doCatchException(connexion,e1);
} finally {
// обробка блоку finally
doFinally(rs, ps, connexion);
}
private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
....
}
- рядки 8, 10: відкриття транзакції (рядок 8) у режимі «тільки для читання» (рядок 10). Транзакція — це послідовність команд SQL, які або всі виконуються успішно, або всі завершуються невдачею. Отже, у транзакції, що містить N команд SQL, якщо команда I+1 завершиться невдало, то попередні I команд будуть скасовані. Для операції читання транзакція не потрібна. Проте створення транзакції «тільки для читання» може дозволити деяким командам SGBD здійснити певні оптимізації;
- рядок 12: використання [PreparedStatement]. [PreparedStatement] зазвичай має параметри, позначені символом ?. Тут їх немає. [PreparedStatement] — це запит, підготовлений SGBD. Ця підготовка має певну вартість і виконується лише один раз. Потім цей підготовлений запит виконується SGBD із різними фактичними параметрами, які замінять формальні параметри «?». Слід зауважити, що краще вказувати потрібні стовпці, а не використовувати символ * для отримання всіх стовпців. Вказавши назви стовпців, можна потім отримати їхні значення на основі їхнього положення у запиті SELECT;
- рядок 13: виконання запиту [PreparedStatement]. Отримуємо об’єкт типу [ResultSet];
Об’єкт типу [ResultSet] представляє таблицю, тобто сукупність рядків і стовпців. У певний момент часу доступний лише один рядок таблиці, який називається поточним рядком. Під час початкового створення [ResultSet] поточного рядка немає. Щоб її отримати, потрібно виконати операцію [ResultSet.next()]. Сигнатура методу next така:
Цей метод намагається перейти до наступного рядка [ResultSet] і повертає true у разі успіху, а false — у разі невдачі. У разі успіху наступний рядок стає новим поточним рядком. Попередній рядок втрачається, і повернутися назад, щоб його відновити, неможливо.
Таблиця [ResultSet] має стовпці з іменами labelCol1, labelCol2, ..., які вказані у виконаному запиті [SELECT]. З запитом:
SELECT ID as myId, NOM as myNom, CATEGORIE as myCategorie, PRIX as myPrix, DESCRIPTION as myDescription FROM PRODUITS
- стовпець [ID] буде перенесено до стовпця таблиці [ResultSet] з назвою [myId];
- стовпець [NOM] буде переміщено до стовпця таблиці [ResultSet] з назвою [myNom];
- ...
У наведеному вище прикладі ідентифікатори [myCol] називаються мітками стовпців. За відсутності цих міток імена стовпців таблиці [ResultSet] залежать від таблиці SGBD. Коли [SELECT] оперує з однією таблицею, мітками стовпців за замовчуванням будуть імена стовпців, запитувані SELECT. Проблема виникає, коли [SELECT] оперує кількома таблицями, у яких зустрічаються однакові імена стовпців, як у наведеному нижче прикладі:
SELECT PRODUITS.NOM, CATEGORIES.NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID
припустимо, що таблиця [PRODUITS] має зовнішній ключ до таблиці [CATEGORIES], що символізується відношенням [Produits].CATEGORIE_ID --> [CATEGORIES].ID, а таблиці [PRODUITS] та [CATEGORIES] обидві мають поле [NOM]. У цьому випадку імена, надані в таблиці [ResultSet] стовпцям [PRODUITS.NOM] та [CATEGORIES.NOM], залежать від таблиці SGBD. Отже, для перенесення даних між файлами SGBD тут слід використовувати мітки стовпців, і ми напишемо:
SELECT PRODUITS.NOM as p_NOM, CATEGORIES.NOM as c_NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID
Для обробки різних полів поточного рядка [ResultSet] доступні такі методи:
для отримання стовпця з назвою «labelColi» з поточного рядка, а отже, стовпця з файлу [SELECT], що має цей міток. Type позначає тип поля coli. Можна використовувати такі методи [getType]: getInt, getLong, getString, getDouble, getFloat, getDate, ... Замість імені стовпця можна використовувати його позицію у виконаному запиті [SELECT]:
де i — індекс потрібного стовпця (i>=1).
- рядки 15–17: отримання значень, зчитаних у BD;
- рядок 19: транзакція підтверджується (це також називають «фіксацією»). Це завершує її та звільняє ресурси, які SGBD задіяв для неї;
- рядок 25: ресурси звільняються у [finally]. Ця процедура викликає наступний метод [doFinally]:
private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
// закриття ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// закриття [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
if (connexion != null) {
try {
// закрити з'єднання
connexion.close();
} catch (SQLException e3) {
// обробити виняток
}
}
}
- рядки 3–9: закриття [ResultSet];
- рядки 11–17: закриття [PreparedStatement];
- рядки 18–27: закриття з’єднання;
Закриття у рядках 3–17 здаються зайвими, оскільки з’єднання закривається у рядках 18–25. Насправді в деяких випадках це не так, і рекомендується залишити їх [http://stackoverflow.com/questions/4507440/must-jdbc-resultsets-and-statements-be-closed-separately-although-the-connection].
- рядок 22: виняток обробляється наступним методом [doCatchException]:
private static void doCatchException(Connection connexion, Throwable th) {
// скасування транзакції
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// обробити виняток
}
}
- рядки 4–6: транзакція скасовується. Це завершує її, і SGBD зможе звільнити ресурси, задіяні для неї;
6.3.4. етап 3 — видача команд SQL та [INSERT, UPDATE, DELETE]
Команди SQL та [INSERT, UPDATE, DELETE] є операціями оновлення: вони змінюють базу даних, але не повертають жодного рядка. Єдина інформація, що повертається, — це кількість рядків, на які вплинула операція оновлення.
Код
Connection connexion = null;
PreparedStatement ps = null;
try {
// відкрити з'єднання
connexion = DriverManager.getConnection(url, user, passwd);
// початок транзакції
connexion.setAutoCommit(false);
// у режимі читання/запису
connexion.setReadOnly(false);
// оновлення таблиці
ps = connexion.prepareStatement("UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?");
// категорія 1
ps.setInt(1, 10);
// виконання
int nbLignes=ps.executeUpdate();
// фіксація транзакції
connexion.commit();
} catch (SQLException e1) {
// обробка винятку
doCatchException(connexion, e1);
} finally {
// обробка блоку finally
doFinally(null, ps, connexion);
}
}
- рядок 9: з’єднання використовується для читання та запису;
- рядок 11: операція [PreparedStatement] з 1 параметром (позначеним символом ?). Параметрів може бути кілька. Вони нумеруються, починаючи з 1;
- рядок 13: єдиному параметру присвоюється значення. Перший параметр [setType] — це позиція параметра в [PreparedStatement] (1, 2, ...), а другий — значення, яке йому присвоюється. Можна використовувати методи [setInt, setLong, setFloat, setDouble, setString, setDate, ...];
- рядок 15: використовується метод [executeUpdate], а не [executeQuery], який зарезервовано для команд SELECT. Цей метод повертає кількість рядків, на які вплинула операція. Може дорівнювати 0.
- рядок 17: транзакція підтверджена;
6.3.5. етап 4 — закриття з'єднання
У багатокористувацькому середовищі з’єднання слід закривати якомога швидше, оскільки SGBD допускає обмежену кількість відкритих з’єднань. У наведених вище прикладах з’єднання закривалося в розділі [finally] операції SQL, щоб воно закривалося незалежно від того, чи стався виняток.
6.4. Приклад проекту
6.4.1. Підтримка
![]() |
Папка [support / chap5] містить проекти Eclipse з цього розділу [1, 2]. Папка [database] містить скрипт SQL, що дозволяє створити приклад бази даних MySQL, описаний у цьому розділі [1, 3].
6.4.2. Використовувана база даних
У наведених нижче прикладах використовується така база даних MySQL:
![]() |
- [ID]: первинний ключ у режимі AUTO_INCREMENT (якщо первинний ключ не вказано, SGBD генерує його);
- [NOM]: назва товару — унікальна;
- [CATEGORIE]: номер категорії;
- [PRIX]: його ціна;
- [DESCRIPTION]: опис товару;
Ми створимо її за допомогою інструменту [WampServer] наступним чином: [1-9]:
![]() |
![]() |
![]() |
![]() |
6.4.3. Проєкт Eclipse
![]() |
Проект є проектом Maven, визначеним наступним файлом [pom.xml]:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.jdbc</groupId>
<artifactId>intro-jdbc-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.1</version>
</dependency>
</dependencies>
</project>
- рядки 8–12: драйвер JDBC для SGBD MySQL5;
- рядки 13–17: бібліотека, здатна обробляти jSON (Javascript Object Notation) (див. параграф 22.6). Ми будемо використовувати її для відображення товарів із бази даних у форматі jSON;
6.4.4. Клас товарів
Клас [Produit] має такий вигляд:
package istia.st.jdbc;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Produit {
// поля
private int id;
private String nom;
private int categorie;
private double prix;
private String description;
// конструктори
public Produit() {
}
public Produit(int id, String nom, int categorie, double prix, String description) {
this.id = id;
this.nom = nom;
this.categorie = categorie;
this.prix = prix;
this.description = description;
}
// методи getter та setter
...
// в String
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
}
- рядок 34: використовується бібліотека jSON для відображення рядка jSON товару. У результаті отримуємо відображення, подібне до такого:
Liste des produits :
{"id":1,"nom":"NOM1","categorie":1,"prix":100.0,"description":"DESC1"}
{"id":2,"nom":"NOM2","categorie":1,"prix":101.0,"description":"DESC2"}
Перевага наведеного вище методу [toString] полягає в тому, що навіть якщо до класу додати або видалити поля, його метод [toString] завжди залишається дійсним. Крім того, якщо самі поля є об’єктами (списки, масиви, словники, об’єкти користувача), бібліотеки jSON вміють перетворювати їх у черги jSON;
6.4.5. Клас [Static]
Клас [Static] об’єднує у методах код, який часто використовується в головному класі:
package istia.st.jdbc;
import java.util.ArrayList;
import java.util.List;
public class Static {
public static List<String> getErreursFromThrowable(Throwable th) {
// отримання списку повідомлень про помилки винятку
List<String> erreurs = new ArrayList<String>();
while (th != null) {
// повідомлення про помилку об'єкта, що викликає виняток
erreurs.add(th.getMessage());
// перехід до причини об’єкта, що викликає виняток
th = th.getCause();
}
// результат
return erreurs;
}
public static void show(String title, List<String> messages){
// заголовок
System.out.println(String.format("%s : ",title));
// повідомлення
for(String message : messages){
System.out.println(String.format("- %s",message));
}
}
}
- рядки 8–19: дозволяють отримати список помилок, інкапсульованих в об’єкт типу [Throwable], який є батьківським класом класу [Exception];
- рядки 21–28: виводить на екран список повідомлень;
Цей код міг би бути в головному класі, оскільки тут він використовується лише ним. Однак ми розглядаємо більш загальний випадок, коли цей код може знадобитися й іншим класам.
6.4.6. Скелет головного класу
package istia.st.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class IntroJdbc01 {
// константи
final static String url = "jdbc:mysql://localhost:3306/dbIntroJdbc";
final static String user = "root";
final static String passwd = "";
final static String insert = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (?, ?, ?, ?, ?)";
final static String delete = "DELETE FROM PRODUITS";
final static String select = "SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS";
final static String update = "UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?";
final static String insert2 = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (100,'X',1,1,'x')";
public static void main(String[] args) {
// завантаження драйвера JDBC з MySQL
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e1) {
doCatchException("Pilote JDBC introuvable", null, e1);
return;
}
// очищення таблиці [PRODUITS]
delete();
// заповнення таблиці
insert();
// зчитування
select();
// оновлення
update();
// виведення
select();
// вставлення двох однакових елементів
// вставлення має завершитися невдало, і жоден з двох елементів не вставляється через транзакцію
insert2();
// перевіряється
select();
// завершено
System.out.println("Travail terminé");
}
// список товарів
private static void select() {
...
}
// видалення товарів
public static void delete() {
...
}
// додавання товарів
public static void insert() {
...
}
// додавання 2 товарів
public static void insert2() {
...
}
// оновлення деяких товарів
public static void update() {
..
}
private static void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
// закриття ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// закриття [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// закрити з'єднання
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// відображаються повідомлення про помилки
Static.show("Les erreurs suivantes se sont produites lors de la fermeture de la connexion",
Static.getErreursFromThrowable(e3));
}
}
}
private static void doCatchException(String title, Connection connexion, Throwable th) {
// відображаються повідомлення про помилки
Static.show(title, Static.getErreursFromThrowable(th));
// скасування транзакції
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// відображаються повідомлення про помилки
Static.show(title, Static.getErreursFromThrowable(e2));
}
}
}
6.4.7. Видалення вмісту таблиці товарів
Метод [delete] видаляє вміст таблиці:
// видалення товарів
public static void delete() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// встановлення з'єднання
connexion = DriverManager.getConnection(url, user, passwd);
// початок транзакції
connexion.setAutoCommit(false);
// очищення таблиці [PRODUITS]
ps = connexion.prepareStatement(delete);
ps.executeUpdate();
// фіксація транзакції
connexion.commit();
// повернення до режиму за замовчуванням
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// обробка винятку
doCatchException("Les erreurs suivantes se sont produites à la suppression du contenu de la table", connexion, e1);
} finally {
// обробка блоку finally
doFinally(null, null, connexion);
}
}
У цьому прикладі використовуються транзакції. Транзакція дозволяє об’єднати команди SQL, які мають бути виконані всі або скасовані всі. Необхідно знати чотири операції:
- початок транзакції: [connexion.setAutoCommit(false)];
- успішне завершення транзакції: [connexion.commit()]. У цьому випадку всі операції, виконані з BD під час транзакції, вважаються дійсними;
- завершення транзакції з помилкою: [connexion.rollback()]. У цьому випадку всі операції, виконані з BD під час транзакції, скасовуються;
- повернення до режиму [auto-commit], який є режимом за замовчуванням для API JDBC: [connexion.setAutoCommit(true)]. У цьому режимі кожна команда SQL є предметом транзакції. Отже, якщо виконати дві операції вставки, друга з яких завершилася невдало:
- у режимі [AutoCommit=true] перша операція вставки зберігається (вона була підтверджена першим AutoCommit);
- у режимі [AutoCommit=false] перша операція вставки скасовується;
У наших прикладах щоразу, коли виникає виняток, ми скасовуємо транзакцію в методі [doCatchException]:
private static void doCatchException(String title, Connection connexion, Throwable th) {
// виведення повідомлень про помилки
Static.show(title, Static.getErreursFromThrowable(th));
// скасування транзакції
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// виводимо повідомлення про помилки
Static.show("Erreur lors de l'annulation de la transaction", Static.getErreursFromThrowable(e2));
}
}
6.4.8. Створення вмісту таблиці товарів
Метод [insert] створює вміст таблиці:
// додавання товарів
public static void insert() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// встановлення з'єднання
connexion = DriverManager.getConnection(url, user, passwd);
// початок транзакції
connexion.setAutoCommit(false);
// заповнення таблиці
ps = connexion.prepareStatement(insert);
for (int i = 0; i < 10; i++) {
// підготовка
int n = i + 1;
ps.setInt(1, n);
ps.setString(2, String.format("NOM%s", n));
ps.setInt(3, n / 5 + 1);
ps.setDouble(4, 100 * (1 + (double) i / 100));
ps.setString(5, String.format("DESC%s", n));
// виконання
ps.executeUpdate();
}
// фіксація транзакції
connexion.commit();
// повернення до режиму за замовчуванням
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// обробка винятку
doCatchException("Les erreurs suivantes se sont produites à la création du contenu de la table", connexion, e1);
} finally {
// обробка блоку finally
doFinally(null, null, connexion);
}
}
6.4.9. Виведення вмісту таблиці товарів
Метод [select] відображає вміст таблиці:
// список товарів
private static void select() {
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// відкриття з'єднання
connexion = DriverManager.getConnection(url, user, passwd);
// початок транзакції
connexion.setAutoCommit(false);
// зчитування таблиці [PRODUITS]
ps = connexion.prepareStatement(select);
rs = ps.executeQuery();
System.out.println("Liste des produits : ");
while (rs.next()) {
System.out.println(new Produit(rs.getInt(1), rs.getString(2), rs.getInt(3), rs.getDouble(4), rs.getString(5)));
}
// фіксація транзакції
connexion.commit();
// повернення до режиму за замовчуванням
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// обробка винятку
doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// обробка блоку finally
doFinally(null, null, connexion);
}
}
6.4.10. Оновлення вмісту таблиці
Метод [update] оновлює певні товари:
// оновлення деяких продуктів
public static void update() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// відкриття з'єднання
connexion = DriverManager.getConnection(url, user, passwd);
// початок транзакції
connexion.setAutoCommit(false);
// оновлення таблиці
ps = connexion.prepareStatement(update);
// категорія 1
ps.setInt(1, 1);
// виконання
ps.executeUpdate();
// фіксація транзакції
connexion.commit();
// повернення до режиму за замовчуванням
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// обробка винятку
doCatchException("Les erreurs suivantes se sont produites à la mise à jour du contenu de la table", connexion, e1);
} finally {
// обробка блоку finally
doFinally(null, null, connexion);
}
}
6.4.11. Роль транзакції
Метод [insert2] вставляє в таблицю два продукти з однаковим первинним ключем, що є неможливим. Оскільки операція виконується в рамках транзакції, перше вставлення буде скасовано.
// додавання 2 товарів з однаковими первинними ключами
public static void insert2() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// відкриття з'єднання
connexion = DriverManager.getConnection(url, user, passwd);
// початок транзакції
connexion.setAutoCommit(false);
// додано 1 рядок
ps = connexion.prepareStatement(insert2);
// виконання
ps.executeUpdate();
// додається той самий рядок вдруге, тобто з тим самим первинним ключем
// вставлення має завершитися невдачею, і жоден з цих двох елементів не має бути вставлений через транзакцію
ps.executeUpdate();
// фіксація транзакції
connexion.commit();
// повернення до режиму за замовчуванням
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// обробка винятку
doCatchException("Les erreurs suivantes se sont produites lors de l'ajout", connexion, e1);
} finally {
// обробка блоку finally
doFinally(null, null, connexion);
}
}
6.4.12. Результати
Результати виконання методу [main] такі:
6.5. Використання джерела даних типу [DataSource]
Ми повернемося до попереднього додатка, використовуючи джерело даних типу [javax.sql.DataSource]:

Ми будемо використовувати джерело даних, реалізоване класом [org.apache.tomcat.jdbc.pool.DataSource]. Цей клас використовує пул з’єднань, тобто набір відкритих з’єднань:
- під час інстанціювання пулу відкривається певна кількість з’єднань із базою даних. Цю кількість можна налаштувати;
- коли код Java відкриває з’єднання, воно надається пулом;
- коли код Java закриває з’єднання, воно повертається до пулу;
У підсумку з’єднання відкриваються лише один раз, що покращує продуктивність доступу до бази даних. Джерело даних буде визначено в класі конфігурації Spring
6.5.1. Проєкт Eclipse
![]() |
Проєкт є проєктом Maven, визначеним у файлі [pom.xml]:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.jdbc</groupId>
<artifactId>intro-jdbc-02</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<!-- бібліотека jSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.1</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
<!-- Tomcat JDBC -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<version>8.0.20</version>
</dependency>
</dependencies>
</project>
- рядки 21–25: залежність від Spring;
- рядки 27–31: залежність від бібліотеки, що надає джерело даних;
Клас конфігурації Spring [AppConfig] має такий вигляд:
package istia.st.jdbc;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
// константи
final static String URL = "jdbc:mysql://localhost:3306/dbIntroJdbc";
final static String USER = "root";
final static String PASSWD = "";
final static String INSERT = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (?, ?, ?, ?, ?)";
final static String DELETE = "DELETE FROM PRODUITS";
final static String SELECT = "SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS";
final static String UPDATE = "UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?";
final static String INSERT2 = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (100,'X',1,1,'x')";
final static String DRIVER_CLASSNAME = "com.mysql.jdbc.Driver";
@Bean
public DataSource dataSource() {
// джерело даних TomcatJdbc
DataSource dataSource = new DataSource();
// конфігурація доступу JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// спочатку відкрите з'єднання
dataSource.setInitialSize(1);
// результат
return dataSource;
}
}
- рядки 11–19: константи, раніше визначені в [IntroJdbc01], перенесені до [AppConfig];
- рядки 31–34: Spring-бін, що визначає джерело даних;
- рядок 24: створення джерела даних, яке ще не налаштовано;
- рядки 26–29: інформація, що дозволяє джерелу даних підключитися до бази даних;
- рядок 31: створює пул із 1 з’єднання. Більше тут не потрібно. Одночасних з’єднань ніколи не буває;
6.5.2. Головний клас
Головний клас [IntroJdbc02] має такий вигляд:
package istia.st.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IntroJdbc02 {
// джерело даних
private static DataSource dataSource;
public static void main(String[] args) {
// отримання контексту Spring
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
// отримання джерела даних
dataSource = ctx.getBean(DataSource.class);
// очищення таблиці [PRODUITS]
delete();
...
// завершено
ctx.close();
System.out.println("Travail terminé");
}
// список товарів
private static void select() {
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// відкриття з'єднання
connexion = dataSource.getConnection();
// початок транзакції
connexion.setAutoCommit(false);
...
} catch (SQLException e1) {
// обробка винятку
doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// обробка блоку finally
doFinally(null, null, connexion);
}
}
...
}
- рядок 14: джерело даних. Зазначимо, що воно має тип [javax.sql.DataSource], який є інтерфейсом;
- рядок 18: створення екземплярів об’єктів Spring;
- рядок 20: отримання посилання на джерело даних. Зазначимо, що ніде не згадується клас, який фактично використовується. Отже, тут ніщо не вказує на те, що використовується реалізація [TomcatJdbc];
- рядок 36: отримання відкритого з’єднання;
- решта коду ідентична коду класу [IntroJdbc01];
6.6. Conclusion
Більше інформації про управління базами даних можна знайти в документі [Exploiter une base relationnelle avec l'écosystème Spring].












