6. [Cours]: Wprowadzenie do API JDBC
Słowa kluczowe: relacyjne bazy danych, API JDBC, SQLException.
6.1. Support
![]() | ![]() | ![]() |
Folder [support / chap-06] zawiera projekty Eclipse z tego rozdziału.
6.2. Architecture
![]() |
Warstwa JDBC (Java DataBase Connectivity) stanowi uniwersalny interfejs dostępu do baz danych. Zawsze udostępnia ten sam interfejs warstwie [DAO]. W przypadku zmiany warstwy SGBD wystarczy zmienić sterownik JDBC. Warstwa [DAO] pozostaje bez zmian.
6.3. Etapy obsługi bazy danych
![]() |
W powyższej architekturze obsługa bazy danych przez program konsolowy obejmuje następujące etapy:
- załadowanie sterownika bazy danych JDBC;
- nawiązanie połączenia z bazą;
- wysłanie polecenia SQL do bazy danych oraz przetworzenie wyników polecenia SQL;
- zamknięcie połączenia;
Krok 1 wykonuje się tylko raz. Kroki 2–4 wykonuje się wielokrotnie. Należy zauważyć, że nie pozostawia się otwartego połączenia. Zamyka się je, gdy tylko przestaje być potrzebne.
6.3.1. krok 1 – załadowanie sterownika JDBC do pamięci
Kod
// ładowanie sterownika JDBC
try {
Class.forName(nom de la classe du pilote JDBC);
} catch (ClassNotFoundException e1) {
// obsługa wyjątku
}
Operacja w wierszu 3 ma na celu załadowanie do pamięci sterownika JDBC z bazy danych. Operację tę należy wykonać tylko raz. Jej powtórzenie nie powoduje jednak błędu. Klasa sterownika JDBC jest wyszukiwana w ścieżce Classpath projektu. W związku z tym w projekcie Eclipse plik [jar] zawierający klasę sterownika JDBC musi zostać dodany do ścieżki Classpath projektu.
6.3.2. Krok 2 – nawiązanie połączenia
Po zainstalowaniu sterownika JDBC należy polecić mu otwarcie połączenia ze sterownikiem BD:
Kod
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 {
// otwarcie połączenia
connexion = DriverManager.getConnection(url, user, passwd);
...
} catch (SQLException e1) {
// obsługa wyjątku
...
} finally {
// zamknięcie połączenia
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e2) {
// obsługa wyjątku
...
}
}
}
- wiersze 3–7: klasy implementujące interfejs JDBC znajdują się w pakiecie [java.sql]. Ponadto w przypadku błędu wszystkie one generują wyjątek typu [SQLException] (wiersze 19, 27). Wyjątek ten pochodzi od klasy [Exception] i jest tzw. wyjątkiem kontrolowanym: konieczne jest zastosowanie bloku try/catch w celu jego obsługi lub, alternatywnie, rezygnacja z obsługi i wskazanie, że metoda pozwala na wystąpienie wyjątku poprzez uzupełnienie sygnatury metody o [throws SQLException];
- wiersz 17, [DriverManager.getConnection] to metoda statyczna, która oczekuje trzech parametrów:
- [url]: wartość URL z bazy danych. Jest to ciąg znaków zależny od użytej wartości BD. W przypadku MySQL ma ona postać [jdbc:mysql://localhost:3306/nom_de_la_bd];
- [user]: właściciel połączenia;
- [passwd]: jego hasło;
- wiersze 24–30: połączenie musi zostać zamknięte w klauzuli [finally], aby zostało zamknięte niezależnie od tego, czy wystąpi wyjątek, czy nie.
6.3.3. krok 3 – wysyłanie poleceń SQL i [SELECT]
Po nawiązaniu połączenia można wysyłać polecenia SQL. Sposób obsługi poleceń odczytu [SELECT] różni się od sposobu stosowanego w przypadku operacji aktualizacji [UPDATE, INSERT, DELETE]. Zaczynamy od poleceń SQL i [SELECT]:
Kod
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// rozpoczęcie sesji
connexion = DriverManager.getConnection(url, user, passwd);
// rozpoczęcie transakcji
connexion.setAutoCommit(false);
// w trybie tylko do odczytu
connexion.setReadOnly(true);
// odczyt tabeli [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)));
}
// zatwierdzenie transakcji
connexion.commit();
} catch (SQLException e1) {
// obsługa wyjątku
doCatchException(connexion,e1);
} finally {
// obsługa bloku finally
doFinally(rs, ps, connexion);
}
private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
....
}
- wiersze 8, 10: otwarcie transakcji (wiersz 8) w trybie tylko do odczytu (wiersz 10). Transakcja to sekwencja poleceń SQL, które albo wszystkie zakończą się powodzeniem, albo wszystkie zakończą się niepowodzeniem. Zatem w transakcji zawierającej N poleceń SQL, jeśli polecenie I+1 zakończy się niepowodzeniem, wówczas poprzednie I polecenia zostaną anulowane. W przypadku operacji odczytu transakcja nie jest konieczna. Niemniej jednak utworzenie transakcji tylko do odczytu może umożliwić niektórym poleceniom SGBD przeprowadzenie pewnych optymalizacji;
- wiersz 12: użycie polecenia [PreparedStatement]. Polecenie [PreparedStatement] zazwyczaj posiada parametry oznaczone znakiem ?. Tutaj ich nie ma. [PreparedStatement] to polecenie przygotowane przez SGBD. Przygotowanie to wiąże się z kosztem i odbywa się tylko raz. Następnie to przygotowane polecenie jest wykonywane przez SGBD z różnymi parametrami rzeczywistymi, które zastąpią parametry formalne „?”. Należy zauważyć, że lepiej jest podawać nazwy żądanych kolumn niż używać symbolu * w celu uzyskania wszystkich kolumn. Określając nazwy kolumn, można następnie uzyskać ich wartości na podstawie ich pozycji w zapytaniu SELECT;
- wiersz 13: wykonanie zapytania [PreparedStatement]. Pobieramy obiekt typu [ResultSet];
Obiekt typu [ResultSet] reprezentuje tabelę, czyli zbiór wierszy i kolumn. W danym momencie mamy dostęp tylko do jednego wiersza tabeli, zwanego wierszem bieżącym. Podczas początkowego tworzenia obiektu [ResultSet] nie ma wiersza bieżącego. Aby ją uzyskać, należy wykonać operację [ResultSet.next()]. Sygnatura metody next jest następująca:
Metoda ta próbuje przejść do następnego wiersza w [ResultSet] i zwraca true w przypadku powodzenia, a false w przeciwnym razie. W przypadku powodzenia następny wiersz staje się nowym wierszem bieżącym. Poprzedni wiersz zostaje utracony i nie ma możliwości cofnięcia się, aby go odzyskać.
Tabela o nazwie [ResultSet] zawiera kolumny o nazwach labelCol1, labelCol2, ... określone w wykonanym zapytaniu [SELECT]. Przy użyciu zapytania:
SELECT ID as myId, NOM as myNom, CATEGORIE as myCategorie, PRIX as myPrix, DESCRIPTION as myDescription FROM PRODUITS
- kolumna [ID] trafi do kolumny w tabeli [ResultSet] o nazwie [myId];
- kolumna [NOM] zostanie przeniesiona do kolumny w tabeli [ResultSet] o nazwie [myNom];
- ...
W powyższym przykładzie identyfikatory [myCol] nazywane są etykietami kolumn. W przypadku braku tych etykiet nazwy kolumn w tabeli [ResultSet] są zależne od tabeli SGBD. Gdy [SELECT] działa na jednej tabeli, etykiety kolumn będą domyślnie nazwami kolumn żądanych przez SELECT. Problem pojawia się, gdy [SELECT] działa na wielu tabelach, a w tych tabelach występują identyczne nazwy kolumn, jak w poniższym przykładzie:
SELECT PRODUITS.NOM, CATEGORIES.NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID
zakładając, że tabela [PRODUITS] posiada klucz obcy do tabeli [CATEGORIES], symbolizowany przez relację [Produits].CATEGORIE_ID --> [CATEGORIES].ID, a tabele [PRODUITS] i [CATEGORIES] posiadają obie pole [NOM]. W tym przypadku nazwy nadane w tabeli [ResultSet] kolumnom [PRODUITS.NOM] i [CATEGORIES.NOM] są zależne od tabeli SGBD. Aby zapewnić przenoszalność między plikami SGBD, należy zatem użyć tutaj etykiet kolumn i zapisać:
SELECT PRODUITS.NOM as p_NOM, CATEGORIES.NOM as c_NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID
Aby wykorzystać różne pola bieżącego wiersza pliku [ResultSet], dostępne są następujące metody:
aby uzyskać kolumnę o nazwie „labelColi” z bieżącego wiersza, a tym samym kolumnę z pliku [SELECT] posiadającą tę etykietę. Type określa typ pola coli. Można użyć następujących metod [getType]: getInt, getLong, getString, getDouble, getFloat, getDate, ... Zamiast nazwy kolumny można użyć jej pozycji w wykonanym zapytaniu [SELECT]:
gdzie i to indeks żądanej kolumny (i>=1).
- wiersze 15–17: pobieranie wartości odczytanych w zapytaniu BD;
- wiersz 19: transakcja zostaje zatwierdzona (mówi się również o jej „commit”); powoduje to jej zakończenie i zwolnienie zasobów, które transakcja SGBD dla niej zablokowała;
- wiersz 25: zasoby są zwalniane w transakcji [finally]. Ta z kolei wywołuje następującą metodę [doFinally]:
private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
// zamknięcie ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// zamknięcie [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
if (connexion != null) {
try {
// zamknięcie połączenia
connexion.close();
} catch (SQLException e3) {
// obsługa wyjątku
}
}
}
- wiersze 3–9: zamknięcie [ResultSet];
- wiersze 11–17: zamknięcie [PreparedStatement];
- wiersze 18–27: zamknięcie połączenia;
Zamknięcia w wierszach 3–17 wydają się zbędne, ponieważ połączenie jest zamykane w wierszach 18–25. W rzeczywistości w niektórych przypadkach nie są one zbędne i zaleca się pozostawienie ich w pliku [http://stackoverflow.com/questions/4507440/must-jdbc-resultsets-and-statements-be-closed-separately-although-the-connection].
- wiersz 22: wyjątek jest obsługiwany przez następującą metodę [doCatchException]:
private static void doCatchException(Connection connexion, Throwable th) {
// anulowanie transakcji
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// obsługa wyjątku
}
}
- wiersze 4–6: transakcja zostaje anulowana. To ją kończy, a metoda SGBD będzie mogła zwolnić zasoby przydzielone do jej realizacji;
6.3.4. etap 3 – wysłanie poleceń SQL i [INSERT, UPDATE, DELETE]
Polecenia SQL i [INSERT, UPDATE, DELETE] są operacjami aktualizującymi: modyfikują bazę danych, ale nie zwracają żadnych wierszy. Jedyną zwracaną informacją jest liczba wierszy, których dotyczy operacja aktualizacji.
Kod
Connection connexion = null;
PreparedStatement ps = null;
try {
// nawiązanie połączenia
connexion = DriverManager.getConnection(url, user, passwd);
// rozpoczęcie transakcji
connexion.setAutoCommit(false);
// w trybie odczytu/zapisu
connexion.setReadOnly(false);
// aktualizacja tabeli
ps = connexion.prepareStatement("UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?");
// kategoria 1
ps.setInt(1, 10);
// wykonanie
int nbLignes=ps.executeUpdate();
// zatwierdzenie transakcji
connexion.commit();
} catch (SQLException e1) {
// obsługa wyjątku
doCatchException(connexion, e1);
} finally {
// przetwarzanie bloku finally
doFinally(null, ps, connexion);
}
}
- wiersz 9: połączenie jest wykorzystywane do odczytu i zapisu;
- wiersz 11: operacja [PreparedStatement] z jednym parametrem (oznaczonym symbolem ?). Parametrów może być kilka. Są one numerowane od 1;
- wiersz 13: przypisuje się wartość do jedynego parametru. Pierwszy parametr [setType] to pozycja parametru w [PreparedStatement] (1, 2, ...), a drugi to przypisana mu wartość. Można użyć metod [setInt, setLong, setFloat, setDouble, setString, setDate, ...];
- wiersz 15: stosuje się metodę [executeUpdate], a nie [executeQuery], która jest zarezerwowana dla zleceń SELECT. Metoda zwraca liczbę wierszy, których dotyczy operacja. Może wynosić 0.
- wiersz 17: transakcja została zatwierdzona;
6.3.5. krok 4 – zamknięcie połączenia
W środowisku wieloużytkownikowym połączenie należy zamknąć jak najszybciej, ponieważ transakcja SGBD dopuszcza ograniczoną liczbę otwartych połączeń. W poprzednich przykładach połączenie było zamykane w klauzuli [finally] operacji SQL, tak aby zostało zamknięte niezależnie od tego, czy wystąpił wyjątek, czy nie.
6.4. Przykładowy projekt
6.4.1. Wsparcie
![]() |
Folder [support / chap5] zawiera projekty Eclipse z niniejszego rozdziału [1, 2]. Folder [database] zawiera skrypt SQL umożliwiający utworzenie przykładowej bazy danych MySQL z niniejszego rozdziału [1, 3].
6.4.2. Wykorzystywana baza danych
Poniższe przykłady wykorzystują następującą bazę danych MySQL:
![]() |
- [ID]: klucz główny w trybie AUTO_INCREMENT (jeśli nie podano klucza głównego, system SGBD generuje go);
- [NOM]: nazwa produktu – unikalna;
- [CATEGORIE]: numer kategorii;
- [PRIX]: cena;
- [DESCRIPTION]: opis produktu;
Utworzymy ją za pomocą narzędzia [WampServer] w następujący sposób: [1-9]:
![]() |
![]() |
![]() |
![]() |
6.4.3. Projekt Eclipse
![]() |
Projekt jest projektem Maven zdefiniowanym przez następujący plik [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>
- wiersze 8–12: sterownik JDBC dla SGBD MySQL5;
- wiersze 13–17: biblioteka obsługująca format jSON (Javascript Object Notation) (patrz punkt 22.6). Wykorzystamy ją do wyświetlania produktów z bazy danych w formacie jSON;
6.4.4. Klasa produktów
Klasa [Produit] ma następujący wygląd:
package istia.st.jdbc;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Produit {
// pola
private int id;
private String nom;
private int categorie;
private double prix;
private String description;
// konstruktory
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;
}
// metody pobierające i ustawiające
...
// na String
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
}
- wiersz 34: wykorzystujemy bibliotekę jSON do wyświetlenia ciągu znaków jSON produktu. Daje to wyświetlenie podobne do poniższego:
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"}
Zaletą powyższej metody [toString] jest to, że nawet jeśli dodamy lub usuniemy pola z klasy, jej metoda [toString] pozostaje nadal poprawna. Ponadto, jeśli pola same w sobie są obiektami (listy, tablice, słowniki, obiekty użytkownika), biblioteki jSON potrafią z kolei przekształcić je w ciąg znaków jSON;
6.4.5. Klasa [Static]
Klasa [Static] grupuje w metodach kod często używany w klasie głównej:
package istia.st.jdbc;
import java.util.ArrayList;
import java.util.List;
public class Static {
public static List<String> getErreursFromThrowable(Throwable th) {
// pobieranie listy komunikatów o błędach wyjątku
List<String> erreurs = new ArrayList<String>();
while (th != null) {
// komunikat o błędzie obiektu typu throwable
erreurs.add(th.getMessage());
// przechodzimy do przyczyny obiektu typu throwable
th = th.getCause();
}
// wynik
return erreurs;
}
public static void show(String title, List<String> messages){
// tytuł
System.out.println(String.format("%s : ",title));
// komunikaty
for(String message : messages){
System.out.println(String.format("- %s",message));
}
}
}
- wiersze 8–19: umożliwia uzyskanie listy błędów zamkniętych w obiekcie typu [Throwable], który jest klasą nadrzędną klasy [Exception];
- wiersze 21–28: wyświetla na ekranie listę komunikatów;
Kod ten mógłby znajdować się w klasie głównej, ponieważ w tym przypadku jest to jedyna klasa, która go wykorzystuje. Rozpatrujemy tutaj jednak szerszy przypadek, w którym inne klasy mogłyby potrzebować tego kodu.
6.4.6. Szkielet klasy głównej
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 {
// stałe
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) {
// ładowanie sterownika JDBC z MySQL
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e1) {
doCatchException("Pilote JDBC introuvable", null, e1);
return;
}
// opróżniono tabelę [PRODUITS]
delete();
// wypełnianie tabeli
insert();
// odczyt tabeli
select();
// aktualizacja
update();
// wyświetlanie
select();
// wstawianie dwóch identycznych elementów
// wstawianie musi się nie powieść i żaden z tych dwóch elementów nie zostanie wstawiony z powodu transakcji
insert2();
// sprawdzamy
select();
// koniec
System.out.println("Travail terminé");
}
// lista produktów
private static void select() {
...
}
// usuwanie produktów
public static void delete() {
...
}
// dodawanie produktów
public static void insert() {
...
}
// dodanie 2 produktów
public static void insert2() {
...
}
// aktualizacja niektórych produktów
public static void update() {
..
}
private static void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
// zamknięcie ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// zamknięcie [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// zamknięcie połączenia
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// wyświetlane są komunikaty o błędach
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) {
// wyświetla się komunikat o błędzie
Static.show(title, Static.getErreursFromThrowable(th));
// anulowanie transakcji
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// wyświetlanie komunikatów o błędach
Static.show(title, Static.getErreursFromThrowable(e2));
}
}
}
6.4.7. Usunięcie zawartości tabeli produktów
Metoda [delete] usuwa zawartość tabeli:
// usunięcie produktów
public static void delete() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// nawiązanie połączenia
connexion = DriverManager.getConnection(url, user, passwd);
// rozpoczęcie transakcji
connexion.setAutoCommit(false);
// opróżnianie tabeli [PRODUITS]
ps = connexion.prepareStatement(delete);
ps.executeUpdate();
// zatwierdzenie transakcji
connexion.commit();
// powrót do trybu domyślnego
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// obsługa wyjątku
doCatchException("Les erreurs suivantes se sont produites à la suppression du contenu de la table", connexion, e1);
} finally {
// przetwarzamy blok finally
doFinally(null, null, connexion);
}
}
W tym przykładzie wykorzystano transakcje. Transakcja pozwala zgrupować polecenia SQL, które muszą zostać wykonane pomyślnie lub anulowane. Należy zapoznać się z czterema operacjami:
- rozpoczęcie transakcji: [connexion.setAutoCommit(false)];
- pomyślne zakończenie transakcji: [connexion.commit()]. W tym przypadku wszystkie operacje wykonane na BD podczas transakcji są zatwierdzone;
- niepowodzenie transakcji: [connexion.rollback()]. W tym przypadku wszystkie operacje wykonane na BD podczas transakcji zostają anulowane;
- powrót do trybu [auto-commit], który jest trybem domyślnym dla API JDBC: [connexion.setAutoCommit(true)]. W tym trybie każde polecenie SQL stanowi przedmiot transakcji. Jeśli więc wykonamy dwa wstawienia, z których drugie zakończy się niepowodzeniem:
- w trybie [AutoCommit=true] pierwsze wstawienie pozostaje (zostało zatwierdzone przez pierwsze AutoCommit);
- w trybie [AutoCommit=false] pierwsze wstawienie zostaje cofnięte;
W naszych przykładach za każdym razem, gdy wystąpi wyjątek, anulujemy transakcję w metodzie [doCatchException]:
private static void doCatchException(String title, Connection connexion, Throwable th) {
// wyświetlanie komunikatów o błędach
Static.show(title, Static.getErreursFromThrowable(th));
// anulowanie transakcji
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// wyświetlanie komunikatów o błędach
Static.show("Erreur lors de l'annulation de la transaction", Static.getErreursFromThrowable(e2));
}
}
6.4.8. Tworzenie zawartości tabeli produktów
Metoda [insert] tworzy zawartość tabeli:
// dodawanie produktów
public static void insert() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// nawiązanie połączenia
connexion = DriverManager.getConnection(url, user, passwd);
// rozpoczęcie transakcji
connexion.setAutoCommit(false);
// wypełnianie tabeli
ps = connexion.prepareStatement(insert);
for (int i = 0; i < 10; i++) {
// przygotowanie
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));
// wykonanie
ps.executeUpdate();
}
// zatwierdzenie transakcji
connexion.commit();
// powrót do trybu domyślnego
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// obsługa wyjątku
doCatchException("Les erreurs suivantes se sont produites à la création du contenu de la table", connexion, e1);
} finally {
// obsługa bloku finally
doFinally(null, null, connexion);
}
}
6.4.9. Wyświetlanie zawartości tabeli produktów
Metoda [select] wyświetla zawartość tabeli:
// lista produktów
private static void select() {
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// nawiązanie połączenia
connexion = DriverManager.getConnection(url, user, passwd);
// rozpoczęcie transakcji
connexion.setAutoCommit(false);
// odczyt tabeli [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)));
}
// zatwierdzenie transakcji
connexion.commit();
// powrót do trybu domyślnego
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// obsługa wyjątku
doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// przetwarzanie bloku finally
doFinally(null, null, connexion);
}
}
6.4.10. Aktualizacja zawartości tabeli
Metoda [update] aktualizuje niektóre produkty:
// aktualizacja niektórych produktów
public static void update() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// nawiązanie połączenia
connexion = DriverManager.getConnection(url, user, passwd);
// początek transakcji
connexion.setAutoCommit(false);
// aktualizacja tabeli
ps = connexion.prepareStatement(update);
// kategoria 1
ps.setInt(1, 1);
// wykonanie
ps.executeUpdate();
// zatwierdzenie transakcji
connexion.commit();
// powrót do trybu domyślnego
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// obsługa wyjątku
doCatchException("Les erreurs suivantes se sont produites à la mise à jour du contenu de la table", connexion, e1);
} finally {
// przetwarzanie bloku finally
doFinally(null, null, connexion);
}
}
6.4.11. Rola transakcji
Metoda [insert2] wstawia do tabeli dwa produkty o tym samym kluczu głównym, co jest niemożliwe. Ponieważ operacja odbywa się w ramach transakcji, pierwsze wstawienie zostanie cofnięte.
// dodanie 2 produktów o tych samych kluczach głównych
public static void insert2() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// nawiązanie połączenia
connexion = DriverManager.getConnection(url, user, passwd);
// rozpoczęcie transakcji
connexion.setAutoCommit(false);
// dodano 1 wiersz
ps = connexion.prepareStatement(insert2);
// wykonanie
ps.executeUpdate();
// dodano ten sam wiersz po raz drugi, a więc z tym samym kluczem głównym
// wstawianie musi się nie powieść i żaden z tych dwóch elementów nie może zostać wstawiony z powodu transakcji
ps.executeUpdate();
// zatwierdzenie transakcji
connexion.commit();
// powrót do trybu domyślnego
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// obsługujemy wyjątek
doCatchException("Les erreurs suivantes se sont produites lors de l'ajout", connexion, e1);
} finally {
// przetwarzanie bloku finally
doFinally(null, null, connexion);
}
}
6.4.12. Wyniki
Wyniki wykonania metody [main] są następujące:
6.5. Korzystanie ze źródła danych typu [DataSource]
Wrócimy do poprzedniej aplikacji, wykorzystując źródło danych typu [javax.sql.DataSource]:

