88
Achilles

Programiranje baza podataka 01

Embed Size (px)

Citation preview

Page 1: Programiranje baza podataka 01

Achilles

Page 2: Programiranje baza podataka 01

Achilles

Pre nego što počnete da čitate ovu skriptu, hteo bih da vam skrenem pažnju na nekoliko pojedinosti vezanih za istu. Pre svega ovaj prevod je samo deo knjige Core Java autora Cay S. Horstmann i Gary Cornell, tačnije njegovo četvrto poglavnje posvećeno programiranju baze podataka. Ovaj prevod (i adaptacija) su urađeni u jako kratkom vremenskom periodu i to za ličnu upotrebu. Ipak, zbog velikog interesovanja, odlučio sam da, ipak, ovaj prevod postavim na web. Iz tog razloga prevod verovatno sadrži obilje pre svega štamparskih, ali i drugih grešaka.

Sve kritike, komentari i sugestuje su dobrodošli i možete ih slati na [email protected].

Beograd, novembar 2005. Autor

Page 3: Programiranje baza podataka 01

Achilles

Programiranje Baze

• Kreiranje JDBC • Strukturni upitni jezik • Instalacija JDBC • Osnovni koncepti programiranja JDBC • Izvršavanje upita • Pomeranje i ažuriranje rezultata • Metapodaci • Setovi redova • Transakcije • Unapređena organizacija povezivananja • Uvod u LDAP

U leto 1996 “San” je predstavio prvu verziju JDBC (Java Data Base Connectivity) API (application programming interface). Ovaj API omogućava programerima povezivanje sa bazom podataka, a potom postavljanje upita i ažuriranje, koristeći SQL (Structured Query Language) {Obično se izgovara kao “sequel” i to je industrijski standard za pristup bazi podataka}

JAVA i JDBC imaju suštinsku prednost u odnosu na okruženja drugih baza podataka: Programi razvijeni sa JAVA-om su nezavisni od računara i OS (operativnog sistema).

Isti program za bazu podataka napisan u Javi može biti pokrenut na NT box-u, Solarisu ili se može primeniti na Java platformi. Možete pomerati vaše podatke sa jedne baze na drugu npr. sa Microsoft SQL Server na Oracle, ili čak na neku malu bazu podataka smeštenu na device, i taj program će i dalje moći da čita podatke. Ova sposobnost je u jasnom kontrastu sa tradicionalnim programiranjem baze podataka. Često programeri pišu aplikaciju za bazu podataka u vlastitom jeziku, koristeći organizacioni sistem baze podataka koji je pristupačan od samo jednog proizvođača kompjutera.

JDBC je bila nadgrađivana nekoliko puta. JDBC je predstavljen kao deo JDK 1.2 (1998). U toku izdavanja ove knjige JDBC 3 je najnovija verzija, dok je JDBC 4 u razvoju. JDBC 3 je sadržan u JDK 1.4 i 5.0.

Moramo da skrenemo pažnju da JDK ne nudi mogućnost razvoja vizuelnih aplikacija za bazu podataka. Za kreiranje, građenja upita, i generisanje izveštaja potrebna je neka traća vrsta alata.

Page 4: Programiranje baza podataka 01

Achilles

U ovom poglavlju: • Objasnićemo pozadinu JDBC API • Uvešćemo vas (ili vas podsetiti) u SQL • Prikazaćemo vam dosta detalja i primera kako bi počeli sa

osnovnim korišćenjem JDBC • Završićemo sa kratkim uvodom u hiararhiju baza podataka,

LDAP protocol i JNDI (Java Naming and Directory Inaterface)

Kreiranje JDBC Od starta, programeri Jave u Sun-u bili su svesni potencijala koji je JAVA pokazivala u radu sa bazama podataka. Počevši od 1995. počeli su da rade na proširivanju standardne JAVA biblioteke za omogućavanje SQL pristupa bazi podtaka. Ono čemu su se nadali da će uraditi je da prošire Javu tako da može da korespondira sa nekom bazom podataka, koristeći “čistu” Javu. Nije ime trebalo puno vremena da shvate da je to nemoguće. Jednostavno postoji puno baza podataka, koje koriste puno protokola. Kako je San dobijao naklonost proizvođaca, to su protokoli za pristup bazi podataka bivali standardizovaniji, ali samo dok je Sun koristio njihove mrežne protokole. Proizvođači baza podataka i alata su se složili da bi bilo korisno kad bi Sun obezbedio čist Java API za SQL pristup zajedno sa driver managerom koji bi dozvolio trećoj generaciji drajvera pristup nekoj bazi podataka. Proizvođači baza podataka bi mogli da naprave sopstvene drajvere koje bi uključili u driver menager. To bi bilo jednostavno rešenje za povezivanje treceg drajvera sa driver menager – suština je u tome da je potrebno da svi drajveri treba da prate zahteve iz driver menager API. Rezutat toga je da imamo dva interfejsa. Aplikacioni programeri koriste JDBC API a proizvođaci baza podataka i alata koriste JDBC driver API. Ovakva organizaja prati veoma uspešan Microsoft-ov model ODBC-a, koji koristi programski jezik C kao ineterfejs za pristup bazi podataka. I JDBC i ODBC su zasnovani na istoj ideji: programi su napisani u skladu sa komunikacijom između API I driver menager-a, koji u zamenu koristi drajvere koji su uključeni u komunikaciju sa aktuelnom bazom podataka.

Sve ovo znači da je JDBC API ono čime će se programeri najviše baviti. Vidi sliku1.

Page 5: Programiranje baza podataka 01

Achilles

Slika 1: Komunikacioni put od JDBC do baze podataka Tipovi JDBC drajvera JDBC drajveri su klasifikovani u više tipova:

• Prvi tip drajvera prevodi JDBC u ODBC i oslanja se na ODBC drajvere za komunikaciju sa bazom podataka. Sun je uključio jedan takav drajver JDBC/ODBC most, zajedno sa JDK. Međutim most zahteva razvijanje i odgovarajuću konfiguraciju ODBC drajvera. Kada je JDBC bio prvi put pokrenut, most je bio zgodan za testiranje, ali nikad nije bio namnjen za krajnji prizvod. Većina dostupnih drajvera je bolja od gore pomenutog mosta, i zato preporučujemo da ga ne koristite.

Page 6: Programiranje baza podataka 01

Achilles

• Drugi tip drajvera je delom napisan u Javi, a delom u izvornom kodu; Komunicira sa klijentom API baze podataka. Kada koristimo takav drajver, moramo instalirati neku platformu sa specifičnim kodom u dodatku Java biblioteke.

• Treći tip drajvera je čista Java client biblioteka koja koristi bazu podataka nezavisno od komunikacionog protokola baze podataka, zahtevanog od komponente servera, koja onda prevodi zahtev u specifičan protokol baze podataka. Ovo se jednostavno može razviti od kada je baza podataka nezavistan kod lociran samo u serveru.

• Červrti tip drajvera je čista Java biblioteka koja prevodi JDBC zahteve direktno u specifični protokol baze podataka

Većina proizvođaca baze podataka koriste tip 3 ili tip 4 drajvera sa njihovom bazom podataka. Štaviše neke od kompanija se specijalizuju za proizvodnju drajvera sa bolje usklađenim standardima, koji podržavaju više platformi, imaju bolje preformanse ili u nekim slučajevima jednostavno bolju pouzdanost nego kod drajvera dobijenih od proizvođaca baza podataka. Na kraju da sumiramo, krajnji cilj JDBC je da omogući sledeće:

• Programeri mogu da pišu aplikacije u Javi da bi pristupili bilo kojoj bazi podataka, koristeći standardne SQL iskaze – ili čak specifične dodatke SQL-a – prateći konvencije Java jezika.

• Proizvođaci baza podataka i alata, mogu da isporučuju drajvere na niskom nivou. Tako mogu da optimizuju njihove dajvere za specifične proizvode.

Tipična upotreba JDBC Tradicionalni klijent/server model je dostigao GUI na klijentu i bazu podataka na serveru (vidi sliku2) U ovom modelu, JDBC drajver je razvijen na klijentu.

Page 7: Programiranje baza podataka 01

Achilles

Slika 2: Tradicionalna klijent/sever aplikacija Međutim, svet se udaljava od klijent/server prema „tronivojskom

modelu“ ili čak naprednijem „n- nivojskom modelu“. U tronivojskom modelu klijent ne poziva bazu podataka. Umesto toga on poziva središnji sloj na serveru koji pravi zahteve za bazu podataka. Ovaj model ima par prednosti. On razdvaja vizuelnu prezentaciju (na klijentu) od poslovne logike (na središnjem sloju) i redova podatka (u bazi podataka). Prema tome, postaje moguće pristupanje istim podacima i istim poslovnim pravilima od strane mnogo klijenata, kao što su Java aplikacija ili aplet ili web forme.

Komunikacija između klijenta i središnjeg sloja može se dešavati preko HTTP (kada koristimo web browser kao klijent), RMI (kada koristimo aplikaciju ili aplet) ili nekog drugog mehanizma. JDBC omogućava komunikaciju između središnjeg sloja i povratka iz baze podataka. Slika 3 pokazuje osnovnu arhitekturu. Naravno ima puno varijacija ovog modela. Specifično za Javu 2 Enterprise Edition je da definiše strukturu za aplikacioni server koji upravlja modulima koda, koji se zovu Enterprise JavaBeans i pruža korisne usluge kao što su balansiranje učitavanja, hvatanje zahteva, sigurnost i jednostavan pristup bazi podataka. U ovoj arhitekturi, JDBC još uvek igra važnu ulogu za emisiju kompleksinih upita baze podataka.

Slika 3: Tronivojska aplikacija

Page 8: Programiranje baza podataka 01

Achilles

The Structured Query Language (SQL) JDBC vam omogućava komunikaciju sa bazom podataka koristeći SQL, koji je komandni jezik za suštinsku komunikaciju sa svim modernim relacionim bazama podataka. Desktop baza podataka obično ima grafički interfejs, koji omogućava korisniku da manipuliše sa podacima direktno, ali baze podataka zasnovane na serveru su pristupačne samo preko SQL. Najviše desktop baza podataka ima SQL interfejs takođe, ali često ne podržava potpun SQL standard. JDBC paket može da bude, ništa drugo do API (application programming interface) za komunikaciju sa SQL iskazima u bazi podataka. U ovom delu ćemo vas ukratko uvesti u SQL. Ako nikada ranije niste videli SQL možda vam ovaj material neće biti dovoljan. Ako je tako treba da pogletate druge knjige sa ovom temom. Vi možda mislite da je baza podataka grupa imenovanih tabela sa redovima i kolonama. Svaka kolona ima svoje ime, a redovi sadrže aktuelne podatke. Ovo se ponekad zove zapis. Kao primer baze podataka za ovu knjigu, mi smo koristili skup tabela koje opisuju kolekciju klasičnih kompjuterskih naučnih knjiga. Table 1: The Authors Table Autor_ID Name Fname ALEX Alexander Christopher BROO Brooks Frederick P. ... ... ... Table 2: The Books Table Title ISBN Publisher_ID Price A Guide to the SQL Standard 0-201-96426-0 0201 47.95 A Pattern Language: Towns, Buildings, Construction

0-19-501919-9 019 65.00

... ... ... Table 3: The Books/Authors Table ISBN Autor_ID Seq No 0-201-96426-0 DATE 1 0-201-96426-0 DARW 2 0-19-501919-9 ALEX 1 … … … Table 4: The Publishers Table Publisher_ID Name URL 0201 Addison-Wesley www.aw-bc.com 0407 John Wiley & Sons www.wiley.com ... ... ...

Page 9: Programiranje baza podataka 01

Achilles

Slika 4 prikazuje pogled na tabelu knjige. Slika 5 prikazuje rezultat povezivanja te tabele sa tabelom publishers.

Obe tabele i knjige i publishers sadrže identifikator publisher. Kada povezujemo ove table preko publisher koda, mi dobijamo rezultat upita koji su napravljeni od vrednosti iz tabela koje se spajaju. Svaki red ovog rezultata sadrži informaciju o knjizi, zajedno sa imenom izdavača i web stranom URL. Obratite pažnju da su imena izdavača i URL duplirana u nekoliko redova zato što imamo nekoliko redova sa istim izdavačem.

Dobit od spajanja tabela je da se izbegne nepotrebno dupliranje podataka u tabelama baze podataka. Npr. jednostavna baza podataka može da ima kolone za izdavačevo ime i URL u tabeli knjige. Ali tada će baza podataka sama za sebe, a ne samo rezultat upita imati mnogo duplikata. Ako se web adresa izdavača promeni, sve što smo uneli mora biti ažurirano. Pojašnjeno, ovo je greška. U racionom modelu, mi distribuiramo podatke u višenamenske tabele tako nijedan podatak nikad nije nepotrebno dupliran. Npr. Svaki URL izdavača je sadržan samo jedanput u tabeli izdavača. Ako informacija treba da bude kombinovana tada se tabele spajaju.

Na slikama možemo videti grafički alat za pregledanje i povezivanje tabela. Mnogi proizvođaci imaju alate za upite u jednostavnoj formi koji spajaju ime kolone i informacije u toj formi. Ovi alati su često zvani query by example (QBE) alat. Suprotno tome, upitnik koji koristi SQL je ispisan u tekstu sa SQL sintaksom. Npr.

SELECT FROM WHERE

U podsetniku ovog dela, vi ćete naučiti kako da napišete ove upite. Ako vam je već poznat ovaj SQL samo preskočite ovaj deo. Konvencijom SQL ključne reči su napisane velikim slovima, mada to nije neophodno. Izraz SELECT je prilično fleksibilan. Vi možete jednostavno da odaberete sve redove u tabeli knjige sledećim upitom: SELECET * FROM books Odredba FROM se traži u svakom SQL SELECT izrazu. Odredba FROM govori koje tabele baze podataka treba da se istraže da bi našli podatak. Vi možete da izaberete kolonu koju želite SELECT ISBN, Price, Title

FROM Books Možete da ograničite redove u odgovoru sa WHERE klauzulom. SELECT ISBN, Price, Title FROM Books WHERE Price <= 29.95

Page 10: Programiranje baza podataka 01

Achilles

Budi pažljiv sa poređenjima jednakosti. SQL koristi = i <> pre nego ==

ili != kao u Java programskom jeziku, za ispitivanje jednakosti. Klauzula WHERE može da koristi pattern-e poređenja uz pomoć LIKE

operatora. The wildcard charcter nisu obični * i ?. Koristite % za nula ili više karaktera i podvlaku (_) za jedan karakter. Npr.

SELECT ISBN, Price, Title FROM Books WHERE Title NOT LIKE ‘%n_x%’ isključuje knjige sa naslovima koji sadrže reči kao što su UNIX ili

LINUX. Primeti da su stringovi ugrađeni u jednostruke navodnike. Jednostruki

navodnici unutar stringa su označeni kao par od jednostrukih navodnika. Npr. SELECT ISBN, Price, Title FROM Books WHERE Title LIKE ‘%’ ‘%’ Ispisuje sve Titlove koje sadrže jednostrani navodnik. Možete da selektujete podatke iz više tabela. SELECT * FROM Books, Publishers Bez WHERE klauzule, ovaj upit nije posebno interesantan. On izlistava

sve kombinacije redova iz obe tabele. U našem slučaju, gde knjige imaju 20 redova, a izdavači 8 redova, rezultat je skup redova sa 20x8 unosa i puno duplikata. Mi hoćemo da prinudimo upitnik da kaže da smo mi samo zainteresovani za povezivanje knjiga sa izdavačima.

SELECT * FROM Books, Publishers WHERE Books.Publisher_ID = Publishers.Publisher_ID Rezultat ovog upita ima 20 redova, jedan za svaku knjigu, zato što

svaka knjiga ima samo jednog izdavača u tabeli “izdavači”. Kad god imate više tabela u upitu, isto ime kolone može se pojaviti na

dva različita mesta. Ovo se desilo u našem primeru. Postoji kolona nazvana Publisher_ID u obe tabele. Da bi smo izbegli dvosmislenost moramo da stavimo ime tabele kome pripada kao prefix, kao što je

Books.Publisher_ID Možete koristiti SQL da bi menjali podatke unutar baze podataka,

takođe koristeći tzv. action query (npr., to su upiti koji pomeraju ili menjaju podatke). Npr. Ako želite da smanjite za $5.00 aktuelnu cenu knjiga koje imaju “C++” u svom nazivu

UPDATE Books SET Price = Price -5.00 WHERE Title LIKE ‘%C++%’

Page 11: Programiranje baza podataka 01

Achilles

Verovatno najvažnija aktivnost pored UPDATE je DELETE, koja

omogućava da se upitom obrisu oni sadržaji koji zadovoljavaju WHERE klauzulu.

Osim toga, SQL dolazi sa ugrađenom funkcijom za izračunavanje proseka, pronalaženje maksimuma i minimuma u koloni i sa mnogo više od toga.

