Jednostavan vodič za spajanje veza u Javi

1. Pregled

Spajanje veza dobro je poznat obrazac pristupa podacima, čija je glavna svrha smanjiti općenite troškove koji su uključeni u izvođenje veza s bazom podataka i čitanje / pisanje operacija baze podataka.

U suštini, spremište veza je, na najosnovnijoj razini, implementacija predmemorije veze baze podataka, koji se mogu konfigurirati tako da odgovaraju specifičnim zahtjevima.

U ovom uputstvu napravit ćemo brzi pregled nekoliko popularnih okvira za spremanje veza i naučit ćemo kako od nule implementirati vlastiti spremište veza.

2. Zašto udruživanje veza?

Pitanje je naravno retoričko.

Ako analiziramo slijed koraka koji su uključeni u tipični životni ciklus veze s bazom podataka, shvatit ćemo zašto:

  1. Otvaranje veze s bazom podataka pomoću upravljačkog programa baze podataka
  2. Otvaranje TCP utičnice za čitanje / pisanje podataka
  3. Čitanje / pisanje podataka preko utičnice
  4. Zatvaranje veze
  5. Zatvaranje utičnice

Postaje očito da veze s bazom podataka prilično su skupe operacije, i kao takav, treba ga svesti na najmanju moguću mjeru u svim mogućim slučajevima korištenja (u rubnim slučajevima, samo izbjegavati).

Ovdje nastupaju implementacije spremanja veza.

Jednostavnom implementacijom spremnika veze baze podataka, koji nam omogućuje ponovnu upotrebu brojnih postojećih veza, možemo učinkovito uštedjeti troškove izvođenja velikog broja skupih putovanja baze podataka, a time i povećati ukupnu izvedbu naših aplikacija vođenih bazom podataka.

3. Okviri za udruživanje JDBC veza

Iz pragmatične perspektive, implementacija spremišta veza od temelja jednostavno je besmislena, s obzirom na broj raspoloživih okvira za udruživanje veza spremnih za poduzeće.

Iz didaktičke, što je cilj ovog članka, nije.

Unatoč tome, prije nego što naučimo kako implementirati osnovno spremište veza, prvo predstavimo nekoliko popularnih okvira za spajanje veza.

3.1. Apache Commons DBCP

Započnimo ovaj brzi pregled s Apache Commons DBCP Componentom, cjelovitim JDBC okvirom za spajanje veza:

javna klasa DBCPDataSource {private static BasicDataSource ds = new BasicDataSource (); static {ds.setUrl ("jdbc: h2: mem: test"); ds.setUsername ("korisnik"); ds.setPassword ("lozinka"); ds.setMinIdle (5); ds.setMaxIdle (10); ds.setMaxOpenPreparedStatements (100); } javna statička veza getConnection () baca SQLException {return ds.getConnection (); } privatni DBCPDataSource () {}}

U ovom smo slučaju koristili klasu omota sa statičkim blokom za jednostavno konfiguriranje svojstava DBCP-a.

Evo kako doći do objedinjene veze s DBCPDataSource razred:

Veza con = DBCPDataSource.getConnection ();

3.2. HikariCP

Krenimo dalje, pogledajmo HikariCP, munjevit okvir za udruživanje JDBC veza koji je stvorio Brett Wooldridge (za sve detalje o tome kako konfigurirati i iskoristiti HikariCP i iskoristiti maksimum, pogledajte ovaj članak):

javna klasa HikariCPDataSource {privatni statički HikariConfig config = novi HikariConfig (); privatni statički HikariDataSource ds; statički {config.setJdbcUrl ("jdbc: h2: mem: test"); config.setUsername ("korisnik"); config.setPassword ("lozinka"); config.addDataSourceProperty ("cachePrepStmts", "true"); config.addDataSourceProperty ("prepStmtCacheSize", "250"); config.addDataSourceProperty ("prepStmtCacheSqlLimit", "2048"); ds = novi HikariDataSource (konfiguracija); } javna statička veza getConnection () baca SQLException {return ds.getConnection (); } privatni HikariCPDataSource () {}}

Slično tome, evo kako doći do združene veze s HikariCPDataSource razred:

Veza con = HikariCPDataSource.getConnection ();

3.3. C3PO

Posljednji u ovom pregledu je C3PO, snažni JDBC4 okvir za povezivanje i spajanje izjava koji je razvio Steve Waldman:

javna klasa C3poDataSource {private static ComboPooledDataSource cpds = new ComboPooledDataSource (); static {try {cpds.setDriverClass ("org.h2.Driver"); cpds.setJdbcUrl ("jdbc: h2: mem: test"); cpds.setUser ("korisnik"); cpds.setPassword ("lozinka"); } catch (PropertyVetoException e) {// obrađuje iznimku}} javna statička veza getConnection () baca SQLException {return cpds.getConnection (); } privatni C3poDataSource () {}}

Kao što se očekivalo, dobivanje objedinjene veze s C3poDataSource razred sličan je prethodnim primjerima:

Veza con = C3poDataSource.getConnection ();

4. Jednostavna provedba

Da bismo bolje razumjeli temeljnu logiku spremanja veza, stvorimo jednostavnu implementaciju.

Počnimo s labavo povezanim dizajnom, koji se temelji na samo jednom sučelju:

javno sučelje ConnectionPool {Connection getConnection (); boolean releaseConnection (Veza veza); String getUrl (); String getUser (); String getPassword (); }

The ConnectionPool sučelje definira javni API osnovnog spremišta.

Sada, kreirajmo implementaciju koja pruža neke osnovne funkcije, uključujući dobivanje i puštanje združene veze:

javna klasa BasicConnectionPool implementira ConnectionPool {private String url; privatni korisnik niza; privatna lozinka za niz; privatni popis connectionPool; privatni popis usedConnections = novi ArrayList (); privatni statički int INITIAL_POOL_SIZE = 10; public static BasicConnectionPool create (String url, String user, String password) baca SQLException {Popis baze = novi ArrayList (INITIAL_POOL_SIZE); for (int i = 0; i <INITIAL_POOL_SIZE; i ++) {pool.add (createConnection (url, korisnik, lozinka)); } vratiti novi BasicConnectionPool (url, korisnik, lozinka, spremište); } // // standardni konstruktori @Override public Connection getConnection () {Connection connection = connectionPool .remove (connectionPool.size () - 1); usedConnections.add (veza); povratna veza; } @Override public boolean releaseConnection (veza s vezom) {connectionPool.add (veza); povratak usedConnections.remove (veza); } privatna statička veza createConnection (URL niza, korisnik niza, lozinka niza) baca SQLException {povratak DriverManager.getConnection (url, korisnik, lozinka); } javni int getSize () {return connectionPool.size () + usedConnections.size (); } // standardni dohvatnici}

Iako prilično naivan, BasicConnectionPool klasa pruža minimalnu funkcionalnost koju bismo očekivali od tipične implementacije udruživanja veza.

Ukratko, klasa inicijalizira spremište veza na temelju ArrayList koji pohranjuje 10 veza, koje se lako mogu ponovno upotrijebiti.

Moguće je stvoriti JDBC veze s DriverManager klase i s implementacijama izvora podataka.

Kako je mnogo bolje stvaranje baze podataka povezivanja držati agnostičkom, koristili smo prvu, unutar stvoriti() statička tvornička metoda.

U ovom smo slučaju metodu smjestili u BasicConnectionPool, jer je ovo jedina implementacija sučelja.

U složenijem dizajnu, s višestrukim ConnectionPool implementacije, bilo bi poželjno da se smjesti u sučelje, čime se dobiva fleksibilniji dizajn i veća razina kohezije.

Ovdje je najvažnije naglasiti da kada se bazen stvori, veze se dohvaćaju iz spremišta, pa nema potrebe za stvaranjem novih.

Nadalje, kada se veza oslobodi, ona se zapravo vraća natrag u spremište, tako da je drugi klijenti mogu ponovno koristiti.

Nema daljnje interakcije s osnovnom bazom podataka, poput eksplicitnog poziva na Veza je blizu () metoda.

5. Korištenje BasicConnectionPool Razred

Očekivano, koristeći naš BasicConnectionPool klasa je izravna.

Stvorimo jednostavan jedinični test i dobijmo objedinjenu H2 vezu u memoriji:

@Test public whenCalledgetConnection_thenCorrect () {ConnectionPool connectionPool = BasicConnectionPool .create ("jdbc: h2: mem: test", "user", "password"); assertTrue (connectionPool.getConnection (). isValid (1)); }

6. Daljnja poboljšanja i refaktoriranje

Naravno, ima dovoljno prostora za dotjerivanje / proširivanje trenutne funkcionalnosti naše implementacije udruživanja veza.

Na primjer, mogli bismo refaktorizirati getConnection () metodu i dodajte podršku za maksimalnu veličinu spremišta. Ako su preuzete sve dostupne veze, a trenutna veličina spremišta je manja od konfiguriranog maksimuma, metoda će stvoriti novu vezu.

Također, mogli bismo dodatno provjeriti je li veza dobivena iz spremišta još uvijek živa, prije nego što je proslijedimo klijentu.

@Override public Connection getConnection () baca SQLException {if (connectionPool.isEmpty ()) {if (usedConnections.size () <MAX_POOL_SIZE) {connectionPool.add (createConnection (url, korisnik, lozinka)); } else {baciti novi RuntimeException ("Dosegnuta maksimalna veličina spremišta, nema dostupnih veza!"); }} Veza veze = connectionPool .remove (connectionPool.size () - 1); if (! connection.isValid (MAX_TIMEOUT)) {connection = createConnection (url, korisnik, lozinka); } usedConnections.add (veza); povratna veza; } 

Imajte na umu da metoda sada baca SQLException, što znači da ćemo morati ažurirati i potpis sučelja.

Ili bismo mogli dodati metodu za elegantno isključivanje naše instance spremišta veza:

javno void shutdown () baca SQLException {usedConnections.forEach (this :: releaseConnection); for (Veza c: connectionPool) {c.close (); } connectionPool.clear (); }

U implementacijama spremnim za proizvodnju, spremište veza trebalo bi pružiti gomilu dodatnih značajki, poput mogućnosti praćenja veza koje su trenutno u upotrebi, podrške za spremanje pripremljenih izraza i slično.

Kako ćemo stvari pojednostaviti, izostavit ćemo kako implementirati ove dodatne značajke i zadržati implementaciju bez zaštite niti radi jasnosti.

7. Zaključak

U ovom smo članku detaljno pogledali što je spremanje veza i naučili kako pokretati vlastitu implementaciju spremanja veza.

Naravno, ne moramo svaki put krenuti ispočetka kada svojim aplikacijama želimo dodati sloj za udruživanje veza s punom značajkom.

Zbog toga smo prvo napravili jednostavan pregled koji prikazuje neke od najpopularnijih okvira spremišta veza, tako da možemo imati jasnu ideju kako raditi s njima i odabrati onaj koji najbolje odgovara našim zahtjevima.

Kao i obično, svi uzorci koda prikazani u ovom članku dostupni su na GitHubu.