Baze podataka i rad sa njima su postale nezaobilazni dio IT industrije. Sve veći broj web i desktop aplikacija oslanja se na rad nekog DBMS (DataBase Management System). Sav posao oko skladištenja, čuvanja i pretraživanja podataka odrađuje softver baze podataka. Time je obezbeđen veći stepen apstrakcije pri programiranju i developeri mogu da se koncentrišu na druge (važnije) probleme ne brinući o kompleksnim problemima čuvanja i efikasnog pretraživanja velikih količina podataka, a aplikacija je sada podijeljena na više (relativno nezavisnih) slojeva tako da je olakšano njeno održavanje.
Danas su svakako najpopularnije relacione baze podataka (Relational DBMS). Razlog za to je velika sigurnost koju pružaju u radu sa podacima, dobre performanse kao i velika rasprostranjenost. Sve ovo su posljedice dobro razvijenog matematičkog modela na kome se zasnivaju. Jezik za rad sa relacionim bazama podataka se naziva Structured Query Language (SQL) i standardizovan je ali u nedovoljnoj mjeri tako da svaki proizvođač softvera za upravljanje bazom podataka dodaje svoja proširenja (ovi problemi su analogni problemima standardizacije HTML-a i JavaScript jezika) koja kasnije zadaju glavobolje developerima koji žele da njihova aplikacija bude nezavisna od baze (database independent).
JDBC predstavlja Java API (Application Programming Interface) za pristup relacionim
bazama podataka nezavisno od vrste baze tj. od proizvođača. Znači koristeći klase
i interfejse iz java.sql.* paketa (paket kome pripadaju JDBC klase i interfejsi)
mi možemo, bez ikakvih ili sa vrlo malo promjena, da, iz istog programa, pristupamo
Oracle, SQL Server, DB2, MySQL ili bilo kojoj drugoj relacionoj bazi. Sve što nam
treba jesu JDBC driveri za tu bazu (možemo da koristimo i ODBC drivere ali to je
priča za sebe). Koristeći JDBC drivere mi pribavljamo konekciju ka bazi sa kojom
radimo (java.sql.Connection interfejs) a od konekcije dobijamo java.sql.Statement
ili java.sql.PreparedStatement (u zavisnosti od toga šta nam je potrebno)
objekte preko kojih izvršavamo SQL upite nad bazom.
Da bismo radili nešto sa bazom (izvršavali neke upite) potrebno je da se povežemo na bazu tj. da uspostavimo komunikacioni kanal između naše aplikacije i softvera baze. Ovo je proces koji je vrlo zahtjevan po pitanju vremena (sekund do dva po nekim merenjima) i računarskih resursa jer softver baze mora da provjeri autentičnost (authentication) korisnika koji pokušava da se poveže a potrebno je da se kreiraju i razne strukture podataka koje će da se brinu o transakcijama, keširanju, smještanju rezultata upita itd. Ukoliko je u pitanju aplikacija koju koristi jedan korisnik (na primjer neka desktop aplikacija) tada nam povezivanje na bazu ne predstavlja opterećenje. Ali šta je sa aplikacijama sa kojima u svakom trenutku radi desetine (ili stotine) korisnika? Ako bi se svaki korisnik nezavisno od sistema povezivao na bazu tada bismo mnogo vremena (i računarskih resursa) trošili na povezivanje korisnika na bazu i odjavljivanje korisnika koji su završili svoj posao. Zamislimo samo kada bi Google (http://www.google.com) za svaki upit koji mu se proslijedi radio sledeće:
1. poveži se na bazu podatakaSigurno da tada ne bismo prilikom pretrage dobijali stotine hiljada rezultata za samo par djelića sekunde.
Rješenje koje se nameće je da bi bilo dobro kada bismo na neki način dijelili konekcije na bazu tako da više korisnika može da koristi jednu istu konekciju (naravno ne istovremeno). Ideja je da se određen broj konekcija na bazu kreira prilikom pokretanja aplikacije (ili povremeno kada nam treba nova konekcija) i da se te konekcije ne zatvaraju poslije upotrebe već da se čuvaju za eventualnu buduću upotrebu. Na taj način je moguće da nekoliko konekcija opslužuje veći broj korisnika. Naravno postoje različiti načini realizacije Connection Pool-a i sama implementacija zavisi od potreba aplikacije za koju će da se koristi sam pool. Connection Pool se u praksi pokazao kao vrlo neophodna stvar i u JDBC 3.0 su definisani interfejsi koje bi Connection Pool trebalo da implementira. Mi ćemo ovde navesti primjer kako se može implementirati jednostavan Connection Pool koristeći JDBC 2.0.
Za realizaciju našeg Connection Poola potrebne su nam dvije klase. Jedna od njih (PoolDataSource)
će da implementira javax.sql.DataSource interface i služiće nam za pribavljanje
konekcija ka bazi. Ovu klasu ćemo realizovati kao singleton (onemogućavamo kreiranje
više instanci ove klase u okviru jedne aplikacije) jer djeluje prirodno da za jednu
aplikaciju postoji jedan i samo jedan Connection Pool. PoolDataSource će sadržati
privatno polje tipa
java.util.Vector u kome ćemo da čuvamo konekcije ka bazi koje se ne koriste
ali su spremne za upotrebu (slažem se da smo umesto java.util.Vector klase
mogli da koristimo i npr. java.util.ArrayList ali performanse nisu tema ovog
teksta). Kada korisnik zatraži konekciju na bazu mi provjeravamo da li u Vector-u
ima slobodnih konekcija. Ako ima korisniku pošaljemo konekciju, a ako nema kreiramo
novu (ukoliko nismo prekoračili limit)! Pored ovog polja ova klasa ima i polja za
čuvanje parametara potrebnih za povezivanje na bazu (korisničko ime, lozinka, URL
do baze) i te parametre učitavamo uz pomoć JNDI (Java Naming and Directory Interface)
iz xml fajla (ovo i nije tako bitno za samu realizaciju Connection Poola).
public class PoolDataSource implements DataSource {
// potrebno za realizaciju sigletona
private static PoolDataSource Pool=null;
// naziv drivera za povezivanje na bazu
private static String driver;
// URL do baze
private static String jdbcURL;
// username za povezivanje na bazu
private static String user;
// lozinka za povezivanje na bazu
private static String pass;
// ovde cuvamo naše (slobodne) konekcije
private Vector pooledCons;
// broj konekcija koje nas pool moze da cuva
private static int maxCons;
// timeout period, ako je konekcija neaktivna
// za ovu vrednost tada je unistavamo
private static int minutes;
// broj konekcija trenutno na poolu
private static int conCount;
... Vjerovatno ste primijetili polja timeout i maxCons (oba polja se učitavaju iz fajla uz pomoć JNDI). Prvo polje (timeout) predstavlja period (u minutama) posle koga konekcija na bazu postaje nevažeća, jer će vjerovatno sam DBMS da odbaci konekciju koja je neaktivna određen period vremena (ovo zavisi od podešavanja vašeg DBMS-a i same konekcije). Drugo polje ograničava broj konekcija koje možemo da čuvamo na Connection Pool-u tj. ograničavamo broj korisnika koji istovremeno mogu da koriste našu aplikaciju. Ovo isto tako nije bitno za realizaciju Connection Pool-a.
Druga klasa je PoolConnection i to je unutrašnja klasa (inner class) prethodno opisane
klase. PoolConnection implementira java.sql.Connection inteface i kao svoje
privatno polje sadrži java.sql.Connection
objekat koji jeste fizička veza ka bazi. Ovo polje odrađuje sav posao sa bazom dok
naša PoolConnection klasa služi samo za pakovanje ovog polja tako da kada se pozove
metod close() nad instancom PoolConnection klase mi nećemo zatvoriti konekciju već
ćemo je vratiti živu i zdravu na PoolDataSource odgovoran za nju (tj. onaj koji je
i kreirao taj naš PoolConnection objekat). Znači kada korisnik od PoolDataSource-a
zatraži konekciju ka bazi on dobija PoolConnection objekat koji će sve pozive metoda
da proslijedi ka svom privatnom polju tipa java.sql.Connection sem poziva
close() kada neće zatvoriti fizičku vezu ka bazi već će se vratiti u PoolDataSource
(da bi stavio tu konekciju na raspolaganje drugim korisnicima).
Vjerovatno najzanimljiviji metod (i jedini koji ima više od 5 linija koda) jeste onaj koga korisnik poziva kada od Connection Poola traži instancu konekcije na bazu:
// thread safe method
public synchronized Connection getConnection() throws SQLException {
// mozemo kreirati jos konekcija ili vec imamo neke slobodne na poolu
if(conCount<maxCons || !pooledCons.isEmpty()){
// trenutno vrijeme
long currenttime=System.currentTimeMillis();
// trazimo validnu konekciju sve dok pool ne postane prazan
while(!pooledCons.isEmpty()){
PoolConnection con=(PoolConnection)pooledCons.firstElement();
pooledCons.removeElementAt(0);
long created=con.getCreationDate().getTime();
// ako je konekcija OK damo ja korisniku tj. nije joj istekao timeout
if(currenttime-created<=timeout){
// azuriramo koliko smo konekcija dali korisnicima
conCount++;
// damo korisniku ono sto je trazio
return con;
}
} // end while<br>
// kreiramo novu konekciju jer je pool prazan,
// a mi mozemo da kreiramo jos konekcija
if(pooledCons.isEmpty() && conCount<maxCons){
try{
initializeCons();
Connection con=(Connection)pooledCons.firstElement();
pooledCons.removeElementAt(0);
conCount++;
return con;
}catch(Exception e){
e.printStackTrace();
return null;
}
} // ne mozemo dati korisniku konekciju pa generisemo izuzetak
System.err.println("More connections required from pool by application
than specified in pool settings!");
throw new SQLException("More connections required from pool by application
than specified in pool settings!");
}
Ovo je vrlo jednostavna implementacija Connection Poola. Složiću se sa svima vama koji
kažete da je moglo i bolje da se uradi ali treba imati na umu da je ovo samo u ilustracione
svrhe. U ovoj implementaciji nema nikavih teških problema koje treba nekako isprogramirati
već je bitno koncentrisati se na ideju tj. na način kako je sve zamišljeno i odrađeno.
Za ovu implementaciju je provjereno da radi sa SQL
serverom i Tomcat JSP serverom.
Kompletan source možete skinuti ovdje.