r09-05.doc

(1259 KB) Pobierz
Szablon dla tlumaczy

W tym rozdziale:

·         Relacyjne bazy danych

·         Interfejs JDBC

·         Powtórne użycie obiektów bazy danych

·         Transakcje

·         Serwlet Księga gości

·         Zaawansowane techniki JDBC

·         Co dalej?

Rozdział 9.

Łączność z bazą danych

W dzisiejszych czasach trudno znaleźć profesjonalną witrynę, która nie ma powiązania z bazą danych. Administratorzy WWW korzystają z baz danych w wyszukiwarkach internetowych, aplikacjach sklepów internetowych i programów wysyłających wiadomości online. Współpraca aplikacji sieciowych z bazami danych ma swoją cenę: zorientowane bazodanowo witryny internetowe mogą być trudne do tworzenia i często powodują ograniczenie wydajności. Korzystanie z baz danych ma jednak więcej zalet niż wad co sprawia, że bazy danych coraz bardziej „sterują” siecią.

W niniejszym rozdziale przedstawiono relacyjne bazy danych, język zapytań SQL i interfejs JDBC. Serwlety, dzięki długiemu czasowi istnienia, oraz JDBC, dobrze zdefiniowany interfejs łączności z bazami danych, są skutecznym rozwiązaniem dla administratorów WWW pragnących wykorzystać bazy danych w swych witrynach. Wprawdzie w książce założono, że czytelnik zna Javę, ale ten rozdział zaczyna się krótkim kursem wprowadzającym interfejs programistyczny JDBC.

Największą zaletą serwletów jest ich czas istnienia (szeroko opisany w rozdziale 3,”Cykl Życia Serwletu”), który pozwala na zarządzanie pulą otwartych połączeń. Takie otwarte połączenie pozwala na szybszą komunikację aplikacji z bazą danych. Korzystając ze skryptów CGI musimy pamiętać, że przy każdym wywołaniu trzeba ponownie otwierać połączenie (co może zająć sporo czasu).

Przewagą serwletów nad skryptami CGI jest niezależność interfejsu JDBC od typów baz danych. Serwlet obsługujący np. bazę danych Sybase może korzystać z bazy Oracle po niewielkiej zmianie w pliku właściwości lub modyfikacji kilku linii (zakładając, że dostawca nie określił wywołania konkretnego typu bazy danych). Przykłady w tym rozdziale są napisane w ten sposób, by przedstawić sposób zapewnienia dostępu do różnorodnych baz danych, łącznie z bazami danych ODBC, takimi jak MS Access, Oracle, czy Sybase.

 

Serwlety w warstwie pośredniej

Warstwa pośrednia służy do połączenia aplikacji klienckich z aplikacjami serwerowymi (np. aplety z bazami danych). Gromadzi głównie serwlety, które wykorzystują bazy danych.

Powinniśmy umieścić warstwę pośrednią pomiędzy aplikacją klienta a naszym krańcowym źródłem danych, ponieważ oprogramowanie warstwy pośredniej (nazywane middleware) zawiera logikę biznesową. Logika biznesowa oddziela skomplikowane zadania niskiego poziomu (takie jak aktualizacja tabel baz danych) od zadań wysokiego poziomu (np. składanie zamówienia), co sprawia, że operacja wykonania zamówienia jest łatwiejsza i bezpieczniejsza.

Aplikacja klienta, składająca zamówienia bez użycia middleware, musi łączyć się bezpośrednio z serwerem bazy danych, który gromadzi zapisy rozkazów i adekwatnie do nich zmienia pola bazy.

Jakakolwiek zmiana w serwerze (przeniesienie do innej maszyny, zmiana struktury tabel bazy danych, itp.) może spowodować przerwanie połączenia z aplikacją klienta. Jeśli dokonamy zmiany w kliencie (zamierzonej lub przypadkowej), baza danych może błędnie zapisać informacje o zamówieniach złożonych za pośrednictwem aplikacji klienta.

Do Middleware przesyłane są informacje o zamówieniu (np. nazwisko, adres, towar, ilość i numer karty kredytowej) i za pomocą tych informacji dokonywana jest weryfikacja użytkownika składającego zamówienie (np. sprawdzana jest ważność karty kredytowej). Jeśli informacje są prawdziwe — zostaną wprowadzone do bazy danych. Jeśli baza danych ulegnie zmianie, middleware aktualizuje się bez konieczności modyfikacji aplikacji klienta. Nawet, gdy zamówienia bazy danych są tymczasowo zapisane w postaci pliku jednorodnego, middleware może przekazać klientowi informacje odczytane z takiego pliku.

