JavaSvet - otvorena java zajednica

 
glavna stranica arr2javasvet  english version arr2java.net

Implementacija connection pool-a

Boriša Živković
6 Jul 2004

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).

Osnove JDBC-a (Java DataBase Connectivity)

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.

Zašto nam treba Connection Pool?

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 podataka
2. pretraži bazu na osnovu parametara proslijeđenih u upitu
3. vrati rezultat pretrage
4. odjavi se sa baze

Sigurno 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.

Detalji implementacije

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() &amp;&amp; conCount&lt;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!"
);
}

Za kraj...

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.