JDBC

advertisement
JDBC
Agenda:
Wprowadzenie
JDBC VS ODBC
Fundament innego API
Zgodność z SQL
Sterowniki (rodzaje)
Użycie
Wersje
Połączenie
Zapytania
Procedury
Transakcje
Przykłady
Wprowadzenie
Java DataBase Connectivity (JDBC) jest interfejsem
programistycznym, skonstruowanym w i przeznaczonym dla języka
Java, który umożliwia ustandaryzowany dostęp do większości
obecnych
na
rynku
systemów
bazodanowych.
Wraz z powstaniem nowego, przenośnego, niezależnego od platformy
i architektury języka programowania pojawiła się idea skonstruowania
nowego interfejsu bazodanowego, który spełniałby podobne
wymagania jak sam język. Naturalną konsekwencją takiego
rozumowania było oderwanie się od już istniejących API, w
szczególności wprowadzonego przez Microsoft standardu Open
DataBase Connectivity (ODBC), i opracowanie nowej technologii,
wykonanej w pełni w Javie. Jakkolwiek nie oznacza to, że programiści
używający dotąd ODBC musieli przestawić się na zupełnie nowy tok
myślenia. Przeciwnie, JDBC funkcjonalnie przypominało technologię
ODBC, zwiększono jedynie możliwości interfejsu oraz przystosowano
go do odmiennej specyfiki języka Java.
JDBC VS ODBC
Stworzenie nowego standardu dostępu do systemów zarządzania
bazami danych (SZBD) w przeciwieństwie do zaadoptowania ODBC
na potrzeby Javy wynikało między innymi z następujących
spostrzeżeń:
ODBC było technologią stworzoną i stosowaną w środowiskach
powstałych w oparciu o języki C i C++. Aplikacje tworzone z ich
użyciem są zależne od systemu operacyjnego, co kłóci się z jednym z
fundamentów języka Java - przenośnością oprogramowania.
Przepisanie ODBC w Javie nie miało sensu, z uwagi na różnice
językowe (konstrukcyjne) pomiędzy Javą a C.
ODBC, w oczach twórców Javy, było zbyt skomplikowane. Nowo
powstały interfejs miał być znacznie prostszy w użyciu, a jednocześnie
bardziej funkcjonalny.
Fundament innego API
JDBC jest wykorzystywane nie tylko bezpośrednio, jako
zunifikowana technologia dostępu do dowolnej bazy danych, ale także
jako budulec dla aplikacji/interfejsów wyższego rzędu, które
umożliwiają dostęp do bazy danych z wyższego poziomu.
Przykładami takich przedsięwzięć są m.in:
SQLJ - SQL zaszyty w kodzie Javy. Kod ten jest następnie
preprocesowany, tak by wydobyć zeń właściwe komendy SQL, za
wykonanie
których
odpowiada
JDBC.
Zgodność z SQL
JDBC określa pewien poziom zgodności z dotychczasowymi
standardami SQL. Główne założenie mówi, iż każdy sterownik JDBC
musi odpowiadać co najmniej wersji ANSI SQL-92 standardu SQL.
Ponadto pomysłodawcy interfejsu poczynili inne założenia, które
spełniają kolejne wersje standardu JDBC:
Zapytania SQL przesyłane są do odpowiedniego SZBD bez względu
na możliwość ich realizacji. W przypadku, gdy dany SZBD nie potrafi
obsłużyć zlecenia przekazanego przez JDBC, aplikacja podnosi
określony wyjątek.
JDBC udostępnia informacje o SZBD i jego cechach szczególnych
(ustawieniach, możliwościach itd.). Informacje te przekazywane są
użytkownikowi w postaci tzw. metadanych.
Sterowniki
Sterownik JDBC jest zbiorem skompilowanych klas, które
implementują wszystkie interfejsy zawarte w java.sql oraz
przedefiniowują (ponownie implementują) pozostające tam klasy.
Implementacja bezpośrednio zależy od systemu zarządzania bazą
danych, z którą będzie się łączyć aplikacja wykorzystująca sterownik.
Stąd każdy sterownik JDBC służy do komunikacji z konkretną bazą
danych i nie jest wykorzystywany w przypadku baz innych
producentów.
Zastosowane rozwiązanie, choć podobne do technologii
używanych w przypadku C++ lub innych języków, w przypadku Javy
nabiera szerszego znaczenia. Ponieważ Java jest językiem w pełni
przenośnym, producenci sterowników muszą przygotowywać tylko
jeden pakiet dla każdej wersji standardu JDBC. Tym samym, w
odróżnieniu np. od wspomnianych interfejsów dla języka C++, z
jednego binarium korzystamy w systemie Linux i w Windows.
Rodzaje sterowników
1.
2.
Ponieważ nie wszyscy producenci systemów bazodanowych
przygotowali javowe implementacje standardu JDBC, a niektórzy
przygotowali rozwiązania hybrydowe, przyjęło się rozróżniać cztery
zasadnicze typy sterowników JDBC:
Mosty
JDBC-ODBC
(JDBC-ODBC
bridge)
korzystamy ze sterownika ODBC, z którym komunikuje się nasz
most. Zapytania formułowane w Javie są tłumaczone na język
sterownika ODBC. Wszelka komunikacja z bazą danych odbywa się
poprzez sterownik ODBC.
Java
do
API
SZBD
(Native-API
partly-Java)
sterowniki wykorzystują biblioteki napisane w C/C++. Ich
używanie jak w przypadku powyżej wymaga dodatkowego
oprogramowania.
Rodzaje sterowników cd.
3.
4.
Pośrednie
JDBC
(JDBC-Net
pure
Java)
sterownik JDBC komunikuje się z serwerem pośredniczącym, za
pomocą protokołu niezależnego od SZBD. Serwer tłumaczy
polecenia na protokól konkretnego SZBD i przesyła do niego
otrzymane zapytania. Serwer może pośredniczyć w wymianie
danych pomiędzy wieloma klientami i wieloma różnymi SZBD.
Tym samym jest to najbardziej ogólne i heterogeniczne rozwiązanie,
obciążone względami bezpieczeństwa.
Bezpośrednie JDBC (Native-protocol pure Java) sterownik JDBC komunikuje się bezpośrednio z bazą danych przy
pomocy jej protokołu sieciowego. Rozwiązanie najbardziej ogólne,
z powodzeniem stosowane w sieciach wewnętrznych.
Sterowniki cd.
Poniższy rysunek obrazuje wspomniane rozwiązania ujęte w
modelu warstwowym:
Użycie
Przed skorzystaniem z klas java.sql, należy pobrać odpowiedni
pakiet zawierający dany sterownik JDBC, a następnie umieścić go
(zgodnie z hierarchią katalogów zawartą w archiwum) w katalogu
widocznym dla maszyny wirtualnej Javy (zmienna CLASSPATH). Po
wykonaniu tych operacji sterownik JDBC jest gotów do pracy. Każda
aplikacja może go dynamicznie załadować. Do tego celu służy
polecenie:
Class.forName("pełna_nazwa_sterownika")
gdzie, w przypadku bazy Oracle, pełna_nazwa_sterownika, to
oracle.jdbc.driver.OracleDriver.
Sterownik w pełni zgodny ze standardem JDBC, powinien wtedy stworzyć
także instancje klasy. Ponieważ nie wszystkie sterowniki spełniają ten wymóg,
warto samodzielnie powołać do życia obiekt klasy:
Class.forName("oracle.jdbc.driver.OracleDriver").newInstance()
Inną metodą na załadowanie sterownika jest ustalenie własności jdbc.drivers na
nazwę ładowanej klasy:
java -Djdbc.drivers="oracle.jdbc.driver.OracleDriver" program
Wersje JDBC
JDBC 1.0
Pierwsza wersja standardu JDBC. Pojawiła się na rynku
wraz z wypuszczeniem JDK 1.0 (Java Development Kit). Zawierała
podstawowy zestaw narzędzi umożliwiających dostęp do bazy danych.
W pakiecie java.sql znalazły się następujące interfejsy:
Connection
DatabaseMetaData
Driver
DriverManager
ResultSet
ResultSetMetaData
Statement
CallableStatement
PreparedStatement
Types
Wersja JDBC
Standard JDBC 2.0 pojawił się wraz z Java SDK 1.2. Stanowi
on funkcjonalne rozwinięcie wersji pierwszej, tak by sprostać
wymaganiom stawianym przez pozostałe produkty Suna. Jego
powstanie było w szczególności związane z wprowadzeniem przez
Suna nowych technologii: Java Transaction Service (JTS), Java
Naming and Directory Interface (JNDI), JavaBeans, czy też Enterprise
JavaBeans (EJB). Do opublikowania nowej wersji JDBC przyczynił
się także rozwój obsługi standardów wielojęzycznych.
JDBC 2.0 podzielony jest na dwie części:
JDBC 2.0 core API - zawarta w java.sql - posiada pełny zestaw
mechanizmów dostępu do baz danych. Faktycznie składa się z
części JDBC 1.2 uzupełnionej o szereg nowych funkcji.
Połączenie
Klasa Connection odpowiada pojedynczemu połączeniu z wybranym
Systemem Zarządzania Bazą Danych.
Po załadowaniu sterownika JDBC, użytkownik może połączyć się z
bazą danych. Do tego celu służy metoda getConnection(...) z klasy
DriverManager. W wyniku jej wywołania:
Connection con =
DriverManager.getConnection (url, username, password)
dostajemy obiekt klasy Connection. Parametrami metody są:
url - np:
"jdbc:oracle:http://adres_hosta_z_baza_danych/nazwa_tabeli", czy też:
"jdbc:mysql://localhost:3306/bazka".
username - identyfikator użytkownika bazy danych
password - hasło użytkownika bazy danych
Zapytania
Klasa Statement reprezentuje medium, służące do transmisji
wszelkich zleceń do bazy danych (operacji SQL).
Obiekt klasy Statement tworzony jest w oparciu o wsześniej
ustanowione połączenie z bazą danych. Statement nie posiada
samodzielnego konstruktora, nowy obiekt jest zwracany przez metodę
createStatement():
Connection con = DriverManager.getConnection (...)
Statement stmt = con.createStatement();
Od tego momentu wszystkie operacje związane z bazą danych
wykonuje się poprzez utworzony obiekt. Oczywiście można utworzyć
więcej niż jeden obiekt klasy Statement. Co więcej, jest to jedyna
metoda, by złożyć kolejne zapytanie podczas przeglądania wyników
poprzedniego.
JDBC rozróżnia trzy odmienne kategorie zleceń do bazy danych.
Kategorie te reprezentuje wspomniana klasa Statement i jej dwie
podklasy: PreparedStatement oraz CallableStatement
Zapytania cd.
Do wykonywania operacji na bazie danych służą bezpośrednio trzy
metody klasy: executeQuery(query), executeUpdate(query), execute(query),
gdzie query jest treścią (String) zapytania. W szczególności:
executeQuery(query)
używana jest do składania zwykłych zapytań SQL rozpoczynających się od
słowa select. W wyniku wykonania metody otrzymujemy listę wierszy
opakowaną w obiekt klasy ResultSet.
executeUpdate(query)
używana jest do wykonywania operacji: insert, update i delete, a także
operacji DDL (Data Definition Language): create table, drop table, czy też
alter table.
W wyniku zwraca pojedynczą liczbę, określającą liczbę wierszy tabeli,
której dotyczyło zapytanie.
execute(query)
używana jest rzadko, wszędzie tam, gdzie w wyniku otrzymujemy więcej
niż jedną listę wierszy lub więcej niż jedną liczbę zmodyfikowanych
wierszy
Zapytania - ResultSet
Klasa ta reprezentuje podstawową strukturę danych wynikowych
dla zapytań SQL. Intuicyjnie kojarzona z bazodanowym kursorem
(iterator), udostępnia szereg metod pozwalających na przetwarzanie
otrzymanych danych. Pojedynczy element iteratora odpowiada
jednemu wierszowi wynikowej tabeli.
Obiekt klasy ResultSet otrzymujemy w wyniku wykonania
metody: executeQuery(). Rodzina metod getXXX (i) służy do
uzyskiwania wartości i-tej kolumny bieżącego wiersza wynikowej
tabeli - np. getInt(1) oznacza pytanie o wartość całkowitą znajdującą
się w pierwszej kolumnie aktualnego wiersza. Do następnego wiersza
przesuwamy się korzystając z metody next(), uprzednio sprawdziwszy,
czy w kursorze pozostały jeszcze wiersze (hasNext()).
ScrollableResultSet
Klasa rozszerza funkcjonalność ResultSet o możliwość elastycznego
poruszania się po otrzymanej liście wierszy oraz pozwala na modyfikacje tabeli
w trakcie przeglądania wyników zapytania.
O rodzaju zwracanego obiektu, a więc wyborze pomiędzy klasą ResultSet a
ScrollableResultSet decyduje metoda tworząca kontener Statement. Użycie
bezparametrowego wywołania createStatement () jest rozwiązaniem
standardowym. Natomiast zastosowanie createStatement (int jaki_rezultat, int
jaka_wspolbieznosc) tworzy kontener zdolny do tworzenia elastycznych
wyników.
Pierwszy argument decyduje o możliwości dwukierunkowego przewijania
kursora oraz reakcji na zmiany dokonane w bazie danych po pobraniu danych.
Dopuszczalne są stałe:
TYPE_FORWARD_ONLY
Klasyczne rozwiązanie, brak możliwości cofania kursora
TYPE_SCROLL_INSENSITIVE
Po kursorze możemy się dowolnie poruszać, włącznie z przemieszczaniem do
dowolnego wiersza. Zmiany zaistniałe w bazie danych po wykonaniu
zapytania nie wpływają na zawartość kursora.(TYPE_SCROLL_SENSITIVE)
ScrollableResultSet
Drugi argument metody określa poziom współbieżności. Możliwe są dwie
stałe:
CONCUR_READ_ONLY
Rozwiązanie klasyczne - działa w parze z opcją
TYPE_FORWARD_ONLY
CONCUR_UPDATABLE
Pozwala użytkownikowi na wykonywanie tzw. programowych
operacji zapisu/modyfikacji/usunięcia danych znajdujących się
bezpośrednio w bazie danych. Jest to istotne rozszerzenie w stosunku
do wersji 1.0 JDBC. Pozwala m.in. na obudowywanie kursorów
warstwą prezentacyjną (formatka).
Do programowych modyfikacji odpowiednich kolumn bazy danych
służą metody postaci updateXXX (pozycja, wartosc) oraz insertRow ()
i deleteRow ().
PrepareStatement
Dla usprawnienia procesu przesyłania danych wprowadzono
mechanizm wstępnego przetwarzania zapytań. Zapytanie, po
przesyłaniu do bazy danych, jest prekompilowane i od tego momentu
użytkownik nie musi go przesyłać po raz kolejny. Oczywiście
zastosowania tego mechanizmu ograniczają się do wąskiego zakresu
przypadków:
treść zapytania nie zmienia się w czasie - zapytanie jest wielokrotnie
ponawiane (procedura bezparametrowa)
zapytanie jest przetwarzane bardzo często, jego treść można rozsądnie
sparametryzować
W obu przypadkach warto rozważyć użycie osadzonych w SZBD,
prekompilowanych zapytań.
Parametry wejściowe procedur kodujemy jako znaki zapytania ("?").
Ustawienie odpowiednich wartości dla brakujących argumentów
odbywa się poprzez metody z rodziny setXXX (pozycja, wartosc),
gdzie pozycja jest kolejnym (licząc od lewej, począwszy od 1)
argumentem zapytania.
PrepareStatement - przykład
pstmt = con.prepareStatement (
"update nazwa_tabeli set kol_1 = ? and kol_2 = ?
where jakas_wartosc = ?"
);
pstmt.setString (1, "cos");
pstmt.setInt (2, 123);
pstmt.setInt (3, 8395);
pstmt.executeUpdate ();
Procedury
JDBC umożliwia korzystanie z funkcji skalarnych wbudowanych
w SZBD. Sterowniki spełniające warunki zgodności z JDBC (JDBC
Compliant) muszą zapewniać poprawność działania z funkcjami
skalarnymi, o ile odpowiadający im SZBD funkcje te udostępnia.
Z uwagi na różnorodność wywołań funkcji u różnych producentów baz
danych, JDBC wprowadziło tzw. sekwencje rozszerzające (escape
sequences) - składniowe ramy dla wywołań funkcji i procedur
składowanych, przekazywania daty, czasu itp. W szczególności,
wywołanie funkcji opatrzone jest następującą klauzulą:
{fn <nazwa_funkcji ()>}
np.
Select {fn concat (string, "napis")}
From...
Procedury składowane
Poza funkcjami skalarnymi, JDBC umożliwia wykorzystanie
procedur składowanych (stored procedures) - procedur i funkcji
zgromadzonych po stronie SZBD. Wołanie procedur oraz
przekazywanie parametrów określają reguły składniowe sekwencji
rozszerzających. Nośnikiem dla zapytań zawierających wywołania
procedur są obiekty klasy CallableStatement, stanowiącej rozszerzenie
klasy Statement. Po utworzeniu obiektu wspomnianej klasy należy
przygotować treść zapytania:
? = call nazwa_procedury ( ? ? )
Procedury składowane cd.
? = call nazwa_procedury ( ? ? )
Znaki zapytania odpowiadają zmiennym przekazywanym procedurze.
JDBC dopuszcza zmienne trzech typów:
IN
Wartości zmiennych przekazywane są procedurze. Do ustawiania argumentu
typu IN stosujemy metody postaci setXXX (pozycja, wartosc), gdzie
pozycja, określa do którego znaku "?" przyporządkowana będzie wartosc
OUT
Z argumentem wiążemy zmienną programu - po wykonaniu procedury
zmienna będzie miała odpowiednio zmodyfikowaną wartość. Do związania
zmiennej używa się polecenia: registerOutParameter (pozycja, typ), gdzie
pozycja jest określona jak dla argumentu typu IN, a typ odpowiada jednemu
z typów JDBC.
INOUT
Argument spełnia obie powyższe role. Dla argumentu określonego jako
INOUT musi określić wysyłaną wartość (setXXX (p, w)), jak i związać z
nim zmienną Javy (registerOutParameter (p, t))
Procedury składowe - przykład
CallableStatement cs =
con.prepareCall (" {? = call nazwa_procedury (? ?)} ");
cs.registerParameterOut (1, Types.INTEGER);
cs.setString (2, "arg_napisowy");
cs.setString (3, "2_arg_napisowy");
cs.registerParameterOut (3, Types.STRING);
cs.execute ();
int wynik = cs.getInt (1);
String arg_2 = cs.getString (3);
Transakcje
JDBC domyślnie ogranicza pojęcie transakcji do pojedynczej
operacji. Jej pomyślne wykonanie oznacza zatwierdzenie efektów
operacji - jest to tzw. tryb autocommit. Dokładniej, efekty operacji
zostają zatwierdzone dopiero po pobraniu ostatniego wiersza
wynikowego kursora (ResultSet) lub po jego zamknięciu.
Aby posłużyć się rozszerzonym pojęciem transakcji, należy wyłączyć
tryb samozatwierdzania, wywołując metodę klasy Connection:
con.setAutoCommit(false);
Zatwierdzenie ciągu operacji wykonanych od ostatniego zatwierdzania
transakcji odbywa sie poprzez wywołanie metody:
Connection.commit();
Transakcje wycofujemy wywołaniem:
Connection.rollback();
Przykład
Statement stm =
con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.
CONCUR_READ_ONLY);
String sql = „select * from tabela”;
ResultSet rs = stm.executeQuery(sql);
rs.afterLast();
while(rs.previous()){
System.out.println(„Wiek” + rs.getInt(„wiek”));
Przykład
Wybrane medody obiektu ResultSet:
rs.Next();
rs.previous();
rs.afterLast();
rs.beforeFirst();
rs.getXXX();
//numer argumentu zapytanie lub nazwa kolumny
rs.absolute(int a);
rs.relative(int a);
rs.first();
rs.last();
rs.updateXXX(„nazwa_kol”,wartosc);
rs.updateRow(); rs.insertRow();
rs.deleteRow(); rs.refreshRow();
Przykład
Aktualizacja za pomocą obiektu ResultSet:
String sql = „Select * from osoba”;
ResultSet rs = stm.executeQuery(sql);
rs.first();
String nazwisko = rs.getString(„Nazwisko”);
rs.updateString(„Nazwisko”, „Pan” + nazwisko);
rs.updateInt(„Wiek”, 22);
rs.updateRow();
Usuwanie za pomocą obiektu ResultSet:
rs.absolute(4);
rs.deleteRow();
Przykład
Wstawianie rekordu za pomocą ResultSet:
String sql = „select * from osoba”;
ResultSet rs = stm.executeQuery(sql);
Rs.moveToInsertRow();
rs.updateInt(„Wiek”, 34);
rs.updateString(„Programista Java”);
rs.insetRow();
Odświerzanie danych za pomocą obiektu ResultSet:
rs.absolute(5);
rs.refreshRow();
Modyfikacja a bezpieczeństowo transakcji:
String sql = „Select from osoba FOR UPDATE”;
Przykład
Batch update:
con.setAutoCommit(false);
try{
Statement stm = con.createStatement();
stm.addBatch(insert into osoba(imie,nazwisko) values(„Jan”,Kowalski”);
stm.addBatch(delete from osoba where nazwisko=‘Nowak’”);
stm.addBatch(update osoba set imie=„Witold” where nazwisko=‘Kowalski’”);
stm.executeBatch();
//stm.clearBatch();
}catch(BatchUpdateException e){
int[] count = e.getUpdateCounts();
}
Przykład
Metadane:
Statement stm = con.createStatement();
String sql = „select * from osoba”:
ResultSet rs = stm.executeQuery(sql);
ResultSetMetaData meta = rs.getMetaData();
int col = meta.getColumnCount();
for(int i = 0;i < col;i++){
System.out.println(meta.getColumnLabel()):
}
Przykład
CREATE TYPE OsobaProp(
waga Number,
Wiek Numer);
Struktury:
public class DaneOsoboweObj implements SQLData{
private String sql_type;
int waga;
int wiek;
DaneOsobowe(){}
DaneOsobowe(String sql_type, int waga, int wiek){
this.sql_type = sql_type; this.waga = waga; this.wiek=wiek; }
public String getSQLTypeName() throws SQLException{
return sql_type; }
public void readSQL(SQLInput stream, String typeName) thows SQLException{
sql_type = typeName; waga = stream.getInt(); wiek = stream.getInt(); }
public void writeSQL(SQLOutput stream) throws SQLException{
stream.writeInt(waga);
stream.writeInt(wiek); }
Przykład struktury cd.
Map map = con.getTypeMap();
map.put(„OsobaProp”,Class.forName(„DaneOsoboweObj”));
Statement stm = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
ResultSet rs = stm.executeQuery(„select Dane from Osoba where nazwisko = ‘Nowak’”);
rs.absolute(1);
DaneOsoboweObj daneOs = (DaneOsoboweObj)rs.getObject(„Dane”);
System.out.println(„Nazwisko” + daneOs.nazwisko);
Aktualizacja:
daneOs = new DaneOsoboweObj(„Adam”, „Mickiewicz”);
rs.exectueQuery(„Update Osoba set Dane = daneOs where nazwiko=„Nowak”);
stm.execute();
KONIEC
Download