Korzystając z middleware można podnieść wydajność systemu poprzez rozproszenie procesu zapisywania informacji w bazie danych na kilka wewnętrznych serwerów. Middleware potrafi wykorzystać przepustowość sieci: zamiast powolnego połączenia klient-serwer, klient może przekazać wszystkie informacje middleware, który użyje szybkiego połączenia sieciowego z puli połączeń i skomunikuje się z bazą danych.

Warstwy pośrednie w sieci są często tworzone przy użyciu serwletów. Serwlety dostarczają odpowiednie sposoby łączenia klientów z wewnętrznym serwerem za pomocą apletów lub formularzy HTML. Klient przesyła żądania do serwletu za pomocą HTTP, a logika biznesowa w serwlecie przekazuje żądanie do wewnętrznego serwera (więcej informacji na temat komunikacji aplet-serwlet znajduje się w rozdziale 10, „Komunikacja aplet-serwlet”).

Serwlety często używają dodatkowej warstwy pośredniej poza serwerem sieciowym (takiej jak Enterprise Java Beans) do łączenia się z bazą danych. Jeśli przeglądarka prześle formularz HTML z zamówieniem do serwletu, to może on przekształcić tą informację i wykonać wywołanie do EJB innej maszyny odpowiedzialnej za obsługę wszystkich zamówień — pochodzących zarówno z serwletów, jak i niezależnych programów. W omówionych przypadkach mamy do czynienia ze strukturą czterowarstwową.

 

Relacyjne bazy danych

We wcześniejszych przykładach omówiono serwlety, które przechowywały dane w postaci pliku umieszczonego na lokalnym dysku. Użycie jednorodnego pliku jest świetnym rozwiązaniem dla małych ilości informacji, ale można szybko stracić nad nim kontrolę. W miarę wzrostu ilości danych umieszczonych w pliku dostęp do nich staje się coraz wolniejszy. Wyszukanie odpowiednich informacji może stać się nie lada wyzwaniem: wyobraźmy sobie wyszukiwanie w pliku tekstowym nazwisk, miast i adresów e-mail wszystkich naszych klientów. Takie podejście jest dobre dla nowo otwartej firmy, ale nie sprawdza się w przypadku korporacji obsługującej setki tysięcy klientów. Wyszukanie na przykład listy klientów z Bostonu, których adresy e-mail są zakończone aol.com z dużego pliku tekstowego stanowiłoby ogromny problem.

Jednym z najlepszych rozwiązań tego problemu jest skorzystanie z systemu zarządzania relacyjnymi bazami danych (RDBMS). W najprostszej postaci RDBMS umieszcza dane w tabelach. Tabele te podzielone są na wiersze i kolumny podobnie do tabel arkusza kalkulacyjnego. Pomiędzy poszczególnymi wierszami i kolumnami jednej tabeli może zachodzić określona relacja (czyli powiązanie) — stąd termin relacyjne.

Jedna tabela relacyjnej bazy danych może zawierać informacje o klientach, druga o ich zamówieniach, a trzecia o przedmiotach zamówienia. Poprzez włączenie unikatowych identyfikatorów (powiedzmy, takich jak numery klientów i zamówień), te trzy tabele mogą być wzajemnie powiązane. Rysunek 9.1 pokazuje, w jaki sposób wygląda ta relacja.

Dane w tabeli mogą być odczytywane, aktualizowane, dopisywane i kasowane za pomocą poleceń języka SQL. JDBC API języka Java wprowadzony w JDK 1.1 używa pewnej odmiany SQL znanej jako ANSI SQL-2 Entry Level. W przeciwieństwie do większości języków programowania SQL jest językiem deklaracyjnym: interpreter SQL wykonuje polecenia wpisywane przez użytkownika. Inne języki programowania takie jak C/C++, Java wymagają użycia procedur, czyli określenia kolejnych kroków wykonania pewnego zadania. SQL nie jest szczególnie złożony, ale jest zbyt szerokim tematem do opisu w ramach tej książki. Więcej informacji na temat relacyjnych baz danych i języka SQL znajduje się w książkach: SQL dla Opornych Allena Tayllora i SQL in a Nutshell Kevina i Daniela Kline.

Rysunek 9.1. Powiązane tabele

Najprostszym i najbardziej pospolitym wyrażeniem SQL jest SELECT, które przeszukuje bazę danych i zwraca zestaw wierszy pasujących do kryterium. Na przykład, poniższe wyrażenie zaznacza wszystkie wiersze tabeli KLIENCI:

SELECT * FROM KLIENCI