Klasično ubacivanje u tabelu je uz pomoć INSERT iskaza INSERT INTO Books VALUES (‘a Guide to the SGQ Standard’,‘0-201-96426-0’,’0201’,’47.95’) Potrebani su vam odvojeni INSERT iskazi za svaki red koji želite da

ubacite u tabelu. Naravno pre neko što postavite upit, modifikujete ili unesete podatak,

potrebno je da imate mesto na kome čuvate podatke. Koristimo iskaz CREATE TABLE da bi kreirali novu tabelu. Vi specificirate ime I tip podatka za svaku kolonu. Npr.

CREATE TABLE Books { Title CHAR(60), ISBN CHAR(13), Publisher_Id CHAR(6), Price DECIMAL(10,2) }

Table 5: SQL Tipovi podataka Data Description INTEGER or INT Uobičajeno, 32-bitna celobrojna vrednost SMALLINT Uobičajeno, 16-bitna celobrojna vrednost NUMERIC(m,n), DECIMAL(m,n) or DEC(m,n)

Fiksan decimalan broj sa m celobrojnih cifara i n cifara posle decimalnog zareza

FLOAT(n) Broj u pokretnom zarez, sa n binarnih cifara preciznosti

REAL Uobičajeno, 32-bitni broj u pokretnom zarezu DOUBLE Uobičajeno, 64-bitni broj u pokretnom zarezu CHARACTER(n) or CHAR(n) String fiksne dužine određene sa n VARCHAR(n) String promenljive dužine sa maksimumom n BOOLEAN Bulove vrednosti DATE Kalendarski datum zavisi od implementacije TIME Vreme dana, zavisi od implementacije TIMESTAMP Datum i vreme, zavisi od implementacije BLOB Binarni veliki objekat CLOB Karakteni veliki objekat

Page 12: Programiranje baza podataka 01

Achilles

U ovoj knjizi nećemo da diskutujemo o dodatnim uslovima, kao što su

ključevi i ograničenja koje možete da koristite uz komandu CREATE TABLE. JDBC instalacija

Prvo vam treba program baze podataka koji je kompatibilan sa JDBC. Ima puno odličnih, kao što su IBM DB2, Microsoft SQL Server, MySQL, Oracle, PostgreSQL.

Morate takođe kreirati bazu podataka za vaše eksperimentalno korišćenje. Podrazumevaćemo da je imenujete COREJAVA. Kreirajte novu bazu podataka ili neka vam administrator baze podataka kreira adgovarajuće dozvole za tu bazu. Ono što vam treba je da možete da je kreirate, ažurirate i brišete tabele iz baze podataka.

Ako nikada ranije niste instalirali klijent/server bazu podataka, može vam se činiti da je podešavanje baze podataka nešto kompleksnije i da dijagnoza uzroka greške može biti teška. Najbolje bi bilo da tražite pomoć eksperta, ako vaše podešavanje ne radi korektno.