Wykorzystamy źródło danych zaimplementowane przez klasę [org.apache.tomcat.jdbc.pool.DataSource]. Klasa ta korzysta z puli połączeń, czyli zbioru otwartych połączeń:
- gdy pula jest instancjonowana, otwierana jest określona liczba połączeń z bazą danych. Liczbę tę można skonfigurować;
- gdy kod Java otwiera połączenie, jest ono pobierane z puli;
- gdy kod Java zamyka połączenie, jest ono zwracane do puli;
W rezultacie połączenia są otwierane tylko raz, co poprawia wydajność dostępu do bazy danych. Źródło danych zostanie zdefiniowane w klasie konfiguracyjnej Spring
6.5.1. Projekt Eclipse
![]() |
Projekt jest projektem Maven zdefiniowanym przez następujący plik [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>
<!-- biblioteka 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>
- wiersze 21–25: zależność od Spring;
- wiersze 27–31: zależność od biblioteki dostarczającej źródło danych;
Klasa konfiguracyjna Springa [AppConfig] wygląda następująco:
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 {
// stałe
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() {
// źródło danych TomcatJdbc
DataSource dataSource = new DataSource();
// konfiguracja dostępu JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// początkowo otwarte połączenie
dataSource.setInitialSize(1);
// wynik
return dataSource;
}
}
- wiersze 11–19: stałe zdefiniowane wcześniej w [IntroJdbc01] zostały przeniesione do [AppConfig];
- wiersze 31–34: bean Spring definiujący źródło danych;
- wiersz 24: utworzenie źródła danych, które nie zostało jeszcze skonfigurowane;
- wiersze 26–29: informacje umożliwiające źródłu danych połączenie się z bazą danych;
- wiersz 31: tworzy pulę zawierającą 1 połączenie. W tym przypadku nie potrzebujemy więcej. Nigdy nie występuje wiele połączeń jednocześnie;
6.5.2. Klasa główna
Główna klasa [IntroJdbc02] wygląda następująco:
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 {
// źródło danych
private static DataSource dataSource;
public static void main(String[] args) {
// pobieranie kontekstu Spring
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
// pobieranie źródła danych
dataSource = ctx.getBean(DataSource.class);
// opróżnianie tabeli [PRODUITS]
delete();
...
// koniec
ctx.close();
System.out.println("Travail terminé");
}
// lista produktów
private static void select() {
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// nawiązanie połączenia
connexion = dataSource.getConnection();
// początek transakcji
connexion.setAutoCommit(false);
...
} catch (SQLException e1) {
// obsługa wyjątku
doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// przetwarzanie bloku finally
doFinally(null, null, connexion);
}
}
...
}
- wiersz 14: źródło danych. Należy zauważyć, że jest to typ [javax.sql.DataSource], który jest interfejsem;
- wiersz 18: instancjonowanie obiektów Spring;
- wiersz 20: uzyskanie odwołania do źródła danych. Należy zauważyć, że w żadnym momencie nie podano nazwy faktycznie używanej klasy. W tym przypadku nic nie wskazuje na to, że używana jest implementacja [TomcatJdbc];
- wiersz 36: uzyskanie otwartego połączenia;
- pozostała część kodu jest identyczna z kodem klasy [IntroJdbc01];
6.6. Conclusion
Więcej informacji na temat zarządzania bazami danych można znaleźć w dokumencie [Exploiter une base relationnelle avec l'écosystème Spring].