Słowa kluczowe SQL takie jak SELECT i FROM oraz obiekty takie jak KLIENCI nie są wrażliwe na wielkość liter. Uruchomienie interpretera SQL SQL*PLUS dla Oracle powinno wygenerować na wyjściu:

KLIENT_ID   NAZWISKO         TELEFON

---------------------------------------------------

1      Bob Copier      617-555-1212

2      Janet Stapler   617 555-1213

3      Joel Laptop     507 555-7171

4      Larry Coffee    212 555-6225

Bardziej zaawansowane wyrażenia mogłyby ograniczać szukanie do wybranych kolumn albo według określonych kryteriów, np.:

SELECT ZAMÓWIENIE_ID, KLIENT_ID, SUMA FROM ZAMÓWIENIA

WHERE ZAMÓWIENIE_ID = 4

Za pomocą tego wyrażenia zaznaczono kolumny ZAMÓWIENIE_ID, KLIENT_ID i SUMA ze wszystkich rekordów, gdzie w polu ZAMÓWIENIE_ID jest wartość 4. Oto przykładowy rezultat:

ZAMÓWIENIE_ID  KLIENT_ID  SUMA

------------------------------------

   4          1         72.19

Instrukcja SELECT może również łączyć kilka tabel na podstawie zawartości poszczególnych pól. Może to być związek jeden-do-jednego albo częściej używany jeden-do-wielu, np. jeden klient do kilku zamówień:

SELECT KLIENCI.NAZWISKO, ZAMÓWIENIA.SUMA FROM KLIENCI, ZAMÓWIENIA

WHERE ZAMÓWIENIA.KLIENT_ID = KLIENCI.KLIENT_ID AND ZAMÓWIENIA.ZAMÓWIENIE_ID = 4

Powyższa instrukcja spaja tabelę KLIENCI z tabelą ZAMÓWIENIA za pomocą pola KLIENT_ID. Należy zauważyć, że obie tabele posiadają to pole. Zapytanie zwraca informacje z dwóch tabel: nazwę użytkownika, który wykonał zamówienie nr 4 i całościowy koszt zamówienia. Oto przykładowy wynik tej operacji:

NAZWISKO        SUMA

-------------------------------------

Bob Copier        72,19

Język SQL jest również używany do aktualizacji baz danych. Na przykład:

INSERT INTO KLIENCI (KLIENT_ID, NAZWISKO, TELEFON)

   VALUES(5, "Bob Smith", "555 123-3456")

UPDATE KLIENCI SET NAZWISKO = "Robert Copier" WHERE KLIENT_ID = 1

DELETE FROM KLIENCI WHERE KLIENT_ID = 2

Pierwsza instrukcja tworzy nowy rekord w tabeli KLIENCI, zapełniając pola KLIENT_ID i TELEFON pewnymi wartościami. Druga aktualizuje istniejący rekord, zmieniając pole NAZWISKO określonego użytkownika. Ostatnia kasuje wszystkie rekordy, gdzie KLIENT_ID = 2. Należy ostrożnie posługiwać się tymi instrukcjami, a w szczególności instrukcją DELETE. Użycie tej instrukcji bez warunku WHERE usunie wszystkie rekordy z tabeli!

JDBC API

Wcześniej zakładaliśmy, że czytelnik posiada ogólną wiedze dotyczącą Java API. Ponieważ nawet wprawieni programiści Javy mogą mieć małe doświadczenie z bazami danych, ten podpunkt wprowadza JDBC na podstawie najpopularniejszej wersji JDBC 1.2. Pod koniec rozdziału dodano fragment na temat JDBC 2.0.

Jeśli czytelnik po raz pierwszy ma do czynienia z bazami danych, powinien przeczytać książki o ogólnych założeniach baz danych i JDBC, takie jak: Database Programming with JDBC and Java George'a Reese'a i JDBC Database Access with Java Grahama Hamiltona, Ricka Cattela, Maydene Fisher. Krótki przegląd znajduje się w Java Enterprise in a Nutshel Davida Flangana. Oficjalna specyfikacja JDBC znajduje się na stronie http://java.sun.com/products/jdbc

JDBC jest interfejsem programowym poziomu SQL, który pozwala wykonywać instrukcje SQL i odczytywać otrzymane wyniki. API jest zbiorem interfejsów i klas zaprojektowanym tak, aby możliwa była współpraca z jakąkolwiek bazą danych. Rysunek 9.2 pokazuje schemat struktury JDBC.

Rysunek 9.2. Java i baza danych

Do Korekty merytorycznej: proszę o uzupełnienie tłumaczenia rysunków

Sterowniki JDBC

