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.

  1. Osadzona w aplikacji – wtedy jest uruchamiana w tej samej maszynie wirtualnej jak aplikacja i zazwyczaj jest przez nią sterowana.
  2. 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.

Baza danych Derby
Baza danych Derby

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.

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.

Rezultatem powinna być odpowiedź o wstawieniu 3 nowych rekordów.

3 wierszy wstawionych/zaktualizowanych/usuniętych

Wyświetlenie rekordów

Po wpisaniu tego polecenia powinniśmy uzyskać odpowiedź od bazy w postaci tabeli.

Rekordy w bazie danych
Rekordy w bazie danych

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.

Eclipse - build path
Eclipse – build path

Od tej chwili możemy łączyć się z bazą. Spójrzmy na poniższe polecenia.

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.

Wróćmy teraz do naszej bazy. Spróbujmy pobrać z niej całą zawartość.

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.

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.

Po wpisaniu tego polecenia w narzędziu ij widać następujący wynik.

Rekordy po uzupełnieniu bazy danych
Rekordy po uzupełnieniu bazy danych

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.

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.

MetodaOpis
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.

Po wykonaniu wszystkich niezbędnych operacji (na przykładzie przelewu bankowego zmiany stanu dwóch kont) należy wykonać ręcznie zatwierdzenie stanu poleceniem.

W przypadku niepowodzenia którejś z operacji należy wykonać metodę rollback();