• Nem Talált Eredményt

példa - Címek adatainak adatbázisból Address -listába olvasása

In document Programozási technológiák – Jegyzet (Pldal 145-150)

A szerződés alapú tervezés alapelvei

A szerződés

2. Adatbázis-kapcsolatok kezelése

6.1. példa - Címek adatainak adatbázisból Address -listába olvasása

1 import hu.unideb.inf.progtech.booketdvdstore.entity.Address;

import java.sql.Connection;

import java.sql.DriverManager;

5 import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Statement;

import java.util.ArrayList;

import java.util.List;

10

public class SampleJdbc {

public List<Address> listAddresses() {

List<Address> resultList = new ArrayList<>();

final String query = "SELECT * FROM ADDRESSES";

15

try (Connection conn = DriverManager.getConnection(1 "jdbc:oracle:thin:@servername.inf.unideb.hu:1521:ORCL", "username", "password");

25 address.setCountry(rs.getString("COUNTRY")5);

address.setCity(rs.getString("CITY")5);

address.setZipCode(rs.getString("ZIP_CODE")5);

address.setStreet(rs.getString("STREET")5);

address.setHouseNumber(rs.getString("HOUSE_NUMBER")5);

30 resultList.add(address);

1 A kapcsolat kiépítésének első lépéseként a DriverManager osztály statikus getConnection metódusát kell meghívni. Ennek kell paraméterként átadni a JDBC URL-t, amely alapján a DriverManager kiválasztja, és szükség esetén betölti és példányosítja a megfelelő implementáció Driver interfészt megvalósító osztályát. A rendelkezésre álló meghajtóprogramok listáját a jdbc.drivers rendszertulajdonságból olvassa ki.

Ez a Driver objektum ezek után a JDBC URL-ben (és a getConnection metódusban) megadott adatok (az alnév, illetve az azonosításra szolgáló adatok) segítsgével kiépíti a socket alapú kapcsolatot a távoli adatbázisszerverrel, a mindkét fél által ismert sepeciális protokollnak megfelelően. Ha a kapcsolat kiépült, akkor létrejön egy, a Connection interfészt implementáló osztály egy objektuma, ez lesz az, amivel a DriverManager.getConnection visszatér.

Fontos

A régebbi (4.0-ást megelőző) JDBC verziókban ezt a lépést megelőzte egy meghajtóprogram-regisztrációs folyamat (amelyet a fenti ábra 0. lépése szemléltet), amelyre – mivel a DriverManager osztály betöltésekor a rendelkezésre álló (a jdbc.drivers rendszertulajdonságban megadott) meghajtóprogramok adatai betöltődnek – ma már csak abban a nagyon ritka esetben lehet szükség, ha a betöltendő meghajtóprogram a DriverManager osztálybetöltésekor még nem állt rendelkezésre. Szükség esetén ezt a DriverManager osztály statikus registerDriver metódusával tehetjük meg. Alternatív megoldásként a JDK-val kompatibilis virtuális gépeken a JDBC-megvalósítás Driver interfészt implementáló osztályának dinamikus (reflection

Adatkezelés

segítségével történő) betöltése is ellátta ezt a feladatot. Példa ezekre az Oracle megvalósítása esetén:

DriverManager.registerDriver (new oracle.jdbc.OracleDriver());

Class.forName("oracle.jdbc.OracleDriver");

Mivel a 4.0-ás JDBC a Java 6-ban jelent meg először, ez azt jelenti, hogy ezt a tevékenységet (a fentiek két utasítás közül csak az egyiket!) csak az ennél régebbi Java verziók esetén kell elvégezni.

2 A második lépésben egy SQL-utasítást reprezentáló Statement objektum létrehozására van szükség. Itt természetesen a helyettesíthetőség elve alapján PreparedStatement vagy CallableStatement is szóba jöhet.

3 A harmadik lépésben az utasítás végrehajtása történik meg. Erre a Statement interfész execute metóduscsaládja (az executeQuery, az executeUpdate, illetve az execute) szolgál, attól függően, hogy a végrehajtadó utasítás SELECT vagy egyéb utasítás-e.

4 A következő tevékenység az utasítás végrehajtási eredményének a feldolgozása. Ez nem SELECT utasítás esetén egyszerűen megy, hiszen az executeUpdate-től visszakapott érték mindössze egy egész érték, azonban lekérdezések végrehajtását követően az eredményhalmaz (ResultSet) objektum feldolgozására van szükség.

5 Végezetül pedig, ha már nincs szükségünk valamely erőforrásra – például ResultSet-re Statement-re vagy akár Connection-re –, azt a close metódus meghívásával be kell zárni. Ez azonban a Java 7-ben bevezetett AutoCloseable interfész és a try-with-resources utasítás segítségével automatizálható is, mivel az említett interfészek mind az AutoCloseable leszármazottai.

2.4.1. Kapcsolat létrehozása

A használni kívánt adatforrással létesítendő kapcsolat kiépítése kétféleképpen történhet: a DriverManager osztály vagy a DataSource interfész segítségével. A DriverManager osztály használata elsősorban Java SE környezetben ajánlott.

Mivel a DriverManager segítségével végzett kapcsolatkialakítást a fentiekben már bemutattuk, ezért itt már csak a DataSource interfész segítségével kiépítésre kerülő kapcsolatok bemutatása kerül sorra.

A DataSource interfész segítségével az adatforrás használata jobban optimalizálható, mint a DriverManager osztály esetében. Egy DataSource objektum lehetővé teszi a kapcsolat a fizikai kapcsolat gyorsítótárazását és ezáltal újrafelhasználhatóságát (connection pooling), valamint elosztott tranzakciók kezelését is, amely vállalati környezetben és Enterprise JavaBeanek (EJB-k) használatakor alapvető fontosságú. DataSource objektum nem csak adatbázis-kezelő rendszert reprezentálhat, hanem egyéb adatforrást is, például egy állományt. Egy DataSource példány létrehozásához névszolgáltatást veszünk igénybe, amely a Java név- és címtárszolgáltatásának, a Java Naming and Directory Interface-nek (a JNDI-nek) az API-ját használja. A létrehozást követően adhatók meg az adatforrás jellemzői.

6.15. ábra - Kapcsolat létrehozása

DataSource

segítségével

A DataSource további előnye a DriverManager-rel szemben, hogy az adatforrás URL-jét nem szükséges fixen kódolnunk, így alkalmazásunk még inkább hordozhatóvá válik. Ezenkívül a DataSource rugalmas is, mert változás esetén elég csak a tulajdonságait lecserélni, és nincs szükség a változás teljes alkalmazáson keresztül történő végigvezetésére. Például, ha az adatforrást egy másik szerverre költöztetnek, elég csak a serverName tulajdonságot átírni.

Adatkezelés

6.16. ábra - Connection pooling

Ha szeretnénk a kapcsolatot újrafelhasználni, a DataSource interfész ConnectionPoolDataSource alinterfészével dolgozhatunk. Elosztott tranzakciók esetén az XADataSource interfész által biztosított kapcsolatok használata javasolt.

6.17. ábra - Elosztott tranzakciók támogatása

Adatkezelés

A DataSource interfészt – bár a Java SE-nek is része – elsősorban Java EE környezetben alkalmazzuk.

2.4.2. SQL-utasítások létrehozása

Az SQL-utasításokat reprezentáló Statement objektumok létrehozásához szükségünk van egy kapcsolatobjektumra, vagyis egy utasítást csakis egy kapcsolathoz kötődően lehet létrehozni (egész pontosan a kapcsolatobjektum hozza létre az utasításobjektumot). Az utasítások háromfélék lehetnek:

• Az egyszerű Statement, amellyel egyszerű SQL-utasításokat hajthatunk végre, olyanokat, amelyek teljes szövege ismert,

• A PreparedStatement, amely az előfordított SQL-utasítások absztrakciója, a Statement típust altípusa. Az ilyen utasítások paraméterekkel is rendelkezhetnek, amelyekhez nem a fordításkor, hanem csak közvetlenül a végrehajtás előtt rendelődik érték,

• A CallableStatement, amely a tárolt programegységek végrehajtására szolgál. A PreparedStatement típus altípusaként a CallableStatement is rendelkezik paraméterekkel, mi több nem csak bemenő, hanem akár kimenő paraméterekkel is (például egy tárolt függvény visszatérési értéke vagy egy PL/SQL tárolt eljárás OUT módú paramétere esetén).

2.4.2.1. Statement

Az SQL-utasítások létrehozásának legegyszerűbb módja a Statement típus használata.

final String query = "SELECT * FROM ADDRESSES";

Statement stmt = conn.createStatement();

ResultSet rs = stmt.executeQuery(query);

Fontos

Az SQL-utasítást tartalmazó sztring végén nincs utasítás lezáró pontosvessző! Futásidejű kivétel váltódik ki, ha pontosvesszőt írunk a sztring végére.

Megjegyzés

Adatkezelés

Az SQL-utasítások nem érzékenyek a kis- és nagybetűkre. Ez alól természetesen kivételt képeznek a karakter- és sztringkonstansok.

2.4.2.2. PreparedStatement

A PreparedStatement típust akkor célszerű használni, ha ugyanazt az SQL-utasítást többször is végre szeretnénk hajtani, vagy ha az SQL-utasítás szövege (különösképpen a WHERE feltételé) dinamikusan áll elő.

Előbbire példa, ha egy beszúró (INSERT) vagy módosító (UPDATE) utasítás esetén az SQL-utasítás törzse ugyanaz, csak a beszúrandó vagy módosítandó értékek változhatnak.(Hasonló helyzet persze a DELETE utasítással is előfordulhat, ha más-más értékkel megegyező attribútumértékű rekordok törlésére volna szükség.) Ilyen esetekben gyakran egy ciklusban adjuk meg az értékeket, mint a PreparedStatement argumentumait, amelyek iterációról iterációra változhatnak.

A második eset akkor fordulhat elő, ha egy Statement objektumot az alábbi módon próbálunk összeállítani:

String custID = ... // felhasználói felületről érkező érték Statement stmt = conn.createStatement();

ResultSet rset = stmt.excuteQuery("SELECT * FROM customers WHERE customer_id = '" + custID + "')");

Megjegyzés

Az SQL nyelv sztringkonstansait aposztrófok határolják, nem pedig idézőjelek, mint a Java nyelv esetén.

Ekkor – különösen, ha a custID értéke felhasználói bemenetről való – könnyedén SQL-befecskendezéses támadás áldozataivá válhatunk, hiszen ehhez elegendő csak a custID változónak a "' OR 1=1--", vagy a

"'--" értéket adni annak érdekében, hogy a végrehajtandó lekérdezés egyetlen rekord (az adott azonosítóval rendelkező ügyfél) helyett vagy az össszes ügyféladatot visszaadja, vagy nem ad vissza egyet sem, hiszen az aposztróf lezárja a WHERE customer_id = résznél nyitott aposztrófot. A -- az SQL-ben a sorközi megjegyzésekre való, vagyis a -- jelsorozattól a sor végéig tart a megjegyzés. így a custID változó értéke után konkatenált sztring elemei már sem nem osztoznak, sem nem szoroznak.

Így a végrehajtandó utasítás szövege rendre így nézne ki:

SELECT * FROM customers WHERE customer_id = '' OR 1=1

Itt az '' (két aposztróf) az üressztringet jelzi. A hozzávagyolt 1=1 feltételrész miatt azonban ez a feltétel mindig igaz lesz, ezért az összes ügyfél adata kiszivároghat.

SELECT * FROM customers WHERE customer_id = ''

Ez esetben pedig az üressztring azonosítójú ügyfelet keresnénk, ez feltehetőleg egyetlen ügyfél esetén sem lesz igaz.

Megjegyzés

Az SQL-befecskendezéses (SQL injection) támadások olyan kódinjekción alapuló technikák, amelyekkel elsősorban adatközpontú alkalmazásokat támadnak meg. A technika lényege, hogy valamilyen speciálisan előkészített sztringgel rábírják az alkalmazást, hogy az eredetileg szándékolttól eltérő SQL-utasítást futtasson le.

6.18. ábra - Exploits of a mom [http://xkcd.com/327/]

Adatkezelés

A PreparedStatement előnye a Statement-tel szemben, hogy már létrehozásakor megkapja az SQL-utasítást, amely azonnal továbbítódik az adatbázis-kezelő rendszerhez, amely lefordítja azt. Így az utasítás végrehajtása során az adatbázis-kezelőnek nem kell újra és újra elvégezni a nagyon hasonló szövegű (egymástól csak paraméterértékekben eltérő) utasítások értelmezését és az az alapján történő végrehajtásiterv-készítést, mivel mindezek már rendelkezésre állnak Segítségükkel a fenti SQL-befecskendezéses támadások ellen is védekezünk, hiszen az utasítás szövege nem ellenőrizetlen sztringkonkatenáció eredményeként áll elő, hanem egy úgynevezett kötési fázis (binding).során rögzül, amely nagyobb kontrollt biztosít az utasítások paraméterei fölött.

A PreparedStatement objektumok argumentumok nélkül is használhatók (ekkor gyakorlatilag egyenértékűek a Statement-tel), jóllehet elsősorban felparaméterezett SQL-utasítások végrehajtására szolgálnak. A JDBC-specifikációnak megfelelően a paramétereket kérdőjelek (?) segítségével jelöljük. A fenti példa helyett a helyes megvalósítás valami ilyesmi lenne:

In document Programozási technológiák – Jegyzet (Pldal 145-150)