JDBC API, znajdujący się w pakiecie java.sql zawiera zestaw klas służących do implementacji określonych interfejsów. Większa część API jest rozprowadzana jako klasy interfejsów neutralnych dla baz danych, a współpracę z konkretnymi bazami danych zapewniają interfejsy dostarczone przez producentów baz danych.

Indywidualny system baz danych jest dostępny poprzez sterownik JDBC, który implementuje interfejs java.sql.Driver. Sterowniki istnieją prawie dla wszystkich popularnych systemów RDBMS, ale nie wszystkie są dostępne za darmo. Firma Sun dodaje darmowy sterownik wykorzystujący technikę pomostową JDBC-ODBC w JDK, aby umożliwić dostęp do danych z baz standardu ODBC, takich jak baza danych Microsoft Access. Jest to bardzo prosty sterownik, którego można używać w bardzo prostych aplikacjach. Twórcy serwletów powinni w szczególności zwrócić uwagę na to ostrzeżenie, ponieważ jakikolwiek problem w treści kodu własnego sterownika JDBC-ODBC może zniszczyć cały serwer.

Sterowniki JDBC są dostępne dla większości platform baz danych tworzonych przez wielu producentów. Istnieją cztery kategorie sterownika:

·         Typ 1: JDBC-ODBC Bridge Driver — sterowniki typu 1 używają technologii pomostowej do łączenia klienta Javy i serwisu bazy danych ODBC.JDBC-ODBC dostarczony z JDK jest najpopularniejszym sterownikiem tego typu. Te sterowniki są implementowane przy użyciu kodu własnego i wymagają instalacji dodatkowego oprogramowania po stronie klienta.

·         Typ 2: Native-API Partly Java Driver — sterowniki typu 2 są napisane w Javie, ale korzystają 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++. Ponieważ sterowniki typu 2 są zaimplementowane za pomocą kodu własnego, zazwyczaj zapewniają lepszą wydajność niż ich wszystkie odpowiedniki w całości napisane w Javie. Użycie tych sterowników może być jednak ryzykowne: błąd w sekcji kodu własnego sterownika może zniszczyć cały serwer. Sterowniki tego typu wymagają instalacji dodatkowego oprogramowania po stronie klienta.

·         Typ 3: Net – Protocoll All-Java Driver — te sterowniki komunikują się z bazami danych za pośrednictwem komponentów pośrednich (ang. middleware) 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, dzięki czemu są wygodnym i bezpiecznym narzędziem zapewniającym apletom i serwletom dostęp do baz danych.

·         Typ 4: Native — Protocoll All Java Driver — sterowniki tego typu są napisane w całości w Javie i komunikują się bezpośrednio z maszyną bazodanową (ang. database engine) za pomocą protokołów sieciowych. Te sterowniki mają bezpośredni dostęp do baz danych bez dodatkowego oprogramowania.

Wykaz obecnie dostępnych sterowników JDBC znajduje się na stronie http://industry.java.sun.com/products/jdbc/drivers.

Połączenie z bazą danych

Pierwszym krokiem, jaki trzeba wykonać, aby użyć sterownika JDBC do połączenia z bazą danych jest załadowanie określonej klasy sterownika do aplikacji JVM (Java Virtual Machine). Dzięki temu sterownik będzie dostępny przy otwieraniu połączenia z bazą danych. Prostym sposobem załadowania klasy sterownika jest użycie metody Class.forName():

Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

Załadowany sterownik sam rejestruje się wewnątrz klasy java.sql.DriverManager, jako dostępny sterownik bazy danych.

Następnym krokiem jest użycie klasy DriverManager do otwarcia połączenia z bazą danych, gdzie za pomocą URL określa się żądaną bazę danych. Metodą używaną do otwierania połączenia jest DriverManager.getConnection, która zwraca klasę implementującą interfejs java.sql.Connection:

Connection con =

      DriverManager.getConnecton("jdbc:odbc:somedb","user","passwd");

Podany 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 JDBC-ODBC Bridge używa jdbc:odbc:datasourcename:odbcoptions.

Podczas wywołania metody getConnection(), obiekt DriverManager rozpoznaje odpowiedni sterownik za pomocą URL przeszukując bazę dostępnych sterowników. Jeśli wymagany sterownik istnieje w bazie, zostanie użyty do stworzenia obiektu Connection. Poniżej znajduje się fragment kodu, jakiego można użyć w serwlecie do ładowania sterownika bazy danych JDBC-ODBC Bridge i realizacji połączenia:

Connection con = null;

