Dostęp do baz danych z aplikacji J2EE

Transkrypt

Dostęp do baz danych z aplikacji J2EE
47
Dostęp do baz danych z aplikacji J2EE
Marek Wojciechowski
[email protected]
http://www.cs.put.poznan.pl/mwojciechowski/
48
Plan rozdziału
• Źródła danych w JDBC
• Java Naming and Directory Interface
• Transakcje na platformie J2EE
49
Bazy danych a aplikacje J2EE
•
W praktyce większość aplikacji J2EE korzysta z baz danych
– Bazy danych to podstawowe miejsce trwałego przechowywania
danych
– Serwlety, JSP, EJB mogą odwoływać się do baz danych
•
Wymagania stawiane mechanizmom komunikacji z bazami
danych na platformie J2EE:
– przenaszalność – przystosowanie do działania na różnych
serwerach aplikacji, łatwość instalacji
– efektywność – buforowanie połączeń (connection pooling/caching)
– funkcjonalność – obsługa transakcji rozproszonych
•
Komunikacja z bazami danych w J2EE oparta o standardy:
– JDBC 2.0 – mechanizm źródeł danych, efektywność
– JTA – zaawansowane mechanizmy transakcyjne (implementacja
standardu XA dla aplikacji języka Java)
50
Nieprzenaszalny kod – JDBC 1.0
• Klasa sterownika JDBC i adres JDBC URL bazy danych
zaszyte w kodzie aplikacji
// rejestracja sterownika Oracle JDBC
DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
// otwarcie połączenia z bazą poprzez DriverManager
Connection conn = DriverManager.getConnection(
"jdbc:oracle:thin:@host1:1521:orcl", "scott","tiger");
Statement stmt = conn.createStatement();
try {
ResultSet rset = stmt.executeQuery("SELECT ename, sal FROM emp");
while (rset.next()) {
String ename
= rset.getString(1);
int sal = rset.getInt(2);
}
} catch (SQLException e) {…}
stmt.close(); conn.close();
51
Źródła danych – od JDBC 2.0
• Obiekty implementujące interfejs javax.sql.DataSource
• Przenaszalny, niezależny od dostawcy mechanizm
uzyskiwania połączeń z bazą danych
– Uzyskane połączenia są normalnymi połączeniami JDBC
(w aplikacji wykorzystywane na tej samej zasadzie co przy
korzystaniu z DriverManager)
– Źródła danych mogą oferować dodatkową funkcjonalność:
• Buforowanie połączeń (pooling/caching)
• Wsparcie dla transakcji rozproszonych
• Typowo definiowane poza aplikacją, dostępne przez JNDI
• Od JDBC 2.0 preferowany sposób uzyskiwania połączeń
z bazami danych
• Szczególnie zalecane dla aplikacji J2EE
52
Źródła danych – Przykład
// uzyskanie kontekstu nazw
Context ic = new InitialContext();
// wyszukanie zródła danych przez jego logiczną nazwę (JNDI)
DataSource ds = (DataSource) ic.lookup("jdbc/OracleDS");
// uzyskanie połączenia z bazą danych
Connection conn = ds.getConnection();
Od momentu uzyskania
połączenia aplikacja
korzysta z niego
w sposób tradycyjny
Statement stmt = conn.createStatement();
try {
ResultSet rset = stmt.executeQuery("SELECT ename, sal FROM emp");
while (rset.next()) {
String ename
= rset.getString(1);
data-sources.xml
int sal = rset.getInt(2);
<data-source
}
class="com.evermind.sql.OrionCMTDataSource"
location="jdbc/OracleDS"
} catch (SQLException e) {…}
connection-driver="oracle.jdbc.driver.OracleDriver"
username="scott"
stmt.close(); conn.close();
password="tiger"
url="jdbc:oracle:thin:@host1:5521:orcl
/>
53
Rodzaje źródeł danych w OC4J
• Emulowane (emulated data sources)
– Szybkie, "lekkie" transakcje (emulacja XA w JTA)
– Brak wsparcia dla transakcji rozproszonych (tylko 1-PC)
– Pool/cache na poziomie OC4J
• Nieemulowane (non-emulated data sources)
– Pełna obsługa transakcyjności JTA
– 2-Phase Commit (2-PC) dla transakcji rozproszonych
– Pool/cache na poziomie sterownika Oracle JDBC
• Natywne (native data sources)
– Brak wsparcia JTA
– Udostępniają funkcjonalność sterownika JDBC konkretnego dostawcy
– Pool/cache ewentualnie na poziomie sterownika JDBC
54
Emulowane źródła danych
• Wprowadzone dla emulacji funkcjonalności XA w JTA
dla sterowników JDBC, jej nie oferującej
• Również obecnie zalecane, gdy aplikacja nie wymaga
obsługi 2 Phase-Commit dla transakcji rozproszonych
– Większa efektywność niż nieemulowane (!)
data-sources.xml
<data-source
class="com.evermind.sql.DriverManagerDataSource"
name="OracleDS"
location="jdbc/OracleCoreDS"
xa-location="OracleDS"
ejb-location="jdbc/OracleDS"
connection-driver="oracle.jdbc.driver.OracleDriver"
username="scott"
password="tiger"
url="jdbc:oracle:thin:@localhost:5521:oracle"
inactivity-timeout="30"
/>
3 lokalizacje wymagane
Używana tylko:
ejb-location
55
Nieemulowane źródła danych
• Pełne wsparcie JTA, również w zakresie 2-Phase Commit
• Pooling/caching, rozszerzenia standardu JDBC
(obecnie tylko dla Oracle'a)
• Zalecane przy pracy z rozproszonymi bazami danych
data-sources.xml
<data-source
class="com.evermind.sql.OrionCMTDataSource"
location="jdbc/OracleDS"
connection-driver="oracle.jdbc.driver.OracleDriver"
username="scott"
password="tiger"
url="jdbc:oracle:thin:@localhost:5521:oracle
/>
Tylko jedna lokalizacja
specyfikowana
56
Natywne źródła danych
• Implementacje DataSource poszczególnych dostawców
• Mogą oferować pooling/caching i specyficzne rozszerzenia
JDBC
• Zalecana ostrożność przy ich wykorzystaniu w OC4J
• OC4J nie może ich uwzględniać w globalnych transakcjach
(nie wspierają JTA)
data-sources.xml
<data-source
class="com.my.DataSourceImplementationClass"
name="NativeDS"
location="jdbc/NativeDS"
username="user"
password="pswd"
url="jdbc:myDataSourceURL"
/>
Tylko jedna lokalizacja
specyfikowana
Wybór rodzaju źródła danych Podsumowanie
• Czynniki, które należy brać pod uwagę:
– Planowane wykorzystanie transakcji JTA
(konieczne dla encyjnych EJB)
– Planowane transakcje rozproszone wymagające 2-Phase Commit
tak
nie
Wymagane JTA?
nie
tak
Wymagane 2-PC?
Emulated DS
Non-Emulated DS
Native DS
57
58
JNDI: Java Naming and Directory Interface
• Interfejs umożliwiający aplikacjom Java korzystanie
z serwisów nazw (LDAP, OID, ...)
• W J2EE używany głównie do lokalizacji:
– Źródeł danych
– Komponentów EJB
• Podstawowe pojęcia związane z JNDI i serwisami nazw:
– Binding – powiązanie nazwy z obiektem lub referencją na obiekt
– Context – zbiór powiązań nazw z obiektami
– Subcontext – występuje gdy nazwa w jednym kontekście jest
powiązana z innym kontekstem (wykorzystującym tę samą konwencję
nazw)
– Naming system – zbiór połączonych kontekstów tego samego typu
– Naming service – usługa oferowana przez system nazw w oparciu
o konkretny interfejs
– Lookup – operacja wyszukania obiektu w kontekście w oparciu
o nazwę obiektu
59
JNDI InitialContext (1/2)
• Początkowy kontekst przy przyłączaniu się aplikacji do serwisu nazw
• Uzyskanie z poziomu aplikacji J2EE na serwerze aplikacji (ta sama JVM):
Context ic = new InitialContext();
DataSource ds = (DataSource) ic.lookup("jdbc/OracleDS");
...
• Uzyskanie z poziomu zdalnego klienta (inna JVM):
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.evermind.server.rmi.RMIInitialContextFactory");
env.put(Context.SECURITY_PRINCIPAL, "admin");
env.put(Context.SECURITY_CREDENTIALS, "welcome");
env.put(Context.PROVIDER_URL,
Wymagane podanie
"ormi://localhost:23791/current-workspace-app"); parametrów połączenia
z serwisem nazw
Context ic = new InitialContext(env);
DataSource ds = (DataSource) ic.lookup("jdbc/OracleDS");
...
Alternatywą dla przekazania Hashtable jest umieszczenie parametrów w pliku jndi.properties
60
JNDI InitialContext (2/2)
• Konstruktor początkowego kontekstu wykorzystuje
ustawienia z jednego z 3 miejsc:
– Wartości właściwości systemowych (system properties)
• Ustawiane przez serwer OC4J
– Plik jndi.properties dystrybuowany z aplikacją
– Instancja Hashtable przekazana jawnie konstruktorowi
• Uzyskanie początkowego kontekstu z poziomu zdalnego klienta
(inna JVM) – ustawienia w jndi.properties:
jndi.properties
java.naming.factory.initial= com.evermind.server.rmi.RMIInitialContextFactory
java.naming.provider.url= ormi://localhost:23791/current-workspace-app
java.naming.security.principal=SCOTT
java.naming.security.credentials=TIGER
Context ic = new InitialContext();
DataSource ds = (DataSource) ic.lookup("jdbc/OracleDS");
...
23791 – domyślny port
ormi (można pominąć)
Udostępnianie zasobów i informacji przez61
JNDI
• Kontener OC4J posiada swoje drzewo
JNDI
/
– Inicjalizacja w oparciu o pliki konfiguracyjne
serwera np. data-sources.xml
– Przykład ścieżki dla operacji lookup:
MyDS
jdbc
OraDS
ejb
EmpEJB DeptEJB
DataSource ds = (DataSource) ic.lookup("jdbc/OraDS");
• Każda aplikacja posiada własne drzewo JNDI
– Inicjalizacja w oparciu o deskryptor instalacji aplikacji np. web.xml,
ejb-jar.xml (referencje na EJB, DS, zmienne środowiskowe)
– Ścieżka do zasobu poprzedzana prefiksem: java:comp/env/
• Dla referencji na źródła danych i EJB oraz zm. środowiskowych
• Zawsze przy dostępie do lokalnych obiektów (w tej samej JVM)
– Przykład ścieżki dla operacji lookup:
DataSource ds = (DataSource) ic.lookup("java:comp/env/jdbc/OraDS");
62
Źródła danych a przenaszalność aplikacji
• Aplikacja może uzyskiwać źródła danych z drzewa JNDI
kontenera (OC4J)
– Definicja w pliku data-sources.xml kontenera lub aplikacji
– Lookup: DataSource ds = (DataSource) ic.lookup("jdbc/OraDS");
• Bardziej elastyczne rozwiązanie polega na wykorzystaniu
referencji do źródeł danych w drzewie JNDI aplikacji
– Definicja źródła w pliku data-sources.xml OC4J (nazwa JNDI źródła)
– Definicja referencji w deskryptorze instalacji (web.xml, ejb-jar.xml)
– Powiązanie referencji na źródło danych z nazwą JNDI źródła danych
w specyficznym dla OC4J pliku konf. (orion-web.xml, orion-ejb-jar.xml)
– Przy instalacji aplikacji wymagana jedynie zmiana powiązań w plikach
orion-web.xml/orion-ejb-jar.xml (dostarczonych z aplikacją lub
automatycznie tworzonych przez OC4J przy instalacji aplikacji)
63
Wyszukanie źródła danych przez
referencję - Przykład
• Przykład dla serwletu i emulowanego źródła danych:
web.xml
<resource-ref>
<res-ref-name>jdbc/RefOracleDS</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Application</res-auth>
</resource-ref>
orion-web.xml
<resource-ref-mapping
name="jdbc/RefOracleDS"
location="jdbc/OracleDS"
/>
data-sources.xml
Operacja lookup
w kodzie serwletu
<data-source
class="com.evermind.sql.DriverManagerDataSource"
name="OracleDS"
location="jdbc/OracleCoreDS"
xa-location="OracleDS"
ejb-location="jdbc/OracleDS"
connection-driver="oracle.jdbc.driver.OracleDriver"
username="scott" password="tiger"
url="jdbc:oracle:thin:@localhost:5521:oracle"
/>
DataSource ds = (DataSource) ic.lookup("java:comp/env/jdbc/RefOracleDS");
64
Transakcje na platformie J2EE
• Transakcja to podstawowa logiczna jednostka interakcji
użytkownika z systemem posiadająca własności "ACID":
– (A) atomowość, (C) spójność, (I) izolacja, (D) trwałość
• Transakcjom na platformie J2EE poświęcony jest standard
JTA: Java Transaction API
– Oparty na specyfikacji JDBC 2.0 i standardzie XA
– Specyfikuje interfejsy między zarządcą transakcji (zgodnym
z JTS – Java Transaction Service) a innymi jej „uczestnikami”
– Za zarządzanie transakcjami odpowiada zarządca transakcji J2EE
• Niektóre typy komponentów aplikacji J2EE mogą korzystać
z tradycyjnych transakcji JDBC
– Za zarządzanie transakcjami odpowiada zarządca transakcji SZBD
65
Funkcjonalność JTA - Schemat
66
Transakcje JTA w aplikacjach J2EE
• Głównie transakcje realizowane przez komponenty EJB
– JTA również dostępne w serwletach/JSP
– Niektóre typy EJB (sesyjne, komunikatowe), podobnie jak serwlety
i JSP, mogą korzystać z transakcji JDBC (dla transakcji lokalnych)
• Realizacja transakcji JTA w aplikacji J2EE wymaga:
– Rejestracji zasobów
• Rejestracja zasobów wiąże się z konfiguracją źródeł danych
w jednym z plików konfiguracyjnych aplikacji
• Od liczby wykorzystanych zasobów w transakcji zależy czy użyty
będzie mechanizm 2-Phase Commit
– Wyznaczenia granic transakcji – dla EJB 2 możliwe podejścia:
• Przez komponent (BMT: Bean-Managed Transaction)
• Przez kontener (CMT: Container-Managed Transaction)
JTA: Transakcje zarządzane
przez komponent (BMT)
• Aplikacja jawnie rozpoczyna i kończy transakcję przez
interfejs UserTransaction
– Dozwolone dla sesyjnych EJB
– Niedozwolone dla encyjnych EJB
– Jedyna możliwość wykorzystania transakcji JTA w serwletach / JSP
...
Context ctx = new InitialContext();
UserTransaction ut
= (UserTransaction) ctx.lookup("java:comp/UserTransaction");
ut.begin();
DataSource ds
= (DataSource) ctx.lookup("java:comp/env/jdbc/OracleDS");
// uzyskanie obiektu Connection po (!) rozpoczęciu transakcji
Connection conn = ds.getConnection();
...
ut.commit();
...
67
JTA: Transakcje zarządzane
przez kontener (CMT)
• Dostępne tylko dla komponentów EJB
– Dla encyjnych jedyna możliwość
– Dla sesyjnych wybór poprzez wartość elementu <transaction-type>:
Bean/Container
• Transakcją steruje kontener, na podstawie informacji
podanych w sposób deklaratywny w deskryptorze instalacji
• Deklaracje mają postać atrybutu transakcyjnego
<trans-attribute> podanego dla całego komponentu lub
poszczególnych jego metod
– Required, RequiresNew, Supports, NotSupported, Mandatory, Never
68
69
Transakcje JDBC w aplikacjach J2EE
• Głównie z myślą o wykorzystaniu "spadkowego" kodu
• Dostępne dla serwletów/JSP oraz sesyjnych
i komunikatowych EJB
• Aplikacja jawnie kończy transakcję poprzez interfejs
Connection
– Nie ma konieczności jawnego rozpoczynania transakcji
...
Connection conn = ...;
conn.setAutoCommit(false);
// wyłączenie trybu "auto-commit"
...
// polecenia SQL
conn.commit();
// lub: conn.rollback();
Transakcje w aplikacjach J2EE Podsumowanie
• Serwlety / JSP:
– Granice transakcji wyznaczane programowo (jawnie): JDBC lub JTA
• Komponenty EJB:
– Granice transakcji wyznaczane programowo – BMT (JDBC / JTA)
lub deklaratywnie – CMT
70