Bazy danych w Javie
Jak zaznaczyliśmy wcześniej podczas omówienia podstawowych operacji na plikach jedną z potrzeb programów komputerowych jest utrwalanie przetworzonych danych. Do niektórych zastosowań wystarczające są pliki, czasem są wręcz wygodniejszą formą archiwizacji. Jednakże gdy danych jest dużo wygodniejszą opcją może być wykorzystanie bazy danych.
Architektura JDBC
Java Database Conectivity to interfejs programistyczny, który pozwala na połączenie aplikacji z bazą danych oraz wykonywanie do niej zapytań. Ważnym aspektem JDBC jest kompatybilność z większością istniejących na rynku baz danych. Aby jednak możliwa była współpraca z bazą danych należy użyć odpowiedniego sterownika. Zaletą tego rozwiązania jest z pewnością najlepsza możliwa optymalizacja w zależności od założeń konkretnej implementacji systemu zarządzania bazą danych.
Ta technologia wymaga od programisty znajomości języka SQL, gdyż z tego właśnie języka korzysta wysyłając zapytania. Ze względu na możliwość obsługi wielu SZBD nie było możliwe wprowadzenie wbudowanych zapytań pomijających SQL.
W tym artykule nie będziemy jednak omawiać języka SQL choć będzie on niezbędny w celu komunikacji z bazą danych.
Instalacja
Zgodnie z poprzednim akapitem możemy wybrać dowolny sterownik bazy danych. W naszym przykładzie użyjemy Bazy danych Derby rozwijaną przez Apache. Pierwszym naszym krokiem jest pobranie pakietu i rozpakowanie go w dowolnym miejscu na dysku. Np.
D:\Java\derby
Baza danych Derby może być używana w dwóch trybach.
- Osadzona w aplikacji – wtedy jest uruchamiana w tej samej maszynie wirtualnej jak aplikacja i zazwyczaj jest przez nią sterowana.
- Tryb serwerowy – zazwyczaj w celu połączenia wielu użytkowników w przez sieć. Jest uruchomiona na innej maszynie niż łączące się z nią aplikacje.
Ustawienie ścieżki
Podobnie jak w przypadku JDK możemy ustawić zmienną środowiskową korzystając z GUI Panelu Sterowania.
Innym sposobem jest wykonanie polecenia w konsoli. Ta wersja nie jest jednak stałym rozwiązaniem i zadziała tylko w trakcie jednej sesji.
set PATH=d:\Java\derby\bin;%PATH%
Jeszcze inną możliwością jest zlokalizowanie odpowiedniego folderu na dysku przy pomocy eksploratora Windows, a następnie na pasku wskazującym ścieżkę należy wpisać cmd, aby uruchomić konsolę od razu w odpowiedniej lokalizacji.
Utworzenie bazy danych Derby
Niezależnie od wybranego wyżej sposobu uruchamiamy konsolę, a następnie wpisujemy polecenie ij. Wynikiem będzie uruchomienie programu.

Utworzenie i połączenie z bazą danych
Kolejnym krokiem będzie utworzenie bazy danych. Do tego niezbędne będzie polecenie.
CONNECT 'jdbc:derby:d:\java\bd;create=true’;
Polecenie CONNECT ustanawia połączenie z bazą. Kolejne polecenie w pojedynczym nawiasie warto zapamiętać, gdyż będzie nam przydatne już w przypadku programowania aplikacji. Kolejne elementy polecenia to.
-
jdbc:derby: wskazanie sterownika dla JDBC.
- d:\java\bd ścieżka oraz nazwa bazy danych (nazwą jest samo bd). Jeśli podamy samą nazwę, to utworzymy bazę w aktualnie wskazywanym w konsoli folderze.
- create=true – zmienna, od której zależy czy w przypadku braku bazy danych w podanej lokalizacji tworzy nową, czy też nie.
Należy także pamiętać o zakończeniu polecenia średnikiem.
Utworzenie tabeli w bazie danych
Do utworzenia tabeli będzie niezbędne polecenie SQL.
1 2 3 |
CREATE TABLE Gory (ID INT PRIMARY KEY, Name VARCHAR(20)); |
Całe polecenie można zapisać w jednej linii jednakże trudniej wtedy zauważyć ewentualne błędy. W związku z tym warto podzielić je na kilka linii. Za każdym razem aby przejść do nowej należy użyć klawisza enter. Średnik na końcu oznacza koniec polecenia i tak też jest interpretowany.
W rezultacie powinniśmy otrzymać odpowiedź
0 wierszy wstawionych/zaktualizowanych/usuniętych
Wstawienie rekordów
W celu wstawienia rekordów do bazy danych ponownie musimy wykorzystać składnię SQL.
1 2 |
INSERT INTO Gory VALUES (1,'Tatry'),(2,'Bieszczady'),(3,'Karkonosze'); |
Rezultatem powinna być odpowiedź o wstawieniu 3 nowych rekordów.
3 wierszy wstawionych/zaktualizowanych/usuniętych
Wyświetlenie rekordów
1 |
SELECT * FROM Gory; |
Po wpisaniu tego polecenia powinniśmy uzyskać odpowiedź od bazy w postaci tabeli.