try{

    // ładujemy (i w ten sposób rejestrujemy) sterownik

    //możliwość wystąpienia wyjątku ClassNotFoundException

Class.forName(„sun.jdbc.odbc.JdbcOdbcDriver”);

    //otwieramy połączenie z bazą danych

// możliwość wystąpienia wyjątku SQLException

   con=DriverManager.getConnection("jdbc:odbc:somedb", "user", "passwd");

// reszta kodu źródłowego ma tu swoje miejsce

}

catch(ClassNotFoundException e){

   // obsługa wyjątku ClassNotFoundException

}

catch(SQLException e){

// obsługa wyjątku SQLException

}

finally {

// zamknięcie połączenia z bazą danych

try{

   if (con!=null) con.close();

}

catch (SQLException ignored) {}

}

 

Właściwie dostępne są trzy formy metody getConnection(). Najprostszą z nich jest ta, która pobiera tylko adres URL: getConnection (String url). Ta metoda ma zastosowanie w przypadku, gdy nie przewidziano konieczności zalogowania się (podania nazwy użytkownika i hasła) lub umieszczenia informacji logujących w adresie URL. Istnieje jeszcze jedna forma, która pobiera adres URL i obiekt Properties: getConnection(String url, Properties props). Ta metoda zapewnia największą elastyczność. Obiekt Properties (tablica mieszająca zawierająca klucze i wartości typu String) zawiera standardowo nazwę użytkownika i hasło, a także dodatkowe informacje przekazywane odpowiedniemu sterownikowi bazy danych. Na przykład, niektóre sterowniki respektują właściwość cacherows, która określa jak dużo wierszy ma trafić do pamięci cache w jednostce czasu. Używanie tej metody ułatwia otwarcie połączenia z bazą danych w oparciu o plik z rozszerzeniem .properties.

Sterownik, adres URL i potrzebne do zalogowania się dane mogą być określone w następującym pliku sql.properties. Format nazwa=wartość jest formatem standardowym w plikach właściwości Javy:

connection.driver=sun.jdbc.odbc.JdbcOdbcDriver

connection.url=jdbc:odbc:somedb

user=user

password=passwd

Za pomocą kodu przedstawionego w przykładzie 9.1 otwiera się połączenie z bazą danych używając wartości zgromadzonych wewnątrz pliku sql.properties. Należy zauważyć, że nazwa użytkownika i hasło to informacja standardowo wymagana do zalogowania się, natomiast właściwości connection.driver i connection.url są specjalnymi nazwami użytymi w poniższym kodzie do rozpoznania sterownika i odpowiadającego mu URL, a wszelkie dodatkowe własności będą przekazane do wybranego sterownika.

Przykład 9.1.

Użycie odpowiedniego pliku do otwarcia połączenia bazodanowego.

// pobierz właściwości połączenia bazodanowego

Properties props = new Properties();

InputStream in = new FileInputStream("sql.properties");

props.load(in);

in.close(); ta instrukcja powinna znaleźć się w bloku finally

// załadowanie sterownika

Class.forName(props.getProperty("connection.driver");

//otwarcie połączenia

con = DriverManager.getConnection("connection.url"),props);

Utworzony obiekt Properties jest wypełniany wartościami odczytanymi z pliku sql.properties, a następnie zawarte w tym pliku właściwości są użyte do otwarcia połączenia z bazą danych. Wykorzystanie pliku właściwości pozwala na zmianę wszystkich informacji o połączeniach z bazą danych bez zbędnego przekompilowania kodu Javy.

Otwieranie połączenia z serwletu

Serwlet może użyć przedstawionego powyżej sposobu ładowania informacji o połączeniu z bazą danych z pliku właściwości znajdującego się w katalogu WEB-INF. Metoda getResourceAsStream() pobiera zawartość tego pliku:

Properties props = new Properties();

InputStream in = getServletContext().getResourceAsStream(

                               "/WEB-INF/sql.properities");

props.load(in);

in.close();

Ponieważ serwlety są najczęściej wdrażane przy użyciu graficznych narzędzi, możemy użyć tych samych narzędzi do skonfigurowania bazy danych. Jednym ze sposobów osiągnięcia tego celu jest umieszczenie połączeń z bazą danych wewnątrz serwera JNDI, gdzie połączenia są rozpoznawane przez serwlety za pomocą określonych nazw. Ten sposób jest wykorzystywany przez serwlety pracujące wewnątrz serwerów J2EE. Rozdział 12 omawia szczegółowo to zagadnienie. Innym zadaniem, które nie wymaga serwera JNDI, a wykorzystuje graficzne narzędzia rozmieszczenia, jest użycie parametrów inicjacji kontekstu...

Zgłoś jeśli naruszono regulamin