JDBC w praktyce - CB227567

advertisement
Technologia JDBC w praktyce
Cezary Bronny
MSUI II
Nr indeksu 227567
Spis treści
1.
Wstęp ........................................................................................... 3
2.
Sterowniki..................................................................................... 4
3.
Ustanawianie połączenia z bazą danych .......................................... 5
4.
Wykonywanie poleceń SQL............................................................. 7
4.1.
Wykonywanie polecenia select..................................................... 7
4.2.
Wykonywanie poleceń insert, update i delete ............................... 8
4.3.
Wykonywanie DDL...................................................................... 9
4.4.
Wykonywanie procedur bazodanowych ........................................ 9
5.
Zarządzanie transakcjami ..............................................................10
6.
Obsługiwanie wyjątków ................................................................12
7.
Bibliografia...................................................................................14
2
1. Wstęp
Java, jako uniwersalny język programowania, daje moŜliwość dostępu do baz
danych. Przenośność aplikacji bazodanowych tworzonych w Javie zapewnia
interfejs JDBC – opracowany przez firmę Sun Microsystems w 1996 roku.
UmoŜliwia on konstruowanie i wykonywanie poleceń SQL’owych z poziomu kodu
Javy. Dzięki JDBC aplikacje bazodanowe napisane w Javie są niezaleŜne od
sprzętu oraz stosowanej bazy danych (niezaleŜność od systemu operacyjnego
zapewnia sama Java). NaleŜy jednak zaznaczyć, iŜ nadal moŜliwe jest stworzenie
specyficznych wywołań lub uŜycie typów danych, występujących tylko w
przypadku konkretnej bazy danych.
Technologia JDBC znajduje zastosowanie między innymi w:
programach aplikacyjnych napisanych w Javie
apletach, czyli programach osadzonych na stronie WWW i uruchamianych
przez przeglądarkę internetową
serwletach
osadzonych
po
stronie
serwera
w
tzw.
kontenerach
serwletów/serwerach aplikacyjnych
procedurach i funkcjach napisanych przy uŜyciu języka Java i składowanych
w bazie danych
W niniejszej pracy przedstawiono praktyczne informacje dotyczące interfejsu
JDBC, tj. jak przygotować środowisko programistyczne do korzystania z
interfejsu, jak ustanawiać połączenie z bazą danych, jak wykonywać polecenia
SQL oraz w jaki sposób moŜna zarządzać transakcjami. Materiał zawiera równieŜ
opis wyjątków zgłaszanych przez interfejs.
3
2. Sterowniki
Fizycznie JDBC jest zbiorem klas i interfejsów, które umoŜliwiają dostęp do bazy
danych z programów napisanych w języku Java. Są one zawarte w pakietach
java.sql oraz javax.sql. Aby moŜliwe było ich uŜycie, najpierw naleŜy załadować
odpowiedni sterownik JDBC, którego klasy implementują interfejsy oraz
przedefiniowują klasy znajdujące się w tychŜe pakietach. Co waŜne, sterowniki
JDBC nie są uniwersalne – współpracują z jednym konkretnym źródłem danych.
Jeśli tym źródłem jest baza danych to sterownik jest zazwyczaj przygotowany
przez jej producenta (ale nie jest to regułą). Ze względu na fakt, Ŝe nie wszyscy
producenci systemów bazodanowych przygotowali implementacje swojego
interfejsu JDBC, a niektórzy przygotowali rozwiązania hybrydowe, moŜna
wyróŜnić cztery rodzaje sterowników. Są nimi:
JDBC-ODBC Bridge Driver – sterownik tego typu wykorzystuje sterownik ODBC
do komunikacji z bazą danych. Sterownik JDBC tego typu pełni jedynie rolę
pośrednika tłumaczącego kod napisany w Javie na język sterownika ODBC.
Native-API Partly Java Driver – sterownik korzysta z bibliotek napisanych w
innych językach. Dla Oracle, biblioteki kodu własnego mogłyby bazować na
bibliotekach OCI, które były pierwotnie zaprojektowane dla programistów
C/C++.
JDBC-ODBC Bridge po lewej i Native-API Partly Java Driver po prawej
4
Net – Protocoll All-Java Driver – ten sterownik komunikuje się z bazą danych
za pośrednictwem komponentów poprzez protokół sieciowy. Komponenty
pośrednie zapewniają dostęp do róŜnych baz danych. Sterowniki tego typu są
w całości napisane w Javie i nie wymagają instalacji dodatkowego
oprogramowania po stronie klienta.
Native – Protocoll All Java Driver – sterownik jest w całości napisany w Javie i
komunikuje
się
bezpośrednio
z
serwerem
bazodanowym
za
pomocą
protokołów sieciowych.
Native po lewej i Net – Protocoll All-Java Driver po prawej
3. Ustanawianie połączenia z bazą danych
Ustanawianie połączenia z bazą danych zawsze odbywa się w dwóch krokach.
Najpierw naleŜy załadować sterowniki JDBC a następnie nawiązać połączenie z
bazą. Krok pierwszy wykonujemy wywołując polecenie:
Class.forName("nazwa_sterownika");
W przypadku ładowania sterownika – mostu JDBC-ODBC, wykonamy:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
a w przypadku sterownika do bazy Oraclowej:
Class.forName("oracle.jdbc.driver.OracleDriver");
5
W następnym kroku moŜemy przejść do nawiązania połączenia z bazą danych. Do
tego celu moŜemy uŜyć metody getConnection z klasy DriverManager.
Parametrami metody są: URL do bazy danych oraz nazwa uŜytkownika i hasło.
Adres URL identyfikuje bazę danych w sposób specyficzny dla określonego
sterownika. RóŜne sterowniki mogą potrzebować róŜnych informacji zawartych w
URL do określenia hosta bazy danych. Adresy URL zazwyczaj zaczynają się od
jdbc:subprotokół:subnazwa.
Subprotokół
określa
nazwę
wykorzystywanego
sterownika, a subnazwa identyfikuje bazę danych. Na przykład, sterownik Oracle
JDBC-Thin wymaga URL w formie jdbc:oracle:thin:@dbhost:port:sid, a JDBCODBC Bridge uŜywa jdbc:odbc:datasourcename:odbcoptions. PoniŜszy fragment
kodu nawiązuje połączenie z bazą danych:
Class.forName(„sun.jdbc.odbc.JdbcOdbcDriver”);
Connection con;
con=DriverManager.getConnection("jdbc:odbc:bazaCB", "uzykownik", "haslo");
Innym sposobem na nawiązanie połączenia jest skorzystanie z obiektu
DataSource. Aby to zrobić, naleŜy najpierw zarejestrować go w usłudze
nazewniczej JNDI (czyli Java Naming and Directory Interface). Dzięki temu
aplikacja nie będzie musiała znać Ŝadnych szczegółów dotyczących sterownika jak
to miało miejsce w przypadku obiektu DriverManager. Wystarczy jej nazwa
logiczna, która zostanie odwzorowana dzięki usłudze JNDI w fizyczny obiekt
DataSource reprezentujący konkretne źródło danych. Dodatkowo DataSource
umoŜliwia utrzymanie tzw. puli połączeń do bazy. Mechanizm puli połączeń (ang.
connection pooling) moŜe mieć zasadniczy wpływ na wydajność aplikacji gdyŜ
ponownie wykorzystuje zwrócone wcześniej połączenia (połączenia nie są
zamykane lecz zwracane do puli). Zamiast za kaŜdym razem fizycznie otwierać
nowe połączenie, obiekt DataSource przekazuje po prostu referencję do
aktywnego, nawiązanego juŜ wcześniej połączenia. PoniŜej kod pozyskujący
połączenie z puli połączeń.
6
Context ctx = new InitialContext(); //inicjalizacja środowiska dla JNDI
DataSource ds;
ds = (DataSource) ctx.lookup("jdbc/bazaCB"); /* jdbc/bazaCB to logiczna nazwa
obiektu reprezentującego źródło danych, który
został wcześniej zarejestrowany w usłudze JNDI */
Connection con = ds.getConnection("cb227567", " haslo"); //pobranie połączenia
4. Wykonywanie poleceń SQL
Po uzyskaniu połączenia z bazą danych moŜna zacząć operować na bazie danych.
Interfejs Statement reprezentuje podstawowe medium, słuŜące do transmisji
wszelkich zleceń do bazy danych. Statement nie posiada samodzielnego
konstruktora – jest tworzony za pomocą metody createStatement wcześniej
pozyskanego obiektu Connection.
Statement stmt = con.createStatement();
4.1.
Wykonywanie polecenia select
Do wykonania polecenia select słuŜy metoda executeQuery obiektu Statement.
Wynikiem jej wykonania jest obiekt typu ResultSet, który jest listą zwróconych
rekordów.
ResultSet rs = stmt.executeQuery("SELECT * FROM KLIENCI");
UŜycie metody next obiektu ResultSet pozwala na przemieszczanie się pomiędzy
wierszami danych reprezentowanych przez ResultSet. Metoda ta w wyniku
wykonania zwraca wartość typu boolean w zaleŜności czy udało się jej wykonać
przejście do następnego rekordu. Odczytanie danych z bazy moŜe zatem
wyglądać następująco:
7
// deklaracja zmiennych
String piwo, bar;
float cena;
// wywołujemy zapytanie
ResultSet rs= stmt.executeQuery("SELECT nazwa_baru, nazwa_piwa, cena_piwa”
+ “ FROM PIWO_W_BARACH");
while( rs.next() ) {
// w petli przechodzimy po kolejnych rekordach uzyskanych z zapytania
bar = rs.getString("nazwa_baru"); // przypisujemy na zmienną bar wartość
z kolumny nazwa_baru
piwo = rs.getString("nazwa_piwa"); // podobnie jw
cena = rs.getFloat("cena_piwa");
//wypisanie informacji na standardowe wyjście
System.out.print(”W barze ” + bar + ” jest piwko ” + piwo);
System.out.println(” w cenie ” + cena + ” za kufel.”);
}
NaleŜy pamiętać, Ŝe obiekt ResultSet jest związany z obiektem Statement. Jeśli
zatem obiekt Statement zostanie zamknięty lub wykorzystany do obsługi innego
zapytania to powiązany z nim obiekt ResultSet równieŜ zostanie zamknięty.
4.2.
Wykonywanie poleceń insert, update i delete
Wykonanie operacji insert, update i delete jest bardzo podobne do select z
poprzedniego podrozdziału. RóŜnica polega na tym, Ŝe zamiast wywoływania
metody executeQuery na obiekcie Statement, wołamy executeUpdate. RównieŜ i
w tym przypadku parametrem metody jest polecenie SQL.
String sql = ”INSERT INTO PIWO_W_BARACH VALUES ('Bar u Ździśka',
'Dębowe Mocne', '2.8')”;
int insertedRows = stmt.executeUpdate(sql);
8
Zwracaną wartością jest w tym przypadku ilość rekordów które zostały
wstawione. W przypadku DELETE wartością zwróconą będzie ilość usuniętych
rekordów.
Natomiast wykonanie
UPDATE zwróci liczbę zmodyfikowanych
rekordów.
4.3.
Wykonywanie DDL
Polecenia DDL są instrukcjami SQL umoŜliwiającymi manipulowanie strukturami
przechowującymi dane (w odróŜnieniu od DML, które realizują operacje na
danych). NaleŜą do nich polecenia CREATE, ALTER oraz DROP. Zlecenie
wykonania ich przez serwer bazy danych zasadniczo nie róŜni się od wywołania
polecenia INSERT czy UPDATE. Realizujemy je np. tak:
Statement stmt = con.createStatement();
String sql = "drop table piwo_w_barach";
stmt.executeUpdate(sql);
4.4.
Wykonywanie procedur bazodanowych
Serwery bazodanowe często posiadają swój własny język programowania, który
umoŜliwia definiowanie tzw. procedur bazodanowych. Utworzone procedury mogą
być wywoływane przez zewnętrzne aplikacje. Jednym z przykładów jest język
PL/SQL dla bazy Oracle. Bazodanowe języki programowania są często lepiej
przystosowane do wykonania pewnych akcji na bazie danych. Interfejs JDBC
umoŜliwia wywoływanie takich procedur. Realizowane to jest przez obiekt
implementujący interfejs CallableStatement. Bazodanową procedurę o nagłówku:
uzupelnij_zapasy_piwa(id IN INTEGER, ile IN INTEGER, nazwa OUT VARCHAR2)
moŜna wywołać w następujący sposób:
CalableStatement ctmt = con.prepareCall("{call uzupelnij_zapasy_piwa (?,?,?)}");
// w miejsce znaków ? zostaną wstawione parametry dla wołanej procedury
9
ctmt.setInt(1, idZapasuPiwa); // pierwszym parametrem będzie wartość zmiennej
idZapasuPiwa
ctmt.setInt(2, dodatkowyZapasPiwa); // drugim dodatkowyZapasPiwa
ctmt.registerOutParameter(3, java.sql.Types.VARCHAR); // trzecim parametrem
procedury będzie parametr typu OUT VARCHAR
ctmt.execute();
System.out.println("Zwiększono zapas piwa " + ctmt.getString(3)); // pobranie
wartości parametru nr 3 i wyświetlenie informacji na standardowe wyjście
Metoda
prepareCall
obiektu
Connection
słuŜy
do
tworzenia
obiektów
CallableStatement. W miejsce znaków zapytania (parametr metody prepareCall)
wstawiane są określone później wartości. Do określania tych wartości słuŜą
metody setXXX obiektu CallableStatement, gdzie XXX to nazwa wstawianego
typu. I tak np. ctmt.setInt(1, idZapasuPiwa) wstawi pod pierwsze wystąpienie
znaku zapytania wartość zmiennej typu integer – idZapasuPiwa. Warto zwrócić
uwagę
na
metodę
registerOutParameter.
Ustawia
ona
typ
parametru
wyjściowego procedury bazodanowej (OUT). Po zleceniu systemowi zarządzania
bazą danych wykonania procedury (metoda execute), i faktycznym jej
wykonaniu, parametry wyjściowe procedury moŜna pobrać metodą getXXX (XXX
– nazwa typu) obiektu CallableStatement.
5. Zarządzanie transakcjami
W JDBC domyślnie kaŜda pojedyncza operacja jest transakcją i jest zatwierdzana
automatycznie (ang. autocommit) po wywołaniu metod execute/executeUpdate i
pomyślnym wykonaniu jej przez serwer bazy danych. Gdy chcemy Ŝeby ciąg jakiś
operacji wykonał się w całości albo wcale (np. przelew pieniędzy w banku z konta
na konto), tryb taki nie będzie odpowiedni. MoŜna go zmienić wywołując metodę
setAutoCommit obiektu klasy Connection z parametrem false. Do zatwierdzania
transakcji słuŜy metoda commit a do wycofań rollback. Przykład wykorzystania:
10
try {
// załoŜenie ze obiekt con klasy Connection został wcześniej zainicjalizowany
con.setAutoCommit(false); //wyłączenie automatycznego zatwierdzania transakcji
Statement stmt = con.createStatement();
stmt.executeUpdate("UPDATE MAGAZYN SET ILOŚĆ = (ILOŚĆ - 10) WHERE
PRODUCTID = 7"); // wykonanie modyfikacji danych tabeli MAGAZYN
stmt.executeUpdate("UPDATE WYSYŁKA SET WYSŁANE = (WYSŁANE + 10)
WHERE PRODUCTID = 7"); // wykonanie modyfikacji danych tabeli WYSYŁKA
con.commit(); // zatwierdzamy wykonanie transakcji
System.out.println("Operacja zamówienia zakończona sukcesem”);
}
catch(Exception e) { // wystąpił błąd
try {
con.rollback(); // wycofujemy transakcje
}
catch (SQLException ignored) {}
System transakcyjny zaimplementowany w JDBC moŜe działać w róŜnych
poziomach izolacji. Do wyboru mamy:
TRANSACTION_NONE
TRANSACTION_READ_COMMITTED
TRANSACTION_READ_UNCOMMITTED
TRANSACTION_REPEATABLE_READ
TRANSACTION_SERIALIZABLE
Odpowiedni poziom moŜemy ustawić za pomocą metody:
Connection.setTransactionIsolation (odpowiednia stała z listy powyŜej);
Warunkiem moŜliwości zastosowania wybranego poziomu izolacji jest konieczność
wsparcia przez system zarządzania bazą danych.
11
Czasami moŜe być przydatny rollback części transakcji a nie całej. Specyfikacja
JDBC w wersji 3.0 określa taką moŜliwość – nosi ona nazwę transaction
savepoints. Savepoint to nazwany punkt wewnątrz transakcji. Ustawia się go
metodą setSavepoints(nazwa_savepointu) naleŜącą do obiektu klasy Connection.
MoŜliwość cofnięcia transakcji do savepointu realizowane jest za pomocą metody
rollback(nazwa_savepointu).
….
con.setAutoCommit(false); //wyłączenie automatycznego zatwierdzania transakcji
Statement stmt = con.createStatement();
Savepoint save1 = con.setSavepoint("INSERT_POINT"); // ustawiamy savepoint
stmt.executeUpdate(“INSERT INTO ...”); //wykonujemy operacje na bazie danych
Savepoint save2 = con.setSavepoint("UPDATE_POINT"); // ustawiamy kolejny
savepoint
stmt.executeUpdate(“UPDATE ...”); //ponownie wykonujemy operacje na bazie
....
con.rollback(save2); // rollback do savepointa save2
....
con.commit(); // zatwierdzenie transakcji
Tak jak w poprzednim przykładzie dotyczącym izolacji transakji, i tu, zarówno
baza danych jak i sterowniki JDBC muszą wspierać savepointy.
6. Obsługiwanie wyjątków
W trakcie działania aplikacji bazodanowej mogą zaistnieć róŜnego rodzaju
sytuacje
powodujące,
Ŝe
określone
operacje
na
bazie
zakończą
się
niepowodzeniem (np. próba utworzenia tabeli o istniejącej juŜ nazwie albo próba
wstawienia nieunikatowej wartości do kolumny klucza głównego tabeli). Dlatego
waŜne jest, aby nasza aplikacja potrafiła obsłuŜyć tego typu zdarzenia. Pomocny
12
okazuje się mechanizm obsługi wyjątków. W takich przypadkach wykorzystujemy
klasę SQLException. Dziedziczy ona bezpośrednio z klasy Exception. Wyjątki
SQL’owe obsługujemy tak, jak wszystkie inne, z którymi spotkać moŜemy się w
Javie.
try {
// operacje na bazie danych
} catch (SQLException e) { // tu obsługujemy przechwycony wyjątek }
13
7. Bibliografia
JDBC Technology – http://java.sun.com/products/jdbc/index.jsp
Java Server Programming - Jason Hunter, William Crawford
Java Enterprise in a Nutshell, 3rd Edition - William Crawford, Jim Farley
Introduction to JDBC – http://www-db.stanford.edu/~ullman/fcdb/oracle/orjdbc.html
Wikipedia – http://pl.wikipedia.org
JDBC – Marek Misiowiec – http://stencel.mimuw.edu.pl/abwi/20011218.b.JDBC/
14
Download