Aby opuścić ij należy użyć polecenia.
exit;
które przy okazji zamknie połączenie z bazą danych.
Powyżej pokazaliśmy proste użycie bazy danych przy pomocy interfejsu tekstowego. Istnieją również graficzne interfejsy do obsługi różnych baz danych.
Połączenie z bazą danych w Javie
W celu połączenia się z bazą danych będziemy potrzebować zwykłego projektu w Eclipse. Nowością będzie dodanie sterownika Derby. Aby to zrobić należy kliknąć PPM na nazwie projektu oraz po kolei wybrać.
Build Path → Configure Build Path
Następnie dodajemy sterownik do Classpath przy pomocy przycisku Add External Jar. Całą operację zatwierdzamy przyciskiem Apply and close.

Od tej chwili możemy łączyć się z bazą. Spójrzmy na poniższe polecenia.
1 2 3 4 |
final static String DB_URL = "jdbc:derby:D:\\Java\\db;create=true"; Connection conn = DriverManager.getConnection(DB_URL); ... conn.close(); |
Stała DB_URL to nic innego jak polecenie użyte wcześniej w ij. Jedyne na co należy zwrócić uwagę, to symbol zastępczy czyli podwójny znak \\.
Zapytania
Do przygotowania zapytania niezbędna będzie klasa Statement. Posiada ona metody takie jak.
- executeQuery – ta metoda zwraca listę rekordów opakowaną w klasie ResultSet. Używana do wykonywania poleceń SELECT.
- executeUpdate – Używana do wykonywania poleceń CREATE TABLE, DROP TABLE, ALTER TABLE, UPDATE, INSERT, DELETE.
Pobieranie danych
Zaczniemy od wyświetlenia aktualnej zawartości bazy danych. Jak pamiętam powyżej umieściliśmy tam 3 nazwy pasm górskich oraz przyporządkowaliśmy im identyfikatory. Aby uzyskać dostęp do danych najpierw musimy się połączyć z bazą. Kolejną czynnością jest przygotowanie zapytań oraz ich wykonanie.
1 2 |
Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(SQL); |
Wróćmy teraz do naszej bazy. Spróbujmy pobrać z niej całą zawartość.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class BD { final static String URL = "jdbc:derby:D:\\Java\\db;create=true"; public static void main(String[] args) { try { Connection conn = DriverManager.getConnection(URL); Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT * FROM Gory"); while(rs.next()) { System.out.println(rs.getInt(1) + " " + rs.getString(2)); } conn.close(); } catch ( SQLException e) { e.printStackTrace(); } } } |
Wynikiem uruchomienia powyższego kodu będzie.
1 Tatry
2 Bieszczady
3 Karkonosze
Jak widzimy połączenie oraz pobranie danych udało się. Uzyskaliśmy identyczny efekt jak powyżej.
Obiekt rs przechowuje wszystkie rekordy zwrócone z zapytania. Zachowanie to przypomina nieco kolekcję gdyż w rs możemy sprawdzić czy istnieje następny rekord metodą hasNext() oraz wskazać go metodą next(). Zwróćmy uwagę na to, że kolejne kolumny rekordu numerujemy od 1 tak jak pokazaliśmy to w pętli while.
Nasze zapytanie było proste i wykonaliśmy je tylko raz. W przypadku zapytania używanego wielokrotnie warto użyć obiektu klasy implementującej interfejs PreparedStatement.
Przykładem takiego polecenia może być dodanie WHERE. Spójrzmy na poniższy listing.
1 2 3 4 5 6 7 |
PreparedStatement ps = conn.prepareStatement("SELECT * FROM Gory WHERE id = ?"); ps.setInt(1, 2); ResultSet rs = ps.executeQuery(); while(rs.next()) { System.out.println(rs.getInt(1) + " " + rs.getString(2)); } |
Poniżej wyjaśnimy co oznacza symbol ? oraz metoda setInt().
Wstawianie danych
W naszej bazie brakuje jeszcze wielu polskich pasm górskich. Spróbujmy więc uzupełnić tę listę. W tym celu wykorzystamy wspomniany wcześniej interfejs PreparedStatement. Spójrzmy na poniższy listing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class BD { final static String URL = "jdbc:derby:D:\\Java\\db;create=true"; public static void main(String[] args) { try { Connection conn = DriverManager.getConnection(URL); PreparedStatement ps = conn.prepareStatement("INSERT INTO Gory VALUES (?,?)"); ps.setInt(1, 4); ps.setString(2, "Pieniny"); ps.executeUpdate(); conn.close(); } catch ( SQLException e) { e.printStackTrace(); } } } |
Po wpisaniu tego polecenia w narzędziu ij widać następujący wynik.

Zwróćmy uwagę na sam fragment związany z obiektem ps. Wewnątrz napisane jest zapytanie SQL. Najważniejszym elementem w nim są dwa znaki zapytania. Są to miejsca, w które możemy dynamicznie wstawić własną wartość. Ma to znaczenie np. przy danych pobieranych od użytkownika. Kolejne znaki zapytania należy zastąpić odpowiednią metodą set. Do tego niezbędna jest znajomość struktury bazy danych. Jak pamiętamy pierwszą kolumną jest identyfikator Id, a drugą nazwa czyli String.
W każdej z metod set (zależnie od typu zmiennej) pierwszy parametr stanowi miejsce do zastąpienia (znak zapytania, zaczynając liczenie od 1), a drugi wartość.
Przewijanie i aktualizowanie wyników zapytań
Czasami w przypadku przeglądania różnych zbiorów przez użytkownika wymagane jest cofnięcie aktualnego iteratora na poprzedni element. Niekiedy też użytkownik chciałby mieć możliwość na bieżąco aktualizowania danych. Opisywany wcześniej tryb nie pozwala na te operacje w związku z tym pokażemy w jaki sposób uruchomić te tryby.
UWAGA! Nie wszystkie sterowniki obsługują poniższe tryby połączenia z bazą danych. Przed wykonaniem poniższych poleceń zapoznaj się z dokumentacją, aby upewnić się czy sterownik zapewnia odpowiednie wsparcie. Niekiedy brak określonej funkcjonalności jest podyktowany przyczynami wydajnościowymi.
W przypadku obiektu Statement i PreparedStatement składnia wygląda następująco.
1 2 3 4 5 6 7 8 |
Statement st = conn.createStatement( ResultSet.TYPE_, ResultSet.CONCUR_); PreparedStatement ps = conn.prepareStatement( SQL, ResultSet.TYPE_, ResultSet.CONCUR_); |
Istnieją 3 typy parametru type.
- TYPE_FORWARD_ONLY – Domyślny tryb. Wyników nie da się przewijać do tyłu.
- TYPE_SCROLL_INSENSITIVE – Przewijanie danych możliwe, ale wyniki nie będą aktualizowane w przypadku zmian w bazie danych.
- TYPE_SCROLL_SENSITIVE – Przewijanie danych możliwe oraz wyniki są aktualizowane razem z bazą.
Drugi parametr można ustawić na 2 sposoby.
- CONCUR_READ_ONLY – zbiór wyników możemy tylko odczytać.
- CONCUR_UPDATABLE – zbiór wyników możemy na bieżąco aktualizować.
Nawigacja iteratora obiektu ResultSet
Obiekty ResultSet dostarczają szeregu przydanych do nawigacji po zbiorze metod. Poniżej wypiszemy je wraz z opisem.
Metoda | Opis |
---|---|
previous() | Ustawia kursor na poprzednim rekordzie |
next() | Ustawia kursor na następnym rekordzie |
absolute(int n) | Ustawia kursor na rekordzie n |
relative(int n) | przesuwa kursor o n pozycji. Może to być liczba ujemna (przesunięcie wstecz). |
first() | Ustawia kursor na pierwszym elemencie. |
last() | Ustawia kursor na ostatnim elemencie. |
Transakcje
Transakcje są to operacje atomowe czyli niepodzielne. W przypadku jakiegokolwiek niepowodzenia w trakcie jej wykonywania stan bazy danych jest przywracany do punktu wejściowego. Transakcje są konieczne gdy zachodzi potrzeba zachowania spójności danych. Najprostszym przykładem jest przelew bankowy. W przypadku jakiegoś błędu mogłoby się okazać, że pieniądze z pierwszego konta zostały pobrane, natomiast stan drugiego pozostał bez zmian. Zabezpieczenie musi istnieć też w drugą stronę, aby nie dało się mnożyć pieniędzy przez przerywanie transakcji w odpowiednim momencie.
Z transakcjami nieodłącznym elementem jest tryb automatycznego zatwierdzania (ang. auto commit). Należy go wyłączyć poleceniem.
1 |
conn.setAutoCommit(false); |
Po wykonaniu wszystkich niezbędnych operacji (na przykładzie przelewu bankowego zmiany stanu dwóch kont) należy wykonać ręcznie zatwierdzenie stanu poleceniem.
1 |
conn.commit(); |
W przypadku niepowodzenia którejś z operacji należy wykonać metodę rollback();
1 |
conn.rollback(); |