Ako je ovo vaše prvo iskustvo sa bazama podataka mi vam preporučujemo da instalirate čistu Javu bazu podataka kao što je McKoi (http://mckoi.com/databases), HSQLDB (http://hsqldb.sourceforge.net) ili Derby (http://incubator.apache.org/derby). Ove baze su manje moćne, ali su lakše za postavljanje.

Suštinski sve baze podataka proizvođača već imaju JDBC drajver. Samo treba da locirate instrukcije proizvođača da učitate drajvere u vaš program i da ga povežete sa datom bazom podataka. U sledećem delu teksta objasnićemo vam podešavanje za dve tipične baze podataka, koje su pritupačne na različitim platformama:

• McKoi • PostgreSQL

Instrukcije za druge baze podataka su slične, mada, naravno postoje neki različiti detalji. Mi smo protiv korišćenja JDBC/ODBC bridge drajvera koji dolazi sa Java 2 SDK. Naročito smo protiv korišćenja tog drajvera za desktop bazu podataka kao što je npr. Microsoft Access. Nije samo konfiguracija i instalacija glomazna, već i bridge drajver i desktop baza podataka imaju ograničenja koje mogu lako da dovedu do konfuzije. Konačno, uči se veoma malo o pravim bazama podataka sa ovim podešavanjem.

Page 13: Programiranje baza podataka 01

Achilles

Osnovni JDBC koncepti programiranja

Programiranje sa JDBC klasama je po dizajnu ne puno različito od programiranja sa uobičajenim klasama Java platforme: gradite objeke od JDBC core klasa, proširujući ih sa nasleđivanjem, ako je potrebno. Ovaj deo vas vodi kroz detalje. URL-ovi baze podataka Prilikom povezivanja sa bazom podataka, morate da specificirate izvor podataka i možda ćete trebati da specificirate dodatne parametre. Npr. Network protocol driver može zahtevati port i ODBC drajver može da zahteva različite atribute. Kao što možda i očekujete, JDBC koristi sintaksu sličnu običnom URL-u da opiše izvor podataka. Slede primeri sintakse jdbc:mckoi://localhost

jdbc:postgresql:COREJAVA Ovi JDBC URL-i specificiraju lokalnu McKoi bazu podataka i

PostggreSQL bazu podataka nazvanu COREJAVA. Generalno sintaksa je jdbc:subprotocol:other stuff Gde odabrani subprotocol bira specifičan drajver da se poveze sa

bazom podataka. Format za other stuff parameter zavisi od korišćenog subprotocol-a.

Pogledaj dogumentaciju proizvođača za specifične podatke.

Ostvarivanje veze Nađi ime JDBC klase drajvera korišćene od strane tvog proizvođača. Tipična drajver imena su: org.postgresql.Driver com.mckoi.JDBCDriver Dalje, nađi biblioteku u kojoj se drajver nalazi, kao što je pg74jdbc3.jar ili mkjdbc.jar. Koristi jedan od sledeća tri navedena mehanizama:

• Započni tvoj program baze podataka sa – classpath command – argumentum komandne linije

• Modifikuj CLASSPATH okolinu promenljivih • Kopiraj biblioteku baze podataka u jre/lib/ext direktorijum

Page 14: Programiranje baza podataka 01

Achilles

Klasa Driver manager određuje drajver baze podataka i kreira novu konekciju baze podataka. Međutim, pre nego što drajver menadžer može da aktivira drajver, drajver mora da bude registrovan.

Jdbc.driver property sadrži listu imena klasa za drajvere koje će drajver menadžer registrovati prilikom startovanja. Dve metode mogu da postave taj property.

Možes da specifikuješ property sa argumentom komandne linije kao što je

java –Djdbc.drivers=org.postgresql.Driver MyProg Ili tvoja aplikacija može da setuje vlasništvo sistema sa pozivom kao

što je System.setProperty(„jdbc.drivers“,“org.postgresql.Driver“); Takođe možes da snadbes multiple drajver; da ih odvojiš kolonom kao

što je org.postgresql.Driver:com.mckoi.JDBCDriver Posle registrovanja drajvera, otvaramo bazu podataka kodom koji je

sličan sledećem primeru: String url = “jdbc:postgresql:COREJAVA”; String username = “dbuser”; String password = “secret”; Connection conn = DriverManager.getConnection(url, username,

password); Drajver menadžer prolazi preko dostupnih drajvera trenutno

registrovanih da bi našao drajver koji može koristiti subprotocol specificiran u URL-u baze podataka.

Za naš program, smatramo da je pogodno koristiti properties da specificiramo URL, username i password u dodatku drajvera baze podataka Tipičan properties document ima sledeći sadržaj:

jdbc.drivers=org.postgresql.Driver jdbc.url=jdbc:postgresql:COREJAVA jdbc.username=dbuser jdbc.password=secret Evo i kod za čitanje properties dokumenta i uspostavljanje veze sa

bazom podataka. Properties props = new Properties(); FileInputStream in = new FileInputStream(“database.properties”); props.load(in); in.close(); String drivers = props.getProperty(“jdbc.drivers”); if (drivers != null) System.setProperty(“jdbc.drivers”,drivers);

Page 15: Programiranje baza podataka 01

Achilles

String url = props.getProperty(“jdbc.url”); String username = props.getProperty(“jdbc.username”); String password = props.getProperty(“jdbc.password”); return DriverManager.getConnection(url, username, password); Metod getConnection vraća objekat Connection. U sledećim odeljcima

videćemo kako da koristimo objekat Connection da bi izvršili SQL iskaz.

Testiranje instalacije tvoje baze

Podešavanje JDBC prvi put može biti problematično. Treba vam

nekoliko specifičnih informacija od proizvođača, i najmanja greška u konfigurisanju može dovesti do veoma zbunjujućih poruka o grešci. Prvi put bi trebali da testirate vašu bazu podataka bez JDBC. Pratite sledeće instrukcije.

Korak 1. Startujte bazu podataka. Za McKoi, pokrenite java -jar mckoidb.jar

iz instalacionog direktorijuma. Sa PostgreSQL pokrenite postmaster –i –D /usr/share/pqsql/data Korak 2. Podesite korisnika i bazu podataka. Za McKoi, pozovite java –jar mckoidb.jar –create dbuser secret Za PostgreSQL ispišite komande

createuser –d – U dbuser createdb –U dbuser COREJAVA Korak 3. Startujte SQL interpretator za vašu bazu podataka. Sa McKoi,

pozovite java –classpath mckoidb.jar com.mckoi.tools.JDBCQueryTool Za PostgreSQL, pozovite psql COREJAVA Korak 4. Unesite sledeće SQL komande: CREATE TABLE Greetings (Message CHAR(20))

INSERT INTO Greetings VALUES (‘Hello, World!’) SELECT * FROM Greetings

Posle ovog koraka videćete ispisano “Hello World!”

Korak 5. Obrišite: DROP TABLE Greetings

Page 16: Programiranje baza podataka 01

Achilles

Sada znate da je vaša instalacija baze podataka uspela i da možete da se ulogujete u bazu podataka, i treba da skupite pet delova informacije:

• Username i password baze podataka • Ime baze podataka koju koristimo (kao sto je COREJAVA) • JDBC URL format • Ime JDBC drajvera • Lokaciju biblioteke fajlova sa drajver kodom

Prva drva zavise od podešavanja vaše baze podataka. Ostala tri se nalaze u specificnoj dokumentaciji JDBC-a od proizvođaca baze podataka. Za McKoi, tipične vrednosti su

• Username baze podataka = dbuser, password = secret • Ime baze podataka = (none) • JDBC URL format = jdbc:mckoi://localhost/ • JDBC driver = com.mckoi.JDBCDriver • Library file = mkjdbc.jar

Za PostgreSQL, imamo

• Username baze podataka = dbuser, password = (none) • Ime baze podataka = COREJAVA • JDBC URL format = jdbc:POSTGRESQL:COREJAVA • JDBC driver = org.postgresql.Driver • Library file = pg74jdbc3.jar

Primer 1 je mali test program, koji možete koristiti da bi testirali vaše podešavanje JDBC-a. Pripremite data-base.properties fajl sa informacijama koje ste skupili. Startujte program sa bibliotekom drajvera iz class path, kao što je java –classpath .:driverPath TestDB (Zapamti da koristiš tačku i zarez umesto dvotake kao kod razdvajanja pod Windowsom)

Ovaj program izvršava iste SQL instrukcije kao i ručni test. Ako dobijete neku SQL poruku o grešci, treba da nastavite rad na vašem podešavanju. Izuzetno je često pravljenje jedne ili više sitnih grešaka sa kapitalizacijom, imena putanja, formatom JDBC URL ili konfiguracijom baze podataka. Kada test program ispiše “Hello, World!”, sve je u redu i možemo preći na sledeći odeljak

Page 17: Programiranje baza podataka 01

Achilles

Primer 1: TestDB.java import java.sql.*; import java.io.*; import java.util.*;

/** Ovaj program testira da li su baza podataka i JDBC drajver pravilno konfigurisani */

class TestDB { public static void main(String args[]) { try { runTest(); } catch (SQLException ex) { while (ex !=null) { ex.printStackTrace(); ex=ex.getNextException(); } } catch (IOException ex) { ex.printStackTrace(); } } /** Pokreće test kreirajući tabelu, dodajući vrednosti, prikazujući sadržaj tabele i brišući tabelu */ public static void runTest() throws SQLException, IOException { Connection conn = getConnection(); try { Statement stat = conn.createStatement();

Page 18: Programiranje baza podataka 01

Achilles

stat.execute("CREATE TABLE Greetings (Message CHAR(20))");

stat.execute("INSERT INTO Greetings VALUES ('Hello, World!')");

ResultSet result = stat.executeQuery("SELECT *

FROM Greetings"); result.next(); System.out.println(result.getString(1)); stat.execute("DROP TABLE Greetings"); } finally { conn.close(); } } /** Prihvata konekciju sa osobinama specificiranim u fajlu

database.properties @vraća konekciju sa bazom podataka */ public static Connection getConnection() throws SQLException, IOException { Properties props = new Properties(); FileInputStream in = new

FileInputStream("database.properties"); props.load(in); in.close(); String drivers = props.getProperty("jdbc.drivers"); if (drivers !=null) System.setProperty("jdbc.drivers",drivers); String url = props.getProperty("jdbc.url"); String username = props.getProperty("jdbc.username"); String password = props.getProperty("jdbc.password"); return DriverManager.getConnection(url, username,

password); } }

Page 19: Programiranje baza podataka 01

Achilles

Izvrsavanje SQL komandi Da bi izvršili SQL komandu, prvo kreirajte objekat Statement. Za

kreiranje Statement objekta, koristite Connection objekat koji se dobija pozivom DriverManager.getConnection.

Statement stat = conn.createStatement(); Zatim, smestite iskaz koji želite da izvršite u npr. string. String command = "UPDATE Books“

+ "SET Price = Price – 5.00” + "WHERE Title NOT LIKE ‘%Introduction%’”;

Zatim pozovite metod executeUpdate iz klase Statement: Stat.executeUpdate(command); Metoda executeUpdate vraća broj redova pogođenih SQL komandom. Npr. Metoda executeUpdate iz prethodnog primera vraća broj knjiga čija je cena snižena za $5.00. Metod executeUpdate može izvršiti akcije kao što su INSERT, UPDATE i DELETE, kao i definisane komande nad podacima kao CREATE TABLE i DROP TABLE. Međutim, treba da koristimo metod executeQuery da bi izvršili SELECT upit. Postoji takođe execute iskaz koji hvata sva izvršenja neosnovanih SQL izkaza. To obično koristimo samo za upite koje korisnik snabdeva interaktivno. Kada izvršite upit, interesuje vas rezultat. Objekat executeQuery vraća objekat tipa ResultSet koji koristite za prolaz kroz rezultat red po red. ResultSet rs = stat.executeQuery("SELECT * FROM Books”) Osnovni okvir za anazlizu rezultata izgleda ovako: While (rs.next())

{ look at a row of the result set

} Kada proveravate pojedinačne redove, želite da znate sadržaj polja.

Veličina broja pristupnih metoda daje vam tu informaciju. String isbn = rs.getString(1); double price = rs.getDouble(“Price”); Postoje pristupni za različite tipove, kao što je getString i getDouble.

Svaki pristup ima dve forme, jedna koja uzima brojne argumente i jedna koja uzima argumente u obliku stringa. Kada snabdevete brojnim argumentom, vi pozivate kolonu sa tim brojem. Npr. rs.getString(1) vraća vrednost prve kolone u trenutnom redu.

Kada snabdevete argumentum tipa string, vi pozivate kolonu po njenom imenu. Npr. Rs.getDouble("Price”) vraća vrednost kolone sa imenom

Page 20: Programiranje baza podataka 01

Achilles

Price. Korišćenje brojnog argumenta je malo efikasnije, ali argument tipa string čini kod jednostavnijim za čitanje i održavanje.

Svaki metod get pravi prihvatljiv tip konverzije kada tip metode ne odgovara tipu kolone. Npr. Kada pozovemo rs.getString(“Price”) konvertuje prethodne vrednosti Price kolone u string. Table 6: SQL tipovi podataka i njima odgovarajući Java tipovi SQL Tip podataka Java Tip podataka INTEGER or INT Int SMALLINT Short NUMERIC(m,n), DECIMAL(m,n) or DEC(m,n)

Java.math.BigDecimal

FLOAT(n) Double REAL Float DOUBLE Double CHARACTER(n) or CHAR(n) String VARCHAR(n) Strintg BOOLEAN Boolean DATE Java.sql.Date TIME Java.sql.Time TIMESTAMP Java.sql.Timestamp BLOB Java.sql.Blob CLOB Java.sql.Clob ARRAY Java.sql.Array Napredni SQL tipovi

Kao dopuna brojevima, stringovima i datumima, mnoge baze podataka

mogu čuvati velike objekte kao što su slike ili drugi podaci. U SQL –u binarni veliki objekti se nazivaju BLOB (binary large object) i karakterni veliki objekti CLOB (catacter large object). Metode getBlob i getColob vraćaju objekte tipa java.sql.Blob I java.sql.Clob. Ove klase imaju metode za stavljanje bajtova ili karaktera u veliki objekat.

SQL ARRAY je niz vrednosti. Npr. U tabeli Student, možete imati kolonu Scores koja je ARRAY OF INTEGER. Metoda getArray vraća objekat tipa java.sql.Array (koji je različit od java.lang.reflect.Array class o kojoj smo diskutovali u prvoj knjizi). Interfejs java.sql.Array ima metode koje dobijaju vrednosti niza.

Kada dobijete BLOB ili niz od baze podataka, trenutni sadržaj se dobija od baze podataka samo kada zahtevate posebnu vrednost. To je korisna preformansa unapređivanja, pošto podaci mogu biti prilično veliki.

Page 21: Programiranje baza podataka 01

Achilles

Neke baze podataka mogu čuvati korisnički definisane struktuirane promenljive. JDBC podržava mehanizam za automatsko mapiranje struktuiranih SQL tipova u Java objekte. Mi nećemo raspravljati o BLOBs, nizovima i korisnički definisanim tipovima i ostalim naprednostima. O tome možete naći vise podataka na drugim mestima.

java.sql.DriverManeger 1.1

• static Connection getConnection (String url, String user, String password)

Uspostavlja konekciju sa datom bazom podataka i vraća objekat Connection

java.sql.Connection 1.1

• Statement createStatement() Kreira objekat Statement koji može biti upotrebljen za izvršavanje SQL upita i ažuriranje bez parametara • void close() Odmah zatvara trenutnu konekciju i JDBC resurse koji su kreirani

java.sql.Statement 1.1

• ResultSet executeQuery (String sqlQuery) Izvršava SQL iskaz dat kao string i vraća objekat ResultSet kao pogled rezultata upita. • int executeUpdate(String sqlStatement) Izvršava SQL INSERT, UPDATE ili DELETE iskaz specificiran u stringu. Takođe izvršava Data Definition Language (DDL) iskaz kao što je CREATE TABLE. Vraća broj pogođenih zapisa ili -1 za iskaz bez broja ažuriranja • boolean execute(String sqlStatement) Izvršava SQL iskaz specificiran u stringu, vraća true ako iskaz vraća set rezultata ili false u ostalim slučajevima. Koristi metode getResultSet ili getUpdateCount za postizanje izkaza ispusta. • int getUpdateCount() Vraća broj pogošenih zapisa na koje je uticao prethodni iskaz ažuriranja ili -1 ako je prethodni iskaz bio bez broja ažuriranja. Pozivajte ovu metodu samo jedanput po izvršavanju iskaza. • ResultSet getResultSet() Vraća result set prethodnog upita ili null ako prethodni iskaz nema set rezultata. Pozivajte ovaj metod samo jedanput po izvršavanju iskaza • void close() Zatvara sve objekte iskaza i to je udružen result set.

Page 22: Programiranje baza podataka 01

Achilles

java.sql.ResultSet 1.1

• boolean next() Čini da se trenutni red u rezultatu pomera napred za jedan. Vraća false posle poslednjeg reda. obratite pažnju da morate pozvati ovaj metod za napredovanje u prvom redu. • Xxx getXxx(int columnNumber) • Xxx getXxx(String columnName) (Xxx je tip kao što je int, double, String, Data, etc.) vraća vrednost kolone sa datim brojem kolone ili imenom, pretvoren u specifičan tip. Nisu sve konverzije tipova legalne. Pogledajte dogumentaciju za detalje. • int findColumn(String columnName) Daje index kolone zajedno sa imenom kolone. • void close() Odmah zatvara trenutni set rezultata

java.sql.SQLException 1.1

• String getSQLState() Dobija “SQL state” pet brojeva greške koda zajedno sa greškom. • int getErrorCode() Dobija specifican proizvođački kod izuzetka. • SQLException getNextException() Dobija lančani izuzetak za taj jedan. Moze sadržati mnogo informacija o greškama.

Managing Konekcije, Iskazi i setovi rezultata

Svaki Connection objekat može da kreira jedan ili više Statement

objekata. Možete koristiti iste Statement objekte za višestruke, unrelated komande i upite. Međutim, iskaz ima najviše jedan otvoren set rezultata. Ako koristiš višestruki upit koji rezultira tvoju analizu istovremeno, zato treba višestruki Statement object.

Opominjemo stoga da najmanje jedna često korišćena baza podataka (Microsoft SQL Server) ima JDBC drajver koji dopušta samo jedan aktivni izkaz u trenutku. Koristimo metodu getMaxStatements iz klase DatabaseMetaData da pronađemo broj istovremeno otvorenih iskaza koji vaš JDBC drajver podržava.

Ovo zvuči ograničavajuće, ali u praksi, vi verovatno nećete raditi sa istovremenim setom rezultata. Ako je set rezultata srodan, onda bi trebalo izvesti kombinovani upit i analizirati jedan rezltat. Mnogo je efikasnije dozvoliti bazi podataka da kombinuje upite nego Java programu da prolazi kroz višestruki set rezultata.

Page 23: Programiranje baza podataka 01

Achilles

Kada završite sa korišćenjem ResultSet, Statement ili Connection, treba odmah pozvati motod close. Ovi objekti koriste velike strukture podataka, a vi ne želite da čekate dok garbage collector obavi posao.

Metoda close iz objekta Statement automatski zatvra združene setove rezultata, ako iskaz ima otvoren set rezultata. Slično, metoda close iz klase Connection zatvara sve iskaze konekcije.

Ako je vaša konekcija kratkotrajna, ne morate da brinete oko zatvaranja iskaza i seta rezultata. Samo se uverite da objekat konekcije nije ostao otvoren smeštanjem close iskaza u finally blok:

Connection conn = . . .; try { Statement stat = conn.createStatement();

ResultSet result = stat.executeQuery(queryString); process query result

} finally { conn.close(); }

Popunjavanje baze podataka

Mi sada želimo da napišemo naš prvi, stvarni, JDBC program. Naravno,

bilo bi lepo kad bi mogli da izvršimo neki fancy upit o kome smo diskutovali ranije. Nažalost, imamo problem: Trenutno nemamo nikakve podatke u bazi podataka. I nemožete naći dokument baze podataka na CD ROM-u koji biste jednostavno kopirali na vaš hard disk kao program baze podataka za čitanje, zato što nije dopustena zamena fajlova SQL relacione baze podataka jednog proizvođača drugim. SQL nema ništa sa tim fajlovima. To je jezik koji predstavlja upite i ažurira baze podataka. Kako baza podataka izvršava te iskaze najefikasnije i šta i kakav format fajla koristi ka tom cilju sasvim zavisi od implementacije baze podataka. Proizvođaci baza podataka se trude da imaju pametnu strategiju za optimizaciju upita i čuvanje podataka, i različiti proizvođaci pružaju različite mehanzime. Prema tome, iako su SQL iskazi prenosivi, podaci nisu.

Mi obezbeđujemo mali set podataka u serijama tekstualnih fajlova koji sadrže red SQL instrukcija za kreiranje tabele i ubacivanje vrednosti. Mi vam takođe dajemo program za čitanje fajlova sa SQL instrukcijama, jednu instrukciju po liniji, i izvršavanje njih.

Specifično, program čita podatak iz tekstualnog fajla u formatu kao CREATE TABLE Publisher (Publisher_Id(6), Name CHAR(30), URL

CHAR(80)) INSERT INTO Publisers VALUES (‘0201’,’Addison-Wesley’,’www.aw-

bc.com’)

Page 24: Programiranje baza podataka 01

Achilles

INSERT INTO Publishers VALUES (‘0471’,’John Wiley & Sons’,’www.wiley.com’)

. . . Na kraju ovog dela možete da vidite kod za program koji čita fajl sa

SQL iskazima i izvršava iskaze. Čak ako niste zainteresovani za gledanje implementacije, morate pokrenuti ovaj program ako želite da izvršite mnoge interesantne primere iz podsetnika ovog poglavlja. Pokrenite program kao što sledi:

java –classpath .:driverPath ExecSQL Books.sql java –classpath .:driverPath ExecSQL Authors.sql java –classpath .:driverPath ExecSQL Publishers.sql java –classpath .:driverPath ExecSQL BooksAuthors.sql Pre pokretanja programa, proverite da li je fajl database.properties

podešen kako treba za vaše okruzenje (strana xx). Sledeći koraci ukratko opisuju program ExecSQL: 1. Povezivanje sa bazom podataka. Metoda getConnection čita

osobine iz fajla database.properties i dodaje jdbc.drivers osobine u aribute (osobine) sistema. Drajver menadzer koristi osobine jdbc.drivers da bi učitao odgovarajući drajver baze podataka. Metoda getConnection koristi jdbc.url, jdbc.username i jdbc.password osobine da bi otvorio konekciju za bazom podataka.

2. Otvara fajl sa SQL komandom. Ako nema imena fajla, korisnik da unese komandu u konzoli.

3. Izvršavanje svake komande generičkom execute motodom. Ako vraća true komanda ima set rezultata. Četiri SQL fajla koja mi dostavljamo za knjigu bazu podataka se završavaju SELECT * iskazom pa možete videti da su podaci usešno ubačeni.

4. Ako ima set rezultata, ispiši rezultat. Zato u ovom generičkom setu rezultata treba koristiti metapodatke za pronalaženje koliko kolona ima u rezultatu. Više ćemo naučiti o metapodacima u nastavku ove knjige.

5. Ako se pojavi neki SQL izuzetak, ispiši izuzetak i svaki lanac izuzetaka koji može biti sadržan u tome.

6. Zatvaramo konekciju sa bazom podataka.

Page 25: Programiranje baza podataka 01

Achilles

Primer 2: ExecSQL.java import java.io.*; import java.util.*; import java.sql.*; /** Izvršava sve SQL iskaze u fajlu. Poziva ovaj program kao java -classpath driverPath: ExecSQL

commandFile */ class ExecSQL { public static void main (String args[]) { try { Scanner in; if (args.length == 0) in = new Scanner(System.in); else in = new Scanner(new File(args[0])); Connection conn = getConnection(); try { Statement stat = conn.createStatement(); while(true) { if(args.length == 0)

System.out.println("Enter command or EXIT to exit:"); if(!in.hasNextLine()) return; String line = in.nextLine(); if(line.equalsIgnoreCase("EXIT"))

return; try { boolean hasResultSet =

stat.execute(line); if (hasResultSet) showResultSet(stat); }

Page 26: Programiranje baza podataka 01

Achilles

catch (SQLException e) { while (e!=null) { e.printStackTrace();

e=e.getNextException(); } } } } finally { conn.close(); } } catch (SQLException e) { while (e != null) { e.printStackTrace(); e=e.getNextException(); } } catch (IOException e) { e.printStackTrace(); } } /** Prihvata konekciju sa osobinama specificiranim u fajlu

database.properties @vraća konekciju sa bazom podataka */ public static Connection getConnection() throws SQLException, IOException { Properties props = new Properties(); FileInputStream in = new

FileInputStream("database.properties"); props.load(in); in.close(); String drivers = props.getProperty("jdbc.drivers");

Page 27: Programiranje baza podataka 01

Achilles

if (drivers != null) System.setProperty("jdbc.drivers",drivers);

String url = props.getProperty("jdbc.url"); String username = props.getProperty("jdbc.username"); String password = props.getProperty("jdbc.password"); return DriverManager.getConnection(url, username,

password); } /** Štampa set rezultata. @param stat iskaz čiji set rezultata treba da bude ištampan */ public static void showResultSet(Statement stat) throws SQLException { ResultSet result = stat.getResultSet(); ResultSetMetaData metaData = result.getMetaData(); int columnCount = metaData.getColumnCount(); for (int i=1; i<=columnCount; i++) { if (i>1) System.out.print(","); System.out.print(metaData.getColumnLabel(i)); } System.out.println(); while(result.next()) { for (int i=1; i<=columnCount; i++) { if(i>1) System.out.print(","); System.out.print(result.getString(i)); } System.out.println(); } result.close(); } }

Page 28: Programiranje baza podataka 01

Achilles

Izvršavanje upita U ovom delu ćemo napisati program koji izvršava upit na COREJAVA

bazi podataka. Da bi ovaj program radio, morate da imate poopunjenu COREJAVA bazu podataka sa tabelama, kao što je opisano u prethodnom delu. Sledeća slika pokazuje aplikaciju QueryDB u akciji.

Možete izabrati autora i izdavača ili dopustiti ma koji kao “any”. Kliknite

na dugme Query; sve knjige koje se podudaraju sa vašim izborom biće ispisane u okviru tekstualnog polja.

Takođe možete menjati podatke u bazi podataka. Izberite izdavača i ukucajte neku vrednost u tekst boxu do dugmeta Change prices. Kada kliknete na dugme, sve cene tih izdavača su podešene sa vrednošću koju ste uneli, i tekstualno polje sadrži poruku koliko zapisa je promenjeno. Međutim, zbog minimalizacije nenameravanih izmena u bazi podataka, ne možete promeniti sve cene odjednom. Polje Autor je ignorisano kada se menja cena. Posle promene cene, možete poželeti da pokrenete upit da bi potvrdili nove cene. Spremni iskazi

U ovom programu, mi koristimo jednu novu osobinu, spremnost iskaza.

Razmotrimo upit za sve knjige po pojedinim izdavačima, nezavisno od autora. SQL upit je

SELECT Books.Price, Books.Title FROM Books, Publishers WHERE Books.Publisher_Id = Publishers.Publisher_Id AND Publishers.Name = the name from the list box Stoga bolje graditi odvojene upitne iskaze svaki put kada korisnik izvrši

takav upit, mi možemo pripremiti upit sa domaćom promenljivom i koristiti je više puta, svaki put puniti je u različiti string za promenljivu. Ta tehnika pospešuje performanse. Kad god baza podataka izvršava upit, prvo proračunava strategiju koja će najefikasnije izvršiti upit. Pomoću pripremanja upita i ponavljanja, uveravate se da je planiranje koraka završeno samo jedanput.

Svaka domaća promenljiva u pripremljenom upitu indikovana je sa ?. Ako je tu više od jedne promenljive mi moramo sačuvati poziciju od ? kada podešavamo vrednosti. Npr. naš pripremljen upit počinje

String publisherQuery = “SELECT Books.Price, Books.Title” +

“FROM Books, Publishers” + “WHERE Books.Publisher_Id = Publishers.Publisher_Id AND

Publishers.Name = ?”;

Page 29: Programiranje baza podataka 01

Achilles

PreparedStatement publisherQueryStat = conn.prepareStatement(publisherQuery); Pre izvršavanja pripremljenog iskaza, morate prvo dodeliti domaćoj promenljivoj aktuelnu vrednost sa set metodom. Kao sa ResultSet get metodama, postoje različite set metode za različite tipove. Ovde mi želimo da podesimo string u ime izdavača. publisherQueryStat.setString(1, publisher); Prvi argument je pozicija broja domaće promenljive koju želimo da podesimo. Pozicija 1 označava prvi ?. Drugi argument je vrednost koju mi želimo da dodelimo domaćoj promenljivoj. Ako ponovo koristimo pripremljene upite koje ste već izvršili i upite koji imaju više od jedne domaće promenljive, sve domaće promenljive ostaju ograničene kao što ste ih podesili sem ako ih izmenite sa set metodom. To znači da je potrebno pozvati metod setXxx tih domaćih promenljivih koje se menjaju od jednog upita ka sledećem. Kad sve promenljive imaju ograničenu vrednost, možete izvršiti upit ResultSet rs = publisherQueryStat.executeQuery(); Osobina ažuriranje cena je implementirana kao UPDATE iskaz. Zapazite da pozivamo executeUpdate, a ne executeQuery, zato UPDATE iskaz ne vraća set rezultata. Povratna vrednost executeUpdate je broj promenjenih redova. Mi prikazujemo broj u tekstualnoj zoni. int r = priceUpdateStmt.executeUpdate(); result.setText(r + “ records update “); Sledeći koraci ukratko opisuju primer programa

1. Urediti komponente u frejmu, koristeći raspored rešetkaste torbe (prva knjiga, 9 poglavlje)

2. Popunite tekst boksove autora i izdavača pomoću pokretanja dva upita koji vraćaju sva imena autore i izdavača u bazi podataka.

3. Kada korisnik klikne na dugme Query, pronalazi koji od četri tipa upita treba da bude izvršen. Ako je to prvi put da se taj upit izvršava, tada je pripremljena promemljiva null, i pripremljeni iskaz je konstruisan. Zatim vrednosti su ograničene na upite i upit se izvršava.

Upiti koji uključuju autore su mnogo kompleksniiji. Zato što knjiga može imati više autora, tabela BooksAuthors daje prepisku između autora i knjiga. Npr, knjiga sa ISBN brojem 0-201-96426-0 ima dva autora sa kodovima DATE I DARW. Tabela BooksAuthors ima redove 0-201-96426-0, DATE, 1 0-201-96426-0, DARW, 2 koji ukazuju na ovu činjenicu. Treća kolona izlistava rasporad autora. (Mi ne možemo samo koristiti poziciju zapisa u tabeli. Ne postoji fiksni red u relacionoj tabeli). Prema tome, upit treba da poveže tabele Books,

Page 30: Programiranje baza podataka 01

Achilles

BooksAuthors i Authors da bi uporedio ime autora sa onim što je izabrao korisnik. SELECT Books.Price, Books.Title FROM Books, BooksAuthors, Publishers

WHERE Authors.Author_Id = BooksAuthors.Author_Id AND BooksAuthors.ISBN = Books.ISBN

AND Books.Publisher_Id = Publishers.Publisher_Id AND Authors.Name = ? AND Publishers.Name = ?

4. Rezultat upita se prikazuje u rezultujućem tekstualnom boksu. 5. Kada korisnik klikne na dugme Change prices, onda komanda

ažuriranja se konstruiše i izvršava. Komanda je prilično složena zato što WHERE klauzula UPDATE iskaza traži kod izdavača, a mi znamo samo ime izdavača. Ovaj problem je rešen sa ugneždenim podupitom. UPDADE Books SET Price = Price + ? WHERE Books.Publisher_Id = (SELECT Publisher_Id FROM Publishers WHERE Name = ?)

6. Mi inicijalizujemo konekciju i objekt iskaza u konstruktoru. To čuvamo za vreme trajanja programa. Upravo pre izlaza iz programa, lovimo slučaj „zatvaranje prozora“ i ovi objekti su zatvoreni. Class QueryDBFrame extends Jframe { public QueryDBFrame()

{ conn = getConnection(); stat = conn.createStatement(); . . . add(new WindowAdapter() { public void windowClosing(WindowEvent

event) { try { stat.close(); conn.close(); } catch (SQLException e) { while (e != null) { e.printStackTrace(); e =

e.getNextException();

Page 31: Programiranje baza podataka 01

Achilles

} } } }); } . . . private Coonection conn;

private Statement stat; } Primer 3: QueryDB.java

import java.net.*; import java.sql.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; import javax.swing.*; /** Ovaj program pokazuje nekoliko složenih upita baze podataka */ public class QueryDB { public static void main(String[] args) { JFrame frame = new QueryDBFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } } /** Ovaj prozor prikazuje combo box-eve za parametre iz upita, tekstualnu oblast za comandne rezultate, I dugmiće za pokretanje upita i ažuriranje */ class QueryDBFrame extends JFrame { public QueryDBFrame() { setTitle("QueryDB"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); setLayout(new GridBagLayout());

Page 32: Programiranje baza podataka 01

Achilles

authors = new JComboBox(); authors.setEditable(false); authors.addItem("Any"); publishers = new JComboBox(); publishers.setEditable(false); publishers.addItem("Any"); result = new JTextArea(4, 50); result.setEditable(false); priceChange = new JTextField(8); priceChange.setText("-5.00"); try { conn = getConnection(); Statement stat = conn.createStatement(); String query = "SELECT Name FROM Authors"; ResultSet rs = stat.executeQuery(query); while (rs.next()) authors.addItem(rs.getString(1)); rs.close(); query = "SELECT Name FROM Publishers"; rs = stat.executeQuery(query); while(rs.next()) publishers.addItem(rs.getString(1)); rs.close(); stat.close(); } catch (SQLException e) { result.setText(""); while (e != null) { result.append(""+e); e = e.getNextException(); } } catch (IOException e) { result.setText(""+e); }

Page 33: Programiranje baza podataka 01

Achilles

//koristimo GBC convenience class of Core Java Volume 1, Chapter 9 add(authors, new GBC(0, 0, 2, 1)); add(publishers, new GBC(2, 0, 2, 1)); JButton queryButton = new JButton("Query"); queryButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { executeQuery(); } }); add(queryButton, new GBC(0, 1, 1, 1).setInsets(3)); JButton changeButton = new JButton("Change prices"); changeButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { changePrices(); } }); add(changeButton, new GBC(2, 1, 1, 1).setInsets(3)); add(priceChange, new GBC(3, 1, 1, 1).setFill(GBC.HORIZONTAL)); add(new JScrollPane(result), new GBC(0, 2, 4, 1).setFill(GBC.BOTH).setWeight(100, 100)); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent event) { try { if (conn != null) conn.close(); } catch (SQLException e)

Page 34: Programiranje baza podataka 01

Achilles

{ while (e!=null) { e.printStackTrace(); e=e.getNextException(); } } } }); } /** Izvršava izabrani upit */ private void executeQuery() { ResultSet rs = null; try { String author = (String) authors.getSelectedItem(); String publisher = (String) publishers.getSelectedItem(); if (!author.equals("Any") && !publisher.equals("Any")) { if (authorPublisherQueryStmt == null) authorPublisherQueryStmt = conn.prepareStatement(authorPublisherQuery); authorPublisherQueryStmt.setString(1, author); authorPublisherQueryStmt.setString(2, publisher); rs = authorPublisherQueryStmt.executeQuery(); } else if (!author.equals("Any") && publisher.equals("Any")) { if (authorQueryStmt == null) authorQueryStmt = conn.prepareStatement(authorQuery); authorQueryStmt.setString(1, author);

Page 35: Programiranje baza podataka 01

Achilles

rs = authorQueryStmt.executeQuery(); } else if (author.equals("Any") && !publisher.equals("Any")) { if (publisherQueryStmt == null) publisherQueryStmt = conn.prepareStatement(publisherQuery); publisherQueryStmt.executeQuery(); rs = publisherQueryStmt.executeQuery(); } else { if (allQueryStmt == null) allQueryStmt = conn.prepareStatement(allQuery); rs = allQueryStmt.executeQuery(); } result.setText(""); while (rs.next()) { result.append(rs.getString(1)); result.append(","); result.append(rs.getString(2)); result.append("\n"); } rs.close(); } catch (SQLException e) { result.setText(""); while (e != null) { result.append("" +e); e = e.getNextException(); } } } /** Izvržava iskaz ažuriranja promene cena */ public void changePrices() {

Page 36: Programiranje baza podataka 01

Achilles

String publisher = (String) publishers.getSelectedItem(); if (publisher.equals("Any")) { result.setText("I am sorry, but I cannot do that."); return; } try { if(priceUpdateStmt == null) priceUpdateStmt = conn.prepareStatement(priceUpdate); priceUpdateStmt.setString(1, priceChange.getText()); priceUpdateStmt.setString(2, publisher); int r = priceUpdateStmt.executeUpdate(); result.setText(r+ "records update."); } catch (SQLException e) { result.setText(""); while (e != null) { result.append("" +e); e = e.getNextException(); } } } /** Prihvata konekciju sa osobinama specificiranim u fajlu database.properties @vraća konekciju sa bazom podataka */ public static Connection getConnection() throws SQLException, IOException { Properties props = new Properties(); FileInputStream in = new FileInputStream("database.properties"); props.load(in); in.close(); String drivers = props.getProperty("jdbc.drivers"); if (drivers != null) System.setProperty("jdbc.drivers",drivers);

Page 37: Programiranje baza podataka 01

Achilles

String url = props.getProperty("jdbc.url"); String username = props.getProperty("jdbc.username"); String password = props.getProperty("jdbc.password"); return DriverManager.getConnection(url, username, password); } public static final int DEFAULT_WIDTH = 400; public static final int DEFAULT_HEIGHT = 400; private JComboBox authors; private JComboBox publishers; private JTextField priceChange; private JTextArea result; private Connection conn; private PreparedStatement authorQueryStmt; private PreparedStatement authorPublisherQueryStmt; private PreparedStatement publisherQueryStmt; private PreparedStatement allQueryStmt; private PreparedStatement priceUpdateStmt; private static final String authorPublisherQuery = "SELECT Books.Price, Books.Title FROM Books, BooksAuthors, Authors, Publisher" + "WHERE Authors.Autor_Id = BooksAuthors.Author_Id AND BooksAuthors.ISBN = Books.ISBN" + "AND Books.Publisher_Id = Publishers.Publisher_Id AND Authors.Name = ?" + "AND Publishers.Name = ?"; private static final String authorQuery = "SELECT Books.Price, Books.Title FROM Books, BooksAuthors, Authors" + "WHERE Authors.Autor_Id = BooksAuthors_Id AND BooksAuthors.ISBN = Books.ISBN" + "AND Authors.Name = ?"; private static final String publisherQuery = "SELECT Books.Price, Books.Title FROM Books, Publishers" + "WHERE Books.Publisher_Id = Publishers.Publisher_Id AND Publishers.Name = ?";

Page 38: Programiranje baza podataka 01

Achilles

private static final String allQuery = "SELECT Books.Price, Books.Title FROM Books"; private static final String priceUpdate = "UPDATE Books " + "SET Price = Price + ? " + "WHERE Books.Publisher_Id = (SELECT Publisher_Id FROM Publishers WHERE Name = ?)"; }

java.sql.Connection 1.1

• PreparedStatement prepareStatement(String sql) Vraća objekat PreparedStatement sadržeći prevodilačke iskaze. String sql sadrži SQL iskaz koji može sadržati jedan ili više parametara placeholder označenih sa ? karakterom.

java.sql.PreparedStatement 1.1

• void setXxx (int n, Xxx x) (Xxx je tip kao i int, double, string, data, idt.) postavlja vrednost n-tog parametara u x • void clearParameters() Briše sve trenutne parametre u pripremljenom iskazu • ResultSet executeQuery() Izvršava pripremljen SQL upit i vraća ResultSet objekat. • int executesUpdate() Izvršava pripremljeni SQL INSERT, UDDATE ili DELETE iskaz koji je predstavljen preko PreparedStatement objekta. Vraća broj pogođenih redova ili 0 za Data Definition Language (DDL) iskaze kao što je CREATE TABLE.

Pomeranje i ažuriranje rezultata

Kao što ste već videli, metoda next klase ResultSet ponavlja preko

redova u setu rezultata. To je sigurno odgovarajuće za program kome je neophodna analiza podataka. Međutim, razmotrimo prikazivanje vizelnog podatka, koji prikazuje tabele ili rezultat upita. Vi često želite da korisnik ima mogućnost pomeranje i napred i nazad u setu rezultata. Ali JDBC 1 nema pervious metodu. Programeri koji su želeli da implementiraju povratak nazad, morali su ručno da keširaju rezultujući set podataka. Scrollable result podešavanje u JDBC 2 dopušta pomeranje naped i nazad preko seta rezultata i skok na bilo koju poziciju u setu rezultata.

Page 39: Programiranje baza podataka 01

Achilles

Štaviše, kada korisnici vide ispisan sadržaj rezultujućeg seta mogli bi da požele da ga edituju. Ako snabdevate korisnike editabilnim pogledom, morate biti sigurni da će korisnikova modifikacija biti vraćena u bazu podataka. U JDBC 1 morali ste da programirate UPDATE iskaze. U JDBC 2 možete jednostavno ažurirati sadržaj seta rezultata i baza podataka će automatski biti ažurirana.

JDBC 2 isporučuje unapređene osobine seta rezultata, kao sposobnost ažuriranja seta rezultata sa najnovijim podacima ako su ti podaci modifikovani od strane druge konkurentne povezane baze podataka. JDBC 3 donosi još neke dodatke, specificiranje ponašanja seta rezultata kada je transakcija izvršena. Međutim, ove napredne osobine su van okvira ovog uvodnog poglavlja. Mi vas upućujemo na JDBC API Tutorial and Reference i JDBC specifikacioni dokument na http://java.sun.com/products/jdbc za više informacija. Pomeranje seta rezultata

Da bi dobili mogućnost pomeranja rezultujućeg seta vašeg upita,

morate upotrebiti različite Statement objekte sa metodom Statement stat = conn.createStatement(type, concurrency); Za pripremljene iskaze, koristite poziv PrepareStatement stat = conn.prepareStatement(command, type,

concurrency); Moguće vrednosti za type i concurrency su navedeni u tabeli 7 I tabeli

8. Imate sledeći izbor: • Da li želite da set rezultata bude scrollable ili ne ? Ako ne

koristite ResultSet.TYPE_FORWARD_ONLY • Ako je set rezultata scrollable, da li želite da bude sposoban da

reflektuje izmene u bazi podataka koje se dese posle upita koji su proizveli? (U našoj diskusiji, mi podrazumevamo ResultSet.TYPE_SCROLL_INSENSITIVE podešavanje za scrollable rezultujući set. Ovo podrazumeva da rezultujući set ne „oseća“ izmene baze podataka koja se desila posle izvršavanja upita.)

• Da li želite da omogućite ažuriranje baze podataka editovanjem rezultujućeg seta ? (Pogledajte sledeću sekciju za više detalja)

Npr., ako jednostavno želite da omogućite scroll kroz set rezultata, ali ne želite da omogućite editovanje tih podataka, koristite: Statement stat = conn.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);

Page 40: Programiranje baza podataka 01

Achilles

Table 7: Tip vrednosti seta rezultata TYPE_FORWARD_ONLY Set rezultata nije pokretan TYPE_SCROLL_INSENSITIVE Set rezultata je pokretan ali nije osetljiv na

promene baze podataka. TYPE_SCROLL_SENSITIVE Set rezultata je pokretan i osetljiv na promene

baze podataka.

Table 8: ResultSet Concurrency Values CONCUR_READ_ONLY Set rezultata ne može biti korišćen za ažuriranje

baze podataka CONCUR_UPDATABLE Set rezultata može biti korišćen za ažuriranje

baze podataka Svi rezultati vraćaju se pozivom motode ResultSet rs = stat.executeQuery(query) i sada su scrollable. Scrollable rezultujući set ima cursor koji pokazuje na trenutnu poziciju. Scrolling je veoma jednostavan. Koristite If (rs.previous())… da scroll-ujete unazad. Metoda vraća true ako je cursor pozicioniran na

aktuelnom redu; false ako sada pokazuje na poziciju pre prvog reda. Vi mozete pomerati kursor napred ili nazad preko brojnih redova

pomoću komande rs.relative(n); Ako je n negativno, to je pomarenje unazad. Ako je n nula, poziv nema

efekta. Ako pokušate da pomerite kursor van trenutnog seta redova, to je ma koja tacka posle poslednjeg reda ili pre prvog reda, zavisno od znaka n.

Tada metoda vraća false i cursor se ne pomera. Metoda vraća true ako je cursor pozicioniran na aktuelni red.

Alternativno, možete podesiti kursor na broj reda: rs.absolute(n); Vi dobijate trenutni broj reda koji ste pozvali int currentRow = rs.getRow(); Prvi red u resultujućem setu ima broj 1. Ako je vraćena vrednost 0,

kursor trenutno nije na tom redu – nalazi se pre prvog reda ili posle poslednjeg.

Konvencione metode su first last beforeFirst afterLast pomera kursor na prvu, poslednju, pre prve, posle poslednje pozicije.

Page 41: Programiranje baza podataka 01

Achilles

Konačno metode isFirst isLast isBeforeFirst isAfterLast testiraju da li je kursor na nekoj od određenih pozicija. Korišćenje scrollable rezultujućeg seta je veoma jednostavno. Težak

posao keširanja upitnih podataka je nevidljiv pomoću database drivera. Ažurirajući set rezultata

Ako želite da omogućite editovanje podataka iz seta rezultata i da

imate automatsko reflektovanje izmena u bazi podataka, vi kreirajte ažurirajući set rezultata. Ažurirajući set rezultata ne mora biti scrollable, ali ako prezentujete podatke korisniku za editovanje, često želite da omogućite takođe scrolling.

Da bi dobili ažurirajući set rezultata, kreirajte iskaz kao što sledi. Statement stat = conn.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE,

ResultSet.CONCUR_UPDATABLE); Rezultujući set vraćen kao poziv executeQuery ima tada mogućnost

ažuriranja. Npr. Predpostavimo da želite da povećate cenu nekim knjigama, ali

nemate jednostavan kriterijum za UPDATE komandu. Stoga, vi možete proći kroz sve knjige i ažurirati cene, na osnovu svojevoljnih uslova.

String query = “SELECT * FROM Books”; ResultSet rs = stat.executeQuery(query); while (rs.next()) {

if(. . .) { double increase = . . . double price = rs.getDouble(“Price”);

rs.updateDouble(“Price”, price + increase); rs.updateRow();

} } Postoje updateXxx metode za sve tipove podataka koje odgovaraju SQL tipovima, kao updateDouble, updateString itd. Kao i sa getXxx metodama, vi odredite ime ili broj kolone. Posle toga odredite novu vrednost za polje.

Page 42: Programiranje baza podataka 01

Achilles

Metoda updateXxx menja samo vrednosti redova, ne bazu podataka. Kada završite sa ažuriranjem polja u redu, morate pozvati metodu updateRow. Metoda šalje sva ažuriranja u trenutni red baze podataka. Ako pomerite kursor na neki drugi red bez poziva updateRow, sve promene su odbačene iz seta redova i nikad neće komunicirati sa bazom podataka. Takođe možete pozvati metod cancelRoeUpdates da otkažete ažuriranja tekućeg reda.

Prethodni primer prikazuje kako da modifikujemo postojeći red. Ako želite da dodate novi red u bazu podtaka, prvo koristite moveToInsertRow metodu za pomeranje kursora na specijalnu poziciju, zvanu insert row. Napravićete novi red na poziciji za ubacivanje reda koristeći updateXxx instrukcije. Konačno, kada završite, pozovite metodu insertRow da bi dostavili novi red u bazu podataka. Kada završite sa ubacivanjem, pozovite moveToCurrentRow za pomeranje kursora nazad na poziciju pre poziva moveToInsertRow. Evo primera:

rs.moveToInsertRow(); rs.updateString(“Title”, title); rs.updateString(“ISBN”, isbn); rs.updateString(“Publisher_Id”, price); rs.updateDouble(“Price”, price); rs.insertRow(); rs.moveToCurrentRow(); Obratite pažnju da ne možete uticati where novi podatak dodat u

rezultujućem setu ili bazi podataka Konačno, možete obrsati red ispod kursora. rs.deleteRow(); Metoda deleteRow odmah briše red i iz rezultujućeg seta i iz baze

podataka. Metode updateRow, insertRow i deleteRow iz klase ResultSet dopuštaju

istu moć kao izvršavanje SQL komandi UPDATE, ISERT I DELETE. Ipak, programeri koji su navikli na Java programski jezik, mogu smatrati prirodnijim da manipulišu sadržajem baze podataka preko seta rezultata nego pravljenjem SQL iskaza.

java.sql.Connection 1.1

• Iskaz createStatement (int type, int concueeency) 1.2 • PreparedStatement prepareStatement (String command, int type, int

concurrency) 1.2 Kreira iskaz ili priprema uskaz koji zahteva rezultujuci set sa datim tipovima i concurrency. Parameters: command Komanda za pripremu

Page 43: Programiranje baza podataka 01

Achilles

Type Jedna od konstanti TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE ili TYPE_SCROLL_SENSITIVE iz interfejsa ResultSet-a.

Concurrency Jedna od konstanti

CONCUR_READ_ONLY ili CONCUR_UPDATABLE iz ResultSet interfejsa

• SQL Warning getWarnings() Vraća prvu nastupajuću grešku na toj konekciji ili null ako nema nastupanja grešaka. Upozorenja su olančana zajedno – nastavi pozivanje getNextWarning u povratnom objektu SQLWarning dok taj metod vraća null. Ovaj poziv ne uništava greške. Klasa SQLWarning nasleđuje SQLException. Koristi nasleđenu getErrorCode i getSQLState za analizu grešaka. • void clearWarnings() Briše sva upozorenja koja su prijavljena za ovu konekciju

java.sql.ResultSet 1.1

• int getType() 1.2 vraća tip seta rezultata, jedan od TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE ili TYPE_SCROLL_SENSITIVE • int getConcurrency() 1.2 vraća podešavanje concurrency ovog seta rezultata, jedan od CONCUR_READ_ONLY ili CONCUR_UPDATABLE. • boolean previous() 1.2 Pomera kursor na prethodni red. Vraća true ako je kursor pozicioniran na red ili false ako je kursor pozicioniran pre prvog reda. • int getRow() 1.2 Uzima broj trenutnog reda. Brojevi redova počinju od 1. • boolean absolute(int r) 1.2 Pomera kursor na red r. Vraća true ako je kursor pozicioniran na dati red. • boolean relative(int d) 1.2 Pomera kursor za d redova. Ako je d negativno, kursor se pomera unazad, Vraća true ako je kursor pozicioniran na red. • boolean first() 1.2 • boolean last() 1.2 Pomera kursor na prvi ili poslednji red. Vraća true ako je kursor pozicioniran na red. • void beforeFirst() 1.2 • void afterLast() 1.2 Pomera kursor pre prvog ili posle poslednjeg reda. • boolean isFirst() 1.2 • boolean isLast() 1.2

Page 44: Programiranje baza podataka 01

Achilles

Proverava da li je kursor na prvom ili poslednjem redu. • boolean isBeforeFirst() 1.2 • boolean isAfterLast() 1.2 Proverava da li je kursor pre prvog ili posle poslednjeg reda. • void moveToInsertRow() 1.2 pomera kursor na ubačeni red. Ubaceni red je specijalni red za ubacivanje novog podatka pomoću metoda updateXxx I insertRow. • void moveToCurrentRow() 1.2 pomera kursor nazad sa ubačenog reda na red koji je bio zauzet kada je pozvan metod moveToInsertRow. • void insertRow() 1.2 ubacuje sadržaj ubačenog reda u bazu podataka i set rezultata. • void deleteRow() 1.2 briše trenutne redove iz baze podataka i seta rezultata. • void updateXxx (int column, Xxx data) 1.2 • void updateXxx (String columnName, Xxx data) 1.2 (Xxx je tip kao int, double, String Data itd.) ažurira polje u trenutnom redu seta rezultata • void updateRow () 1.2 Šalje promene trenutnog reda bazi podataka • void cancelRowUpdates() 1.2 Otkazuje trenutne promene reda.

java.sql.DatabaseMetaDate 1.1

• boolean supportsResultSetType(int type) 1.2 Vraća true ako baza podataka može da podrži set rezultata sa datim tipm.

Parameters: type Jedan od konstanti TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE ili TYPE_SCROLL_SENSITIVE iz interfejsa ResultSet

• boolean supportsResultSetConcurrency(int type, int concurency) 1.2 Vraća true ako baza podataka može da podrži set rezultata zadate kombinacije tipa i concurrency.

Parameters: type Jedna od konstanti TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENITIVE ili TYPE_SCROLL_SENSITIVE iz interfejsa ResultSetKomanda za pripremu

Concurrency Jedna od konstanti CONCUR_READ_ONLY ili

CONCUR_UPDATABLE iz intefejsa ResultSet.

Page 45: Programiranje baza podataka 01

Achilles

Metapodaci U prethodnom delu, videli ste kako da popunite, postavljate upite i

azurirate tabele baza podataka. Ipak, JDBC vam moze dati dodatne informacije o strukturi baze podataka i njenih tabela. Npr. mozete dobiti listu tabela određene baze podataka ili imena kolona i tipove tabela. Ova informacija nije korisna kada implementirate poslovnu aplikaciju sa predefinsanom bazom podataka. Povrh svega, ako kreirate tabele, vi znate njihovu strukturu. Strukturna informacija je, ipak, veoma korisna za programere koji pisu alate za rad sa bazama podataka.

U ovom delu, prikzaćemo vam kako da napišete neki jednostavan alat. Taj alat dopušta razgledanje tabela iz baze podataka.

Combo box na vrhu prikazuje sve tabele u bazi podataka. Izaberite jednu od njih i centar frejma će se ispuniti sa imenima polja te tabele i vrednostima prvog zapisa, kao što je prikazano na slici 8. Kliknite Next za kretanje kroz zapise tabele.

Mnoge baze podataka imaju mnogo više sofisticiranijih alata za

gledanje i editovanje tabela. Ako vaša baza nema, proverite SQL- Viewer (http://isql.sourceforge.net) ili SQuirreL (http://squirrel-sql.sourceforge.net). Ovi programi mogu da vide tabele u bilo kojoj JDBC bazi podataka. Naš primer program nije namenjen kao zamena za te alate, ali prikazuje kako implementirati alat za rad sa proizvoljnim tabelama.

U SQL, podaci koji opisuju bazu podataka ili jedan njen deo su nazvani metapodaci (za razlikovanje njih od stvarnih podataka sačuvanih u bazi podataka). Možete dobiti tri vrste metapodataka: o bazi podataka, o setu rezultata, i o parametrima pripremljenih iskaza.

Za otkrivanje više o bazi podataka, zahtevajte objekat tipa DatabaseMetaDate od konekcije sa bazom podataka

DatabaseMetaData meta = conn.getMetaData(); Sada ste spremni da dobijete neke metapodatke. Npr, pozovite ResultSet mrs = meta.getTables(null, null, null, new String[]

{„TABLE“}); Vraća set rezultata koji sadrži informacije o svim tabelama u bazi

podataka (Pogledaj API belešku o drugim parametrima za ovaj metod.) Svaki red u setu rezultata sadrži informaciju o tabeli u bazi podataka.

Mi jedino brinemo o trećoj koloni, imenu tabele. (Ponovo pogledajte API belšku za ostale kolone). Prema tome rs.getString(3) je ime tabele. Ovo je kod koji popunjava Combo box.

while (mrs.next()) tableNames.addItem(mrs.getString(3)); rs.close()

Page 46: Programiranje baza podataka 01

Achilles

Klasa DatabaseMetaData daje podatke o bazi podataka. Druga klasa metapodataka, ResultSetMetaData, izveštava informacije o setu rezultata. Kad god imamo set rezultata od upita, možemo ispitati broj kolona i ime svake kolone, tip i širinu polja.

Mi koristimo ove informacije da napravimo oznaku za ime svake kolone i polje teksta sa dovoljnom veličinom za svaku vrednost

ResultSet mrs = stat.executeQuery(“SELECT * FROM” + tableName); ResultSetMetaData meta = mrs.getMetaData(); for (int I =1; i <= meta.getColumnCount(); i++) { String columnName = meta.getColumnLabel(i);

int columnWidth = meta.getColumnDisplaySize(i); JLabel l = new Label (columnName); JTextField tf = new TextField (columnWidth); . . .

} Postoji i druga važna upotreba metapodataka baze podataka. Baze podataka su složene i SQL standard ostavlja mnogo prostora za variacije. Preko stotine metoda u klasi DatabaseMetaData mogu ispitati o bazi podataka, iključujući pozive sa neobičnim imenima kao meta.supportsCatalogsInPrivilegeDefinitions() i meta.nullPlusNonNullIsNull()

Jasno, ovo je oprema naprednih korisnika sa specijalnim potrebama, u stvari, za one koji moraju da pišu veoma prenosive kodove koji rade sa više baza podataka. U našem primeru prgrama, mi dajemo samo jedan primer ove tehnike. Pitamo metapodatke baze podataka da li JDBC drajver podržava kretanje kroz set rezultata. Ako je tako, mi otvaramo šetajući set rezultata i dodajemo dugme Previous za vraćanje nazad.

if (meta.supportsResultSetType (ResultSet.TYPE_SCROLL_INSENSITIVE)). . .

Sledeći koraci ukratko opisuju probu programa 1. Dodajemo Combi box za ime tabele, panel koji prikazuje

vrednosti tabele i dugme panel. 2. Povezivanje sa bazom podataka. Otkrivanje da li podržava

pomerajući set rezultata. Ako podržava, pravljenje objekta Statement za proizvodnju pomerajućeg seta rezultata. Inače, samo kreiranje default Statement.

3. Uzimanje imena tebele i punjenje unutar izabrane komponente.

4. Ako je podržan scrolling, dodaj dugme Previous. Uvek dodaj dugme Next.

5. Kada korisnik izabre tabelu, napravi upit da vidiš sve njene vrednosti. Vidi set rezultata metapodataka. Izbaci stari scroll

Page 47: Programiranje baza podataka 01

Achilles

pane od cetralnog panela. Napravi panel koji sadrži raspored rešetkaste vreće sa oznakom i polja za tekst. Dodaj to u frejm i pozovi metod validate za ponovno raspoređivanje frejma. Zatim, pozovi showNextRow da prikažeš prvi red.

6. Pozovi metodu showNextRow za prikazivanje prvog zapisa i takođe kad god korisnik klikne dugme Next. Metoda showNextRow uzima sledeći red tabele i puni vrednosti kolona vrednošću unutar tekst boksa.

7. Postoji beznačajna finoća u detektovanju kraja seta rezultata. Kada je set rezultata scrollable, mi možemo jednostavno koristiti metodu isLast. Ali kada nije scrollable, poziv ove metode će uzrokovati izuzetak (ili čak JVM grešku ako je drajver JDBC 1 drajver). Zato, mi koristimo različite strategije za non-scrollable set rezultata. Kada rs.next() vraća false, mi zatvaramo set rezultata i podesavamo rs na null.

8. Dugme Previous poziva showPerviousRow, koje pomera set rezultata unazad. Pošto je ovo dugme instalirano samo kada je set rezultata scrollable, mi znamo da su metode previous i isFirst podržane.

9. Metoda showRow jednostavno puni sva polja seta rezultata unutar tekstualnog polja na data panelu.

Primer 4: ViewDB.java

import java.net.*; import java.sql.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; import javax.swing.*; /** Ovaj program koristi metapodatke za ispisivanje proizvoljnih tabela beze podataka */ public class ViewDB { public static void main(String[] args) { JFrame frame = new ViewDBFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

Page 48: Programiranje baza podataka 01

Achilles

frame.setVisible(true); } } /** Prozor koji sadrži panel sa podacima i dugmićima za navigaciju */ class ViewDBFrame extends JFrame { public ViewDBFrame() { setTitle("ViewDB"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); tableNames = new JComboBox(); tableNames.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { showTable((String) tableNames.getSelectedItem()); } }); add(tableNames, BorderLayout.NORTH); try { conn = getConnection(); meta = conn.getMetaData(); createStatement(); getTableNames(); } catch (SQLException e) { JOptionPane.showMessageDialog(this, e); } catch (IOException e) { JOptionPane.showMessageDialog(this, e); } JPanel buttonPanel = new JPanel(); add(buttonPanel, BorderLayout.SOUTH); if (scrolling)

Page 49: Programiranje baza podataka 01

Achilles

{ previousButton = new JButton("Previous"); previousButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { showPreviousRow(); } }); buttonPanel.add(previousButton); } nextButton = new JButton("Next"); nextButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { showNextRow(); } }); buttonPanel.add(nextButton); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent event) { try { if (conn != null) conn.close(); } catch (SQLException e) { while (e != null) { e.printStackTrace(); e = e.getNextException(); } }

Page 50: Programiranje baza podataka 01

Achilles

} }); } /** Kreira objekte iskaza koji se koriste za izvršavanje upita. Ako baza podataka podržava pomerajući kursor, onda se piše iskaz koji će ih napraviti */ public void createStatement() throws SQLException { if (meta.supportsResultSetType( ResultSet.TYPE_SCROLL_INSENSITIVE)) { stat = conn.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); scrolling = true; } else { stat = conn.createStatement(); scrolling = false; } } /** Uzima imena svih tabela iz baze podataka I dodaje ih combo box-u */ public void getTableNames() throws SQLException { ResultSet mrs = meta.getTables(null, null, null, new String[] {"TABLE"}); while (mrs.next()) tableNames.addItem(mrs.getString(3)); mrs.close(); }

Page 51: Programiranje baza podataka 01

Achilles

/** Priprema tekstualno polje za prikazivanje nove tabele, i prikazuje prvi red. @param tableName ime tabele za prikazivanje */ public void showTable(String tableName) { try { if (rs != null) rs.close(); rs = stat.executeQuery("SELECT * FROM" + tableName); if (scrollPane != null) remove(scrollPane); dataPanel = new DataPanel(rs); scrollPane = new JScrollPane(dataPanel); add(scrollPane, BorderLayout.CENTER); validate(); showNextRow(); } catch (SQLException e) { JOptionPane.showMessageDialog(this, e); } } /** Pomera na prethodni red tabele */ public void showPreviousRow() { try { if (rs == null || rs.isFirst()) return; rs.previous(); dataPanel.showRow(rs); } catch (SQLException e) { JOptionPane.showMessageDialog(this, e);

Page 52: Programiranje baza podataka 01

Achilles

} } /** Pomera na sledeći red tabele. */ public void showNextRow() { try { if (rs == null || scrolling && rs.isLast()) return; if (!rs.next() && !scrolling) { rs.close(); rs = null; return; } dataPanel.showRow(rs); } catch (SQLException e) { JOptionPane.showMessageDialog(this, e); } } /** Prihvata konekciju sa osobinama specificiranim u fajlu database.properties @vraća konekciju sa bazom podataka */ public static Connection getConnection() throws SQLException, IOException { Properties props = new Properties(); FileInputStream in = new FileInputStream("database.properties"); props.load(in); in.close(); String drivers = props.getProperty("jdbc.drivers");

Page 53: Programiranje baza podataka 01

Achilles

if (drivers != null) System.setProperty("jdbc.drivers", drivers); String url = props.getProperty("jdbc.url"); String username = props.getProperty("jdbc.username"); String password = props.getProperty("jdbc.password"); return DriverManager.getConnection(url, username, password); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; private JButton previousButton; private JButton nextButton; private DataPanel dataPanel; private Component scrollPane; private JComboBox tableNames; private Connection conn; private Statement stat; private DatabaseMetaData meta; private ResultSet rs; private boolean scrolling; } /** Ovaj panel prikazuje sadržaj seta rezultata. */ class DataPanel extends JPanel { /*** Konstruiše panel sa podacima @param rs the set rezultata čiji sadržaj panel predstavlja */ public DataPanel(ResultSet rs) throws SQLException { fields = new ArrayList<JTextField>(); setLayout(new GridBagLayout());

Page 54: Programiranje baza podataka 01

Achilles

GridBagConstraints gbc = new GridBagConstraints(); gbc.gridwidth = 1; gbc.gridheight = 1; ResultSetMetaData rsmd = rs.getMetaData(); for (int i=1; i<=rsmd.getColumnCount(); i++) { gbc.gridy = i - 1; String columnName = rsmd.getColumnLabel(i); gbc.gridx = 0; gbc.anchor = GridBagConstraints.EAST; add(new JLabel(columnName), gbc); int columnWidth = rsmd.getColumnDisplaySize(i); JTextField tb = new JTextField(columnWidth); fields.add(tb); gbc.gridx = 1; gbc.anchor = GridBagConstraints.WEST; add(tb, gbc); } } /** Prikazuje red baze podataka popunjavajući sva tekstualna polja vrednostima kolone. */ public void showRow(ResultSet rs) throws SQLException { for (int i=1; i<= fields.size(); i++) { String field = rs.getString(i); JTextField tb = (JTextField) fields.get(i - 1); tb.setText(field); }

Page 55: Programiranje baza podataka 01

Achilles

} private ArrayList<JTextField> fields;

}

java.sql.Connection 1.1

• DatabaseMetaData getMetaData() Vraća metapodatke za konekciju kao objekat DatabaseMetaData.

java.sql.DatabaseMetaData 1.1

• ResultSet getTables(String catalog, String schemaPattern, String

tableNamePattern, String types[]) uzima opis svih tabela u katalogu kojima se podudara šema i ime tabele patterns i kriterijum tip. (Schema opisuje grupu povezanih tabela i pristupnih dozvola. Catalog opisuje povezanu grupu schema. Ovi koncepti su važni za struktuiranje velikih baza podataka.) Parametri Catalog i Schema mogu biti “ “ za obnavljanje tabela bez catalog-a ili schema-a ili null za vraćanje tabela bez obzira na katalog ili shemu. Tip niz sadrži imena tipa tabele koji ukljućuje. Tipični tipovi su TABLE, VIEW, SYSTEM, TABLE, GLOBAL TEMPORARY, LOCAL TEMPORARY, ALIAS i SYNONYM. Ako je tip null, tada se vraća tabela svih tipova Set rezultata ima pet kolona, sve one tipa string su prikazane u tabeli 9

• int getJDBCMajorVersion() • int getJDBCMinorVersion() (JDBC 3) vraća major i minor JDBC verzije brojeva drajvera koji uspostavljaju konekciju sa bazom podataka. Npr. JDBC 3.0 drajver ima major verziju broj 3 i minor verziju broj 0. • int getMaxConnections() Vraća maksimalan broj konkurentskih konekcija sa tom bazom podataka.

Table 7: Pet kolona metode getTables 1. TABLE_CAT Katalog tabele (može biti

null) 2. TABLE_SCHEM Shema tabele (može biti

null) 3. TABLE_NAME Ime tabele 4. TABLE_TYPE Tip tabele 5. REMARKS Komentari tabele

Page 56: Programiranje baza podataka 01

Achilles

• int getMaxStatements() Vraća maksimalan broj konkurentski otvorenih iskaza po konekciji sa bazom podataka ili 0 ako je broj neograničen ili nepoznat.

java.sql.ResutlSet 1.1

• ResultSetMetaData getMetaData() Daje metapodatke udružene sa tekućom kolonom ResultSet

java.sql.ResultSetMetaData 1.1 • int getColumnCount() Vraća broj kolona u trenutnom ResultSet objektu. • int getColumnDisplaySize(int column) Govori vam maksimalnu širinu kolone specificiranu index parametrom Parameters: column The column number • String getColumnLabel(int column) Daje predlog za naslov kolone Parameters: column The column number • String getColumnName(int column) Daje ime kolone udruženo sa specifikacojom indeks kolone

Parameters: column The column number

Podešavanje redova

Scrollable set rezultata je moćan. Treba da sačuvate otvorenu

konekciju sa bazom podataka tokom cele interakcije sa korisnikom. Međutim, korisnik može otići od svog kompjutera na duže vreme, ostavljajući konekciju zauzetom. To nije dobro – veza sa bazom podataka je deficitaran resurs. U takvoj situaciji koristimo row set. Interfejs RowSet nasleđuje interfejs ResultSet, ali row set ne mora biti vezan za konekciju sa bazom podataka.

Row set su takođe pogodni ako treba da pomerimo rezultat upita na drugi red komplekse aplikacije, ili na drugi uređaj kao telofon. Vi nikad nećete želieti da pomerate set rezultata - njegova struktura podataka može biti ogromna, i povezan je sa konekcijom baze podataka.

Paket javax.sql.rowset obezbeđuje sledeće interfejse koji nasleđuju RowSet interfejs:

• CachedRowSet dopušta operaciju bez konekcije. Raspravljaćemo o cached row setovima u sledećem delu.

• WebRowSet je keširan set redova koji može biti sačuvan kao XML fajl. XML fajl može biti pomeren na drugi red web aplikacije, gde može da se otvori preko drugog WebRowSet objekta

• FilteredRowSet i JoinRowSet interfejsi podržavaju lake operacije sa setovima redova koji su ekvivalentni SQL SELECT I JOIN

Page 57: Programiranje baza podataka 01

Achilles

operacijama. Ove operacije se prnose na podatake sačuvane u setovima redova, bez pravljenja konekcije sa bazom podataka.

• JdbcRowSet je tanak omot oko ResultSet. Dodaje korisne getters i setters od RowSet interfejsa, pretvarajući result set u “zrna” (Pogledajte poglevlje 8 za više informacija o zrnima)

Sun Microsystems očekuje od proizvođača baza podtaka proizvodnju efikasnih implementacija ovog interfejsa. Srećom, oni takođe snabdevaju preporuku impelmentacije tako koji možete koristiti row set čak ako ih proizvođac baze podataka ne podržava. Preporuke implementacije su deo JDK 5.0. Vi takođe možete download-ovati to sa http://java.sun.com/jdbc. Prepruke implementacije su u paketu com.sun.rowset

Cached Row Sets

Cached row set sadrži sve podatke iz seta rezultata. Zato što

CachedRowSet nasleđuje ResultSet interfejs, vi možete koristiti cached row set baš kao što bi koristili set rezultata. Cached row sets pruža važnu korist: Možete zatvoriti konekciju iI još uvek koristiti row set. Kao što ćete videti u našem primer programu, ovo u mnogome olakšava implementaciju interaktivne aplikacije. Svaka komanda korisnika jednostavno otvara konekciju sa bazom podataka, postavlja upite, stavlja rezultate u row set iI onda zatvara konekciju sa bazom podataka.

Čak je moguće modifikovati podatke u cached row set. Naravno, modifikacije se ne reflektuju odmah u bazi podataka. Umesto toga, treba napraviti eksplicitan zahtev za prihvatanje akumuliranih promena. CachedRowSet se zatim ponovo konektuje sa bazom podataka i pravi SQL komande za zapisivanje akumuliranih promena.

Naravno, cached row sets nisu prikladni za velike rezultate upita. Bilo bi veoma neefikasno pomerati veliki broj zapisa iz baze podataka u memoriju, pogotovo ako korisnici gledaju samo nekoliko od njih.

Možete popuniti CachedRowSet od seta rezultata: ResultSet result = stat.executeQuery(queryString); CachedRowSet rowset = new com.sun.rowset.CachedRowSetImpl();

//ili koristite neku implementaciju od proizvođaca vaše baze podataka rowset.populate(result);

conn.close(); // sada je ok da zatvorite konekciju sa bazom podataka Alternativno, možete pustiti da CachedRowSet objekat uspostavi

automatski konekciju. Podesite parametre baze podataka: rowset.setURL(“jdbc:mckoi://localhost/”); rowset.setUsername(“dbuser”); rowset.setPassword(“secret”);

Page 58: Programiranje baza podataka 01

Achilles

Zatim podesite komandu upita rowset.setCommand(“SELECT * FROM Books”); Konačno, popunite row set sa rezultatom upita: rowset.execute(); Ovaj poziv uspostavlja konekciju sa bazom podataka, piše upit,

popunjava row set i prekida vezu. Možete pregledati i modifikovati row set sa istom komandom koju

koristite za result set. Ako modifikujete sadržaj row set-a, morate napisati to u bazi podataka pozivom:

rowset.acceptCnanges(conn); ili rowset.acceptChanges(); Drugi poziv radi samo ako ste konfigurisali row set sa informacijom

(kao sto su URL, user name i password) koje su zahtevane za konekciju sa bazom podatka.

Na jednoj od prethodnih strana, videli ste da nisu svi result sets sa mogućnošću ažuriranja. Slicno, row set koji sadrzi rezultat kompleksnih upita nece biti u mogućnostida zapiše promene u bazu podataka. Trebali bi da budete sigurni ako vas row set sadrzi podatke iz jedne table.

Druga složenost prositiče ako su podaci iz baze podataka promenjeni posle pounjavanja row set-a. Ovo je jasan znak problema koji može voditi protivrečnim podacima. Napomena implementacije proverava da li su originalne vrednosti row set (tj. vrednost pre editovanja) identične trenutnim vrednostima u bazi podataka. Ako jeste, oni su zamenjeni sa editovanim vrednostima. Inače, SyncProviderException je odbačena i nijedna promena nije upisana. Druge implementacije mogu koristiti druge strategije za sinhronizaciju.

Program u primeru 5 je identičan onom iz primera 4. Međutim, mi sada koristimo cached row set. Programska logika je u mnogome pojednostavljena

• Mi jednostavno otvaramo i zatvaramo konekciju u svakoj akciji slušalac

• Ne treba nam više “window closing” slučaj za zatvaranje konekcije

• Ne brinemo dugo da li je result set scrollable. Row set je uvek scrollable.

Page 59: Programiranje baza podataka 01

Achilles

Primer 5: RowSetTest.java import com.sun.rowset.*; import java.net.*; import java.sql.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; import javax.swing.*; import javax.sql.*; import javax.sql.rowset.*; /** Ovaj program koristi metapodatke da prikaže proizvoljnu tabelu baze podataka. */ public class RowSetTest { public static void main(String[] args) { JFrame frame = new RowSetFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } } /** Prozor koji sadrži panel sa podacima i dugmiće za navigaciju. */ class RowSetFrame extends JFrame { public RowSetFrame() { setTitle("RowSetTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); tableNames = new JComboBox(); tableNames.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { showTable((String) tableNames.getSelectedItem()); }

Page 60: Programiranje baza podataka 01

Achilles

}); add(tableNames, BorderLayout.NORTH); try { Connection conn = getConnection(); try { DatabaseMetaData meta = conn.getMetaData(); ResultSet mrs = meta.getTables(null, null, null, new String[] {"TABLE"}); while (mrs.next()) tableNames.addItem(mrs.getString(3)); } finally { conn.close(); } } catch (SQLException e) { JOptionPane.showMessageDialog(this, e); } catch (IOException e) { JOptionPane.showMessageDialog(this, e); } JPanel buttonPanel = new JPanel(); add(buttonPanel, BorderLayout.SOUTH); previousButton = new JButton("Previous"); previousButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { showPreviousRow(); } }); buttonPanel.add(previousButton); nextButton = new JButton("Next"); nextButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event)

Page 61: Programiranje baza podataka 01

Achilles

{ showNextRow(); } }); buttonPanel.add(nextButton); deleteButton = new JButton("Delete"); deleteButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { deleteRow(); } }); buttonPanel.add(deleteButton); saveButton = new JButton("Save"); saveButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { saveChanges(); } }); buttonPanel.add(saveButton); } /** Priprema tekstualna polja za prikazivanje nove tabele, i prikazuje prvi red. @param tableName ime tabele za prikazivanje */ public void showTable(String tableName) { try { //open connection Connection conn = getConnection(); try { //get result set Statement stat = conn.createStatement(); ResultSet result = stat.executeQuery("SELECT * FROM" + tableName); //copy into row set

Page 62: Programiranje baza podataka 01

Achilles

rs = new CachedRowSetImpl(); rs.setTableName(tableName); rs.populate(result); } finally { conn.close(); } if(scrollPane != null) remove(scrollPane); dataPanel = new DataPanel(rs); scrollPane = new JScrollPane(dataPanel); add(scrollPane, BorderLayout.CENTER); validate(); showNextRow(); } catch (SQLException e) { JOptionPane.showMessageDialog(this, e); } catch (IOException e) { JOptionPane.showMessageDialog(this, e); } } /** Pomera na prethodni red tabele */ public void showPreviousRow() { try { if (rs==null || rs.isFirst()) return; rs.previous(); dataPanel.showRow(rs); } catch (SQLException e) { System.out.println("Error " + e); } } /** Pomera na sledeći red tabele */

Page 63: Programiranje baza podataka 01

Achilles

public void showNextRow() { try { if (rs == null || rs.isLast()) return; rs.next(); dataPanel.showRow(rs); } catch (SQLException e) { JOptionPane.showMessageDialog(this, e); } } /** Briše trenutni red tabele. */ public void deleteRow() { try { rs.deleteRow(); if (!rs.isLast()) rs.next(); else if (!rs.isFirst()) rs.previous(); else rs = null; dataPanel.showRow(rs); } catch (SQLException e) { JOptionPane.showMessageDialog(this, e); } } /** Čuva sve promene. */ public void saveChanges() { try { Connection conn = getConnection(); try { rs.acceptChanges(conn); } finally {

Page 64: Programiranje baza podataka 01

Achilles

conn.close(); } } catch (SQLException e) { JOptionPane.showMessageDialog(this, e); } catch (IOException e) { JOptionPane.showMessageDialog(this, e); } } /** Prihvata konekciju sa osobinama specificiranim u fajlu database.properties @vraća konekciju sa bazom podataka */ public static Connection getConnection() throws SQLException, IOException { Properties props = new Properties(); FileInputStream in = new FileInputStream("database.properties"); props.load(in); in.close(); String drivers = props.getProperty("jdbc.drivers"); if (drivers != null) System.setProperty("jdbc.drivers",drivers); String url = props.getProperty("jdbc.url"); String username = props.getProperty("jdbc.username"); String password = props.getProperty("jdbc.password"); return DriverManager.getConnection(url, username, password); } public static final int DEFAULT_WIDTH = 400; public static final int DEFAULT_HEIGHT = 200; private JButton previousButton; private JButton nextButton; private JButton deleteButton; private JButton saveButton; private DataPanel dataPanel; private Component scrollPane; private JComboBox tableNames;

Page 65: Programiranje baza podataka 01

Achilles

private CachedRowSet rs; } /** Ovaj panel prikazuje sadržaj seta rezultata */ class DataPanel extends JPanel { /** Formira panel sa podacima @param rs the set rezultata čiji sadržaj ovaj panel prikazuje. */ public DataPanel(RowSet rs) throws SQLException { fields = new ArrayList<JTextField>(); setLayout(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridwidth = 1; gbc.gridheight = 1; ResultSetMetaData rsmd = rs.getMetaData(); for (int i = 1; i<= rsmd.getColumnCount(); i++) { gbc.gridy = i-1; String columnName = rsmd.getColumnLabel(i); gbc.gridx = 0; gbc.anchor = GridBagConstraints.EAST; add(new JLabel(columnName), gbc); int columnWidth = rsmd.getColumnDisplaySize(i); JTextField tb = new JTextField(columnWidth); fields.add(tb); gbc.gridx = 1; gbc.anchor = GridBagConstraints.WEST; add(tb, gbc); } } /** Prikazuje red baze podataka popunjavajući sva tekstualna polja vrednostima kolone */ public void showRow(ResultSet rs) throws SQLException { for (int i=1; i <=fields.size(); i++)

Page 66: Programiranje baza podataka 01

Achilles

{ String field = rs.getString(i); JTextField tb = (JTextField) fields.get(i-1); tb.setText(field); } } private ArrayList<JTextField> fields; }

java.sql.RowSet 1.4

• String getURL() • void setURL (String url) Uzima ili podešava URL baze podataka • String getUsername() • void setUsername (String username) Uzima ili podešava korisničko ime za konekciju sa bazom podataka • String getPassword() • void setPassword (String password) Uzima ili podešava password za konekciju sa bazom podataka • String getCommand() • void setCommand (String comand) Uzima ili podešava komandu koja izvršava nastavnjivanje tog row set-a. • void execute() Nastanjuje ovaj row set, korišćenjem seta naredbi sa setCommand. Da bi drajver menadžer uspostavio konekciju, URL, usermane, i password moraju biti podešeni.

java.sql.rowSet.CachedSet 5.0

• void execute (Connection conn) Nastanjuje ovaj row set, korišćenjem seta naredbi sa setCommand. Ovaj metod koristi datu konekciju i zatvara je. • void populate (ResultSet result) nastanjuje ovaj cached row set sa podacima dobijenim iz seta rezultata. • String getTableName() • void getTableName(String tableName) Uzima ili podešava ime tabele od koje ovaj row set je nastanjen. • void acceptChanges() • void acceptChanges(Connection conn) Ponovo se konektuje se sa bazom podataka i upisuje promene koji su rezultat editovanja row set-a. Može izbaciti SyncProviderException ako podaci ne mogu biti upisani zato što su podaci baze podatake promenjeni.

Page 67: Programiranje baza podataka 01

Achilles

Transakcije Možete grupisati set iskaza de formirate transakciju. Transakcija može

biti izvršena ako sve dobro prođe. Ili, ako se neka greška dogodi u jednoj od njih, to može biti rolled back kao da nijedna komanda nije napisana.

Glavni razlog za grupisanje komandi u transakciju je integritet baze podataka. Npr., pretpostavimo da želimo transfer novca sa jednog bankovnog računa na dugi. Stoga je važno da mi simultano zadužimo jedan račun i kredit drugog. Ako je sistem propao pre kreditiranja drugog računa, zaduživanje mora biti neizvršeno.

Ako grupišete iskaze za ažuriranje u transakcije, tada će transakcija ili uspeti u potpunosti i biti izvršena, ili će propasti zbog neke greške u sredini. U tom slučaju, možete izvršiti rollback i baza podataka automatski ponovo uradi efkte svih ažuriranja koji su se pojavili posle poslednje izvršene transakcije.

Po default-u, konekcija sa bazom podataka je autocommit, i svaka SQL komanda je upućena u bazu podataka ubrzo kad je i izvršena. Kad je jedna komada upućena, vi je ne možete vratiti nazad.

Da bi proverili podešavanje trenutnog autocommit moda, pozovite getAutoCommit metod iz klase Connection.

Isključite autocommit mod sa komandom conn.setAutoCommit(false); Sada kreirajte iskaz objekta na normalan nacin: Statement stat = conn.createStatement(); Pozovite executeUpdate ma koliko puta: stat.executeUpdate(command1); stat.executeUpdate(command2); stat.executeUpdate(command3); … Kada sve komande budu izvršene, pozovite commit metodu: conn.commit(); Ipak, ako je desila greška, pozovite conn.rollback(); Zatim, sve komande do poslednjeg commit-a su automatski obrnute.

Uobičajeno pozivate rollback kada je vaša transakcija prekinuta od SQLException.

Page 68: Programiranje baza podataka 01

Achilles

Save Points Možete postići finiju kontrolu rollback procesa korišćenjem save points.

Napravite save point oznake za tačke koje možete kasnije vratiti bez vraćanja na start transakcija. Npr.,

Statement stat = conn.createStatement(); // počinje transakcije rollback() kreće odavde

stat.executeUpdate(command1); Savepoint svpt = conn.setSavepoint(); // podešavanje savepoint;

rollback(svpt) kreće odavde stat.executeUpdate(command2); if(. . .) conn.rollback(svpt); // poništavanje efekta iz komande 2 . . . conn.commit(); Ovde, mi koristimo anonimni save point. Vi takođe možete dati ime

save point-u, kao savepoint svpt = conn.setSavepoint(“stage1”); Kada završite sa save point, trebali bi ih osloboditi: stat.releaseSavepoint(svpt);

Batch Updates Pretpostavimo da program zahteva izvršavanje mnogo INSERT iskaza

da bi popunio tabelu baze podataka. U JDBC 2 možete poboljšati preformanse programa korišćenjem batch update. U grupnom ažuriranju, niz komandi se sakuplja i izvršava kao grupa.

Komande u grupi mogu biti akcije kao INSERT, UPDATE i DELETE kao i komande definisane podacima kao što su CREATE TABLE i DROP TABLE. Ipak, vi ne možete dodati SELECT komandu pošto izvršavanje SELECT izkaza vraća set rezultata.

Za izvršavanje grupe, prvo kreirajte objekat Statement na uobičajeni način:

Statement stat = conn.createStatement(); Sada, umesto poziva executeUpdate, pozovite metodu addBatch: String command = “CREATE TABLE . . .” stat.addBatch(command); while(. . .) { command = “INSERT INTO . . . VALUES (“ + . . . +”)”; stat.addBatch(command); }

Page 69: Programiranje baza podataka 01

Achilles

Konačno, pošaljite sadržaj grupe: int[] counts = stat.executeBatch(); Poziv executeBatch vraća niz broja redova za sve poslate komande.

(Podsećamo da zaseban poziv executeUpdate vraća integer, u stvari, broj redova koji su pogođeni komandom.) U našem primeru, metoda executeBatch vraća niz sa prvim elementom jednakim 0 (zato što komanda CREATE TABLE proizvodi red broja 0) i svih ostalih elementa jednakih 1 (zato svaka komanda INSERT utiče na jedan red).

Da bi ste otklonili grešku u batch modu, treba da tretirate izvršenje batch-a kao jednu transakciju. Ako se grupa pokvari u sredini, želite da se vratite u stanje pre početka grupisanja.

Prvo, isključite autocommit mod, zatim sakipite grupu, izvršite je, obavezite je i konačno vratite originalni autocommit mod:

boolean autoCommit = conn.getAutoCommit(); conn.setAutoCommit(false); Statement stat = conn.getStatement(); . . . // nastavite pozivanje stat.addBatch(. . .); . . . stat.executeBatch(); conn.commit(); conn.setAutoCommit(autoCommit);

java.sql.Connection 1.1

• void setAutoCommit (bolean b) Podešava autocommit mod te konekcije na b. Ako je autocommit mod true, svi iskazi su angažovani i uskoro će njihovo izvršavanje biti završeno. • boolean getAutoCommit () Uzima autocommit mod te konekcije • void commit () Izvršave sve iskaze koji su napisani posle poslednjeg izvršavanja. • void rollback () poništava efekte svih iskaza koji su napisani posle poslednjeg izvršavanja • Savepoint setSavepoint () 1.4 Podešava neimenovani save point • Savepoint setSavepoint (String name) 1.4 Podešava imenovani save point • void rollback(Savepoint svpt) 1.4 Vraća se unazad do datog save point-a • void releaseSavepoint (Savepoint svpt) 1.4

Oslobađa dati save point

Page 70: Programiranje baza podataka 01

Achilles

java.sql.Savepoint 1.4

• int getSavepointId() Uzima ID od neimenovanog save point-a, ili izbacuje SQLException ako je to ime save point-a • String getSavepointId() uzima ime tog save point-a, ili izbacuje SQLException ako je to neimenovani save point.

java.sql.Statement 1.1

• void addBatch (String command) 1.2 Dodaje komandu u trenutnu grupu komandi za taj iskaz • int[] executeBatch () 1.2 Izvršava sve komande u trenutnoj grupi. Vraća niz broja redova, sadrži element za svaku komandu u grupi koja naznačava broj redova pogođenih tom komandom.

java.sql.DatabaseMetaData 1.1

• boolean supportsBatchUpdates() 1.2 Vraća true ako drajver podržava ažuriranje grupe.

Napredni menadzment konekcije

Pojednostavljeno podešavanje konekcije sa bazom podataka sa

database.properties fajlom, kao što smo opisali u prethodnom delu, je pogodno za male test programe, ali neće raditi za velike aplikacije.

Kada JDBC aplikaciju svrstamo u neko poslovno okruženje, menadžment konekcije sa bazom podataka je integrisana sa Java Naming and Directory Interface (JNDI). Opisi izvora podatka poduhvata se čuvaju u direktorijum. Korišćenje direktorijuma omogućava centralozovani menadžment korisničkog imena, lozinke, imena baze podataka i URL-om JDBC-a.

U takvom okruženju koristite sledeći kod da uspostavite konekciju sa bazom podataka:

Context jndiContext = new InitalContext(); DataSource source = (DataSource)

jndiContext.lookup(“java:comp/env/jdbc/corejava”); Connection conn = source.getConnection(); Obratite pažnju da DriverManager nije više uključen. Umesto njega,

JNDI servis locira izvor podataka. Izvor podataka je interfejs koji omogućava jednostavne JDBC konekcije kao i mnogo naprednije servise , kao što je

Page 71: Programiranje baza podataka 01

Achilles

izvšavanje distribuiranih transakcija koje uključuju višestruke baze podataka. Interfejs DataSource je definisan u javax.sql standardnom proširenju paketa.

Naravno, izvor podataka treba konfigurisati negde. Ako napišete program baze podataka koji se izvršava u servlet kontejnru kao što je Apache Tomcat ili u aplikativnom serveru kao BFA WebLogic, tada postavite konfiguraciju baze podatka (ukljucujuci JDBC URL, korisnicko ime I lozinku) u konfiguracioni fajl.

Menadžment korisnickog imena i logovanja je samo jedan od stvari koja zahteva specijalnu pažnju. Druga stvar je da obuhvata cenu uspostavljanja konekcije sa bazom podataka.

Naš primer programa baze podataka uspostavlja jednu konekciju sa bazom podataka, na početku programa i zatvara je na kraju programa. Ipak, u mnogim programiranim situacijama ovaj pristup neće raditi. Razmotrimo tipičnu web aplikaciju. Ovakva aplikacija opslužuje više zahteva paralelno. Višestruki zahtevi mogu zahtevati istovremeni pistup bazi podataka. U mnogim bazama podtaka konekcija nije planirana da bude deljena od više strana. Prema tome, svaki zahtev zahteva vlastitu konekciju. Jednostavni prstup je uspostavljanje konekcije za svaki zahtevanu stranu i zatvoriti je posle, ali to je veoma skupo. Uspostavljanje konekcije sa bazom podataka može potrošiti puno vremena. Konekcija su određene da bi se koristile za višestruke upite, ne da bi bile zatvorene posle jednog ili dva upita.

Rešenje je pool konekcija. Ovo sredstvo povezivanja sa bazom podataka se ne zatvara fizički, već se čuva u redu da bi se ponovo koristilo. Konekcija pooling je važan servis i JDBC specifikacija obezbeđuje alate implementatorima za nabavku tog. Ipak, Java SDK u sebi ne obuhvata neke implementacije i proizvođači baze podataka često ne uključuju jedno od tih u sadržaj JDBC drajvera. Umesto toga, proizvošači aplikativnih servera kao BEA WebLogic ili IBM WebSphere snabdevaju implementaciju pool konekcije kao deo aplikativnog server paketa.

Korišćenje pool konekcije je potpuno transparentno za programera. Vi uspostavljate konekciju sa izvorom pooled konekcija koristeći izvor podataka i pozivom getConnection. Kada završite korišćenje konekcije, pozivate close. To ne zatvara fizičku konekciju, ali govori pool-u da ste završili sa korišćenjem.

Sada ste naučili suštinu JDBC i znate dovoljno o implementaciji jednostavne aplikacije sa bazom podataka. Ipak, kao što smo spomenuli na početku poglavlja, baza podataka je kompleksna i veoma malo naprednih tema su u okviru uvodnog poglavlja. Za opšti pregled i napredne JDBC sposobnosti, proverite JDBC API Tutorial and Reference ili JDBC specifikaciju na http://java.sun.com/products/jdbc

Page 72: Programiranje baza podataka 01

Achilles

Uvod u LDAP U prethodnom delu videli ste kako da komunicirate sa relacionom

bazom podataka. U ovoj sekciji, ukratko ćemo pogledati hijerarhijske baze podataka koje koriste LDAP (Lightweight Directory Access Protocol). Ovaj deo je adaptiran od Core JavaServer Faces on Geary i Horstmann [Sun Microsystems Press 2004.].

LDAP se preferira za relacione baze podataka kada aplikativni podaci prirodno prate drviodnu strukturu i kada operacije čitanja brojno nadmaše pisane operacije. LDAP se najčešće koristi za čuvanje direktorijuma koji sadrže podatke kao korisničko ime, lozinka i dozvole.

LDQP baze podataka čuvaju sve podatke u drvoidnoj strukturi, ne u setu tabela kao u relacionim bazama podataka. Svaki sadržaj u drvetu ima sledeće:

• Nula ili više atributa. Atribut ima ID i vrednost. Primer atributa je cn = John q. Public (ID cn čuva “specifično ime” pogledajte tabelu 10….)

• Jedan ili više objekata klase. Objekat klase definiše set zahtevanih i opcionih atributa za taj element. Npr., objekat klase person definiše zahtevani atribut cn i opciono atribut telephoneNumber. Naravno, klase objekta su različite od Java klase, ali takođe podržava ideju nasleđivanja. Npr., organizationalPerson je podklasa person sa dodatnim atributima.

• Različito ime (npr. Uid = jqpublic, ou = people, dc = mycompany, dc = com). Različito ime je niz atributa koji kopira deo povezanog sadržaja sa korenom drveta. Drvo može biti naizmenican put, ali jedan od njih mora biti specifikovan kao rezličit.

Table 10: Najčešće korišćeni LDAP atributi Attribute ID Meaning Dc Domain component

Cn Common name Sn Surname Dn Distinguishhed name O Organization Ou Organization unit Uid Unique identifier

Kako da organizujemo direktorjum drveta iI koju informaciju da

postavimo u to, može biti vazna napeta rasprava. Mi nećemo diskutovati o tome. Umesto toga, mi jednostavno pretpostavljamo da je organizaciona shema uspostavljena i da je direktorijum već popunjen relevantnim korisničkim podacima.

Page 73: Programiranje baza podataka 01

Achilles

Konfigurisanje LDAP servera Imate nekoliko opcija za pokretanja LDAP servera da bi isprobali

program iz ove sekcije. Evo najčešćih izbora: • IBM Tivoli Directory Server • Microsoft Active Directory • Novell eDirectory • OpenLDAP (http://openldap.org), besplatan server dostupan za

Linux i Windows i izgrađen unutar Mac OS X • Sun Java System Directory Server

Mi dajemo ukratko instrukcije za konfigurisanje OpenLDAP. Ako

koristite drugi direktorijum osnovni korici su slični.

Ako koristite OpenLDAP, treba da editujete fajl slapd.conf pre startvanja LDAP servera. (Na Linux-u default lokacija za slapd.conf fajl je /usr/local/etc/openldap.) Upišite sufiks ulaza u slapd.conf da odgovara

Page 74: Programiranje baza podataka 01

Achilles

primeru seta podataka. Ovaj sadržaj specificira različito ime suffix za ovaj server. Trebalo bi da pročita

suffix “dc=mycompany,dc=com” Vi takođe treba da konfigurišete korisnika LDAP sa pravima

administratora za editovanje podataka u direktorijumu. U OpenLDAP, dodaj ove linije u slapd.conf:

rootdn “cn=Manager, dc=mycompany, dc=com” rootpw secret Sada možete startovati LDAP server. Na Linux-u, pokrenite

usr/local/libexec/slapd Dalje, popunite server sa primerima podataka. Većina LDAP servera

dopušta uvoz LDIF (Lightweight Directory Interchange Format) podataka. LDIF je ljudski-čitljiv format koji jednostavno izlistava sav sadržaj direktorijuma, uključujući njegova različita imena, objekte klase i atribute. Primer 6 prikazuje LDIF fajl koji opisuje naš primer podatka.

Primer 6: sample.ldif

#Define top-level entry dn: dc=mycompany, dc=com objectClass: dcObject objectClass: organization dc: mycompany o: Core Java Team #Define an entry to contain people #serches for users are based on this entry dn: ou=people, dc=company, dc=com objectClass: organizationlUnit ou: people #Define a user entry for John Q. Public dn: uid=jqpublic, ou=people, dc=mycompany, dc=com objectClass: person objectClass: uidObject uid: jqpublic sn: Public cn: John Q. Public telephoneNumber: +1 408 555 0017 userPassword: wombat #Define a user entry for Jane Doe dn: uid=jdoe, ou=people, dc=mycompany, dc=com

Page 75: Programiranje baza podataka 01

Achilles

objectClass: person objectClass: uidObject uid: jdoe sn: Doe cn: Jane Doe telephoneNumber: +1 408 555 0029 userPassword: heffalump #Define an entry to contain LDAP groups #searches for roles are based on this entry dn: ou=groups, dc=mycompany, dc=com objectClass: organizationalUnit ou: groups #Define an entry for the "techstaff" group dn: cn=teachstaff, ou=groups, dc=mycompany, dc=com objectClass: groupOfUniqueNames cn: techstaff uniqueMember: uid-jdoe, ou=people, dc=mycompany, dc=com #Define an entry for the "staff" group dn: cb=staff, ou=groups, dc=mycompany, dc=com objectClass: groupOfUniqueNames cn: staff uniqueMember: uid=jqpublic, ou=people, dc=mycompany, dc=com uniqueMember: uid-jdoe, ou=people, dc=mycompany, dc=com

Npr., sa OpenLDAP, koristite ldapadd alat za dodavanja podataka u direktorijum.

ldapadd –f sample.ldif –x –D “cn=Manager, dc=mycompany, dc=com” –w secret

Pre prethodnog, je dobra ideja, dupla provera da li direktorijum sadrži

podatke koji su potrebni. Mi predlažemo da download-ujete Jarek Gawor’s LDAP Browser\Editor sa http://www-unix.mcs.anl.gov/~gawor/ldap/. Ovaj pogodan Java program omogućava pretraživanje sadržaja bilo kog LDAP servera. Pokrenite program i konfigurišite ga sa sledećim opcijama:

• Host: localhost • Base DN: dc=mycompany, dc=com • Anonymous bind: unchecked • User DN: cn=Manager • Append base DN: checked • Password: secret

Uverite se da je LDAP server startovao, potom se konektujte. Ako je

sve uredu, videćete drvoidni direktorijum sličan ovom na slici 10

Page 76: Programiranje baza podataka 01

Achilles

Pristupanje informacijama LDAP direktorijuma Jednom kada je vaša LDAP baza podataka napunjena, povežite je sa

Java programom. Koristite JNDI (Java Naming and Directory Interface) interfejs koji ujdeinjuje različite protokole direktorijuma.

Startujte sa uzimanjem directory context iz LDAP direktorijuma, sledećim:

Hashtable env = new Hashtable(); env.put(Context.SECURITY_PRINCIPAL, username); env.put(Context.SECURITY_CREDENTIALS, password); DirContext initial = new InitalDirContext(env); DirContext context = (DirContext)

initial.lookup(“ldap://localhost:389”); Ovde se konektujemo sa LDAP serverom na local host-u. Port broj 389

je default LDAP port. Ako se konektujete na LDAP bazu podataka sa pogrešnim

user/password izbaciće se AuthenticationException. Da bi izlistali atribute sa datim sadržajem, odredite različito ime i onda

koristite metodu getAttributes Attributes attrs = context.getAttributes(“uid=jqpublic, ou=people,

dc=mycompany, dc=com”); Možete uzeti specifične atribute sa get metodom, npr., Attribute commonNameAttribute = attrs.get(“cn”); Za enumeraciju svih atributa, koristite klasu NamingEnumeration.

Dizajneri te klase osetili su da oni mogu poboljšati standardni Javin protokol iteracije, i dali su na korišćenje ovaj obrazac:

NamingEnumeration<? Extends Attribute> attrEnum = attrs.getAll(); while (attrEnum.hasMore()) { Attribute attr = attrEnum.next();

String id = attr.getID(); . . .

} Primetite da koristimo hasMore umesto hasNext. Ako znate da neki atribut ima jedinstvenu vrednost, možete pozvati get

metodu za obnovu toga: String commonName = (String) commonNameAttribute.get(); Ako neki atribut može imati višestruku vrednost, treba da koristite

drugi NamingEnumeration za listanje svih njih: NamingEnumeration<?> valueEnum = attr.getAll(); while (valueEnum.hasMore()) {

Page 77: Programiranje baza podataka 01

Achilles

Object value = valueEnum.next(); . . . } Sada znate kako da napravite upit direktorijumu za korisničke podatke.

Dalje, uzmimo operacije za modifikaciju sadržaja direktorijuma. Za dodavanje novog sadržaja prikupimo set atributa u objektu

BasicAttributes (Klasa BasicAttributes implementira Attributes interfejs.) attributes attrs = new BasicAttributes(); attrs.put(“uid”,”alee”); attrs.put(“sn”, “Lee”); attrs.put(“cn”,”Amy Lee”); attrs.put(“telephoneNumber”,”+1 408 555 0033”); String password = “redqueen”; attrs.put(“userPassword”,password.getBytes()); //sledeći atribut ima dve vrednosti Attribute objclass = new BasicAttribute(“objectClass”); objclass.add(“uidObject”); objclass.add(“person”); attrs.put(objclass); Zatim pozujemo metodu createSubcontext. Obezbedite rezličito ime

novog sadržaja i seta atributa. context.createSubcontext(“uid=alee, ou=people, dc=mycompany,

dc=com”, attrs); Za brisanje saržaja pozivamo destroySubcontext: context.destroySubcontext(“uid=jdoe, ou-people, dc=mycompany,

dc=com”); Konačno, možemo poželeti da editujemo atribute u postojećem

sadržaju. Pozivamo metodu: context.modifyAttributes(distinguishedName, flag, attrs); Ovde obeležimo jedan od: DirContext.ADD_ATTRIBUTE DirContext.REMOVE_ATTRIBUTE DirContext.REPLACE_ATTRIBUTE Parametar attrs sadrži set atributa koji će biti dadati, obrisani ili

zamenjeni. Konvencijalno, BasicAttributes(String, Object) konstruktor konstruiše

set atributa sa jednim atributom. Npr. context.modifyAttributes(“uid=alee, ou=people, dc=mycompany,

dc=com”, DirContext.ADD_ATTRIBUTE,

Page 78: Programiranje baza podataka 01

Achilles

New BasicAttributes(“title”, “CTO”)); context.modifyAttributes(“uid=alee, ou=people, dc=mycompany,

dc=com”, DirContext.REMOVE_ATTRIBUTE, New BasicAttributes(“telephoneNumber”, “+1 408 555 0033”)); context.modifyAttributes(“uid=alee, ou=people, dc=mycompany,

dc=com”, DirContext.REPLACE_ATTRIBUTE, New BasicAttributes(“userPassword”, password.getBytes())); Konačno kada završite sa kontekstom, zatvorite to: context.close() Program iz primera 7 pokazuje kako da pristupamo hijararhijskoj bazi

podataka pomoću LDAP. Program pušta gledanje, modifikaciju i brisanje informacija u bazi podataka sa primerom podatka iz primera 6.

Unesite uid unutar polja teksta i kliknite na dugme Find da bi pronašli sadržaj. Ako editujete sadržaj kliknete Save, vaše promene će biti sačuvane. Ako editujete uid polje, kreirećete novi sadržaj.

Inače, postojeći sadržaj će biti ažuriran. Takođe možete brisati sadržaj klikom na dugme Delete (pogledajte sliku ispod)

Sledeći koraci ukratko opisuju program: 1. Konfikuracija za LDAP server sadržana je u fajlu

ldapserver.properties. Fajl definise URL, korisničko ime i lozinku servera, kao npr. ldap.username=cn=Manager, dc=mycompany, dc=com ldap.password=secret ldap.url=ldap://localhost:389 Metoda getContext čita fajl i upotrebljava directory context

2. Kad korisnik klikne na dugme Find, metoda findEntry dobavlja set atributa sa sadržajem sa datim uid. Set atributa se koristi za konstrukciju novog DataPanel-a

3. DataPanel konstruktor prelazi preko seta atributa i dodaje

oznaku i tekstualno polje za svaki ID/value par.

4. Kada korisnik klikne na dugme Delete, metoda deleteEntry briše sadržaj sa datim uid i odbacuje data panel.

Page 79: Programiranje baza podataka 01

Achilles

5. Kada korisnik klikne na dugme Save, DataPanel konstruiše objekat BasicAttributes sa trenutnim sadržajem u tekstualnom polju. Metoda saveEntry proverava da li je uid promenjen. Ako je korisnik uneo uid, kreiran je novi sadržaj. Inače, modifikovani atributi su ažurirani. Modifikacioni kod je jednostavan zato što imamo samo jedan atribut sa višestrukim vrednostima, zvanim, objectClass. Uopšteno, treba raditi više da bi se rukovalo višestrukim vrednostima za svaki atribut.

6. Slično programu iz primera 4, zatvorićemo directory context

kada frame prozor bude zatvoren.

Sada znate dovoljno o directory operacijama za rešavanje zadataka koji će obično trebati kada radite sa LDAP direktorijumima. Dobar izvor za više naprednih informacija je JNDI tutorial http://java.sun.com/products/jndi/tutorial

Primer 7: LDAPTest.java

import java.net.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; import javax.naming.*; import javax.naming.directory.*; import javax.swing.*; /** Ovaj program prikazuje pristup hijerarhijskoj bazi podataka preko LDAP-a */ public class LDAPTest { public static void main(String[] args) { JFrame frame = new LDAPFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }

Page 80: Programiranje baza podataka 01

Achilles

/** Prozor koji sadrži panel sa podacima i dugmićima za navigaciju. */ class LDAPFrame extends JFrame { public LDAPFrame() { setTitle("LDAPTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); JPanel northPanel = new JPanel(); northPanel.setLayout(new java.awt.GridLayout(1, 2, 3, 1)); northPanel.add(new JLabel("uid",SwingConstants.RIGHT)); uidField = new JTextField(); northPanel.add(uidField); add(northPanel, BorderLayout.NORTH); JPanel buttonPanel = new JPanel(); add(buttonPanel, BorderLayout.SOUTH); findButton = new JButton("Find"); findButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { findEntry(); } }); buttonPanel.add(findButton); saveButton = new JButton("Save"); saveButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { saveEntry(); } }); buttonPanel.add(saveButton); deleteButton = new JButton("Delete"); deleteButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event)

Page 81: Programiranje baza podataka 01

Achilles

{ deleteEntry(); } }); buttonPanel.add(deleteButton); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent event) { try { if (context != null) context.close(); } catch (NamingException e) { e.printStackTrace(); } } }); } /** Nalazi sadržaj za uid u tekstualnom polju. */ public void findEntry() { try { if (scrollPane != null) remove(scrollPane); String dn = "uid=" + uidField.getText() + ",ou=people,dc=mycompany,dc=com"; if (context == null) context = getContext(); attrs = context.getAttributes(dn); dataPanel = new DataPanel(attrs); scrollPane = new JScrollPane(dataPanel); add(scrollPane, BorderLayout.CENTER); validate(); uid = uidField.getText(); } catch (NamingException e) { JOptionPane.showMessageDialog(this, e); } catch (IOException e) {

Page 82: Programiranje baza podataka 01

Achilles

JOptionPane.showMessageDialog(this, e); } } /** Čuva promene koje je korisnik napravio */ public void saveEntry() { try { if (dataPanel == null) return; if (context == null) context = getContext(); if (uidField.getText().equals(uid)) //update existing entry { String dn ="uid=" + uidField.getText() + ",ou=people,dc=mycompany,dc=com"; Attributes editedAttrs = dataPanel.getEditedAttributes(); NamingEnumeration<? extends Attribute> attrEnum = attrs.getAll(); while (attrEnum.hasMore()) { Attribute attr = attrEnum.next(); String id = attr.getID(); Object value = attr.get(); Attribute editedAttr = editedAttrs.get(id); if (editedAttr != null && !attr.get().equals(editedAttr.get())) context.modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, new BasicAttributes(id, editedAttr.get())); } } else //pravi novi sadržaj { String dn = "uid=" +uidField.getText() + ",ou=people,dc=mycompany,dc=com"; attrs = dataPanel.getEditedAttributes(); Attribute objclass = new BasicAttribute("objectClass"); objclass.add("uidObject"); objclass.add("person");

Page 83: Programiranje baza podataka 01

Achilles

attrs.put(objclass); attrs.put("uid",uidField.getText()); context.createSubcontext(dn, attrs); } findEntry(); } catch (NamingException e) { JOptionPane.showMessageDialog(LDAPFrame.this, e); e.printStackTrace(); } catch (IOException e) { JOptionPane.showMessageDialog(LDAPFrame.this, e); e.printStackTrace(); } } /** Briše sadržaj uid-a iz tekstualnog polja. */ public void deleteEntry() { try { String dn = "uid=" + uidField.getText() + ",ou=people,dc=mycompany,dc=com"; if (context == null) context = getContext(); context.destroySubcontext(dn); uidField.setText(""); remove(scrollPane); scrollPane = null; repaint(); } catch (NamingException e) { JOptionPane.showMessageDialog(LDAPFrame.this, e); e.printStackTrace(); } catch (IOException e)

Page 84: Programiranje baza podataka 01

Achilles

{ JOptionPane.showMessageDialog(LDAPFrame.this, e); e.printStackTrace(); } } /** Uzima kontekst iz osobina specificiranim u fajlu ldapserver.properties @vraća kontekst direktorijuma */ public static DirContext getContext() throws NamingException, IOException { Properties props = new Properties(); FileInputStream in = new FileInputStream("ldapserver.properties"); props.load(in); in.close(); String url = props.getProperty("ldap.url"); String username = props.getProperty("ldap.username"); String password = props.getProperty("ldap.password"); Hashtable<String, String> env = new Hashtable<String, String>(); env.put(Context.SECURITY_PRINCIPAL, username); env.put(Context.SECURITY_CREDENTIALS, password); DirContext initial = new InitialDirContext(env); DirContext context = (DirContext) initial.lookup(url); return context; } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; private JButton findButton; private JButton saveButton; private JButton deleteButton;

Page 85: Programiranje baza podataka 01

Achilles

private JTextField uidField; private DataPanel dataPanel; private Component scroolPane; private DirContext context; private String uid; private Attributes attrs; } /** Ovaj panel prikazuje sadržaj seta rezultata */ class DataPanel extends JPanel { /** Formira panel sa podacima @param attributes atributi datog ulaza */ public DataPanel(Attributes attrs) throws NamingException { setLayout(new java.awt.GridLayout(0, 2, 3, 1)); NamingEnumeration<? extends Attribute> attrEnum = attrs.getAll(); while (attrEnum.hasMore()) { Attribute attr = attrEnum.next(); String id = attr.getID(); NamingEnumeration<?> valueEnum = attr.getAll(); while(valueEnum.hasMore()) { Object value = valueEnum.next(); if (id.equals("userPassword")) value = new String((byte[]) value); JLabel idLabel = new JLabel(id, SwingConstants.RIGHT); JTextField valueField = new JTextField("" + value);

Page 86: Programiranje baza podataka 01

Achilles

if (id.equals("objectClass")) valueField.setEditable(false); if(!id.equals("uid")) { add(idLabel); add(valueField); } } } } public Attributes getEditedAttributes() { Attributes attrs = new BasicAttributes(); for (int i=0; i<getComponentCount(); i+=2) { JLabel idLabel = (JLabel) getComponent(i); JTextField valueField = (JTextField) getComponent(i + 1); String id = idLabel.getText(); String value = valueField.getText(); if (id.equals("userPassword")) attrs.put("userPassword", value.getBytes()); else if (!id.equals("") && !id.equals("objectClass")) attrs.put(id, value); } return attrs; } }

java.naming.directory.InitalDirContext 1.3

• InitalDirContext (Hashtable env) Konstruiše kontekst direktorijuma, koristeći data podešavanja okoline. Hasch tabela može da sadrži vezivanje za Context.SECURITY_PRINCIPAL, Context.SECURITY_CREDENTIALS i druge ključeve – pogledaj API dokumentaciju za javax.naming.Context za detalje

Page 87: Programiranje baza podataka 01

Achilles

java.naming.Context 1.3

• Object lookup (String name) Traži objekte sa datim imenom. Povratna vrednost zavisi od prirode ovog konteksta. To je uglavnom kontekst poddrvo ili objekat list. • Context createSubcontext (String name) Kreira podkontekst sa datim imenom. Podkontekst postaje dete ovog konteksta. Svi delovi komponente imena, osim za poslednjeg, moraju postojati. • void destroySubcontext (String name) Uništava subcontext sa datim imenom. Svi delovi komponete imena, osim poslednjeg, moraju postojati • void close () Zatvara ovaj kontekst

java.naming.directory.DirContext 1.3

• Attributes getAttributes(String name) Uzima atribut sadržaja sa datim imenom. • void modifyAttributes(String name, int flag Attributes modes) Modifikuje atribute sadržaja sa datim imenom. Vrednost obeleženog je jedna od DirContext.ADD_ATTRIBUTE, DirContext.REMOVE_ATTRIBUTE, DirContext.REPLACE_ATTRIBUTE

java.naming.directory.Attributes 1.3

• Attribute get(String id) Uzima attribut sa datim ID • NamingEnumeration<? Extends Attribute> getAll() Proizvodi enumeraciju koja prelazi preko svih atributa u ovom setu atributa • Attribute put(Attribute attr) • Attribute put(String id, Object value) Dodaje atribut u ovaj set atributa

java.naming.directory.BasicAttributes 1.3

• BasicAttributes (String id, Object value) Konstruiše set atributa koji sadrži jedan atribut sa datim ID i vrednošću

java.naming.directory.Attributes 1.3

• String getID() Uzima ID od ovog atributa

Page 88: Programiranje baza podataka 01

Achilles

• Object get() Uzima prvu vrednost atributa, ako je vrednost sortirana ili proizvoljnu vrednost ako je ona nedosledna. • NamingEnumeration<?> getAll() Proizvodi enumeraciju koja prolazi kroz sve vrednosti ovih atributa.

java.naming.NaminEnumeration<T> 1.3

• boolean hasMore() Vraća true ako ova enumeracija objekta ima više elemenata • T next() Vraća sledeći element od ove enumeracije.