Jednoduchý sprievodca združovaním pripojení v Jave

1. Prehľad

Združovanie pripojení je známy vzor prístupu k údajom, ktorého hlavným účelom je znížiť réžiu spojenú s vykonávaním databázových pripojení a operáciami čítania a zápisu do databázy.

Stručne, oblasť pripojení je na najzákladnejšej úrovni implementácia medzipamäte databázového pripojenia, ktoré je možné nakonfigurovať tak, aby vyhovovalo konkrétnym požiadavkám.

V tomto výučbe urobíme rýchle zhrnutie niekoľkých populárnych rámcov na združovanie pripojení a naučíme sa, ako implementovať od začiatku náš vlastný fond pripojení.

2. Prečo združovanie pripojení?

Otázka je samozrejme rétorická.

Ak analyzujeme postupnosť krokov typických v životnom cykle databázového pripojenia, pochopíme prečo:

  1. Otvorenie pripojenia k databáze pomocou ovládača databázy
  2. Otvorenie zásuvky TCP na čítanie / zápis dát
  3. Čítanie / zápis dát cez soket
  4. Ukončuje sa spojenie
  5. Zatváranie zásuvky

Je zrejmé, že databázové pripojenia sú dosť drahé operácie, a ako také, by sa mali znížiť na minimum v každom možnom prípade použitia (v okrajových prípadoch sa tomu treba vyhnúť).

Tu prichádza na rad implementácia združovania pripojení.

Jednoduchou implementáciou kontajnera na pripojenie k databáze, ktorý nám umožňuje opätovne použiť množstvo existujúcich pripojení, môžeme efektívne ušetriť náklady na vykonanie veľkého množstva drahých databázových ciest, a tým zvýšiť celkový výkon našich databázovo riadených aplikácií.

3. JDBC rámce na združovanie pripojení

Z pragmatického hľadiska je implementácia fondu pripojení od základu nezmyselná, ak vezmeme do úvahy počet rámcov združovania pripojení „pripravených na podnikanie“, ktoré sú k dispozícii.

Z didaktickej, ktorá je cieľom tohto článku, to tak nie je.

Aj napriek tomu si predtým, ako sa naučíme implementovať základný fond pripojení, najskôr predstavíme niekoľko populárnych rámcov združovania pripojení.

3.1. Apache Commons DBCP

Začnime toto rýchle zhromaždenie s komponentom Apache Commons DBCP, plnohodnotným rámcom JDBC na združovanie pripojení:

verejná trieda DBCPDataSource {súkromná statická BasicDataSource ds = nová BasicDataSource (); static {ds.setUrl ("jdbc: h2: mem: test"); ds.setUsername ("užívateľ"); ds.setPassword ("heslo"); ds.setMinIdle (5); ds.setMaxIdle (10); ds.setMaxOpenPreparedStatements (100); } verejné statické pripojenie getConnection () hodí SQLException {return ds.getConnection (); } súkromný DBCPDataSource () {}}

V tomto prípade sme na ľahkú konfiguráciu vlastností DBCP použili triedu wrapper so statickým blokom.

Tu je príklad, ako získať spoločné spojenie s DBCPDataSource trieda:

Pripojenie con = DBCPDataSource.getConnection ();

3.2. HikariCP

Ďalej sa pozrime na HikariCP, bleskurýchly rámec na združovanie pripojení JDBC, ktorý vytvoril Brett Wooldridge (všetky podrobnosti o tom, ako nakonfigurovať a vyťažiť z HikariCP maximum, nájdete v tomto článku):

verejná trieda HikariCPDataSource {súkromná statická konfigurácia HikariConfig = nová HikariConfig (); súkromný statický HikariDataSource ds; static {config.setJdbcUrl ("jdbc: h2: mem: test"); config.setUsername ("užívateľ"); config.setPassword ("heslo"); config.addDataSourceProperty ("cachePrepStmts", "true"); config.addDataSourceProperty ("prepStmtCacheSize", "250"); config.addDataSourceProperty ("prepStmtCacheSqlLimit", "2048"); ds = nový HikariDataSource (konfigurácia); } verejné statické pripojenie getConnection () hodí SQLException {return ds.getConnection (); } súkromný HikariCPDataSource () {}}

Tu je príklad, ako získať spoločné spojenie s HikariCPDataSource trieda:

Pripojenie con = HikariCPDataSource.getConnection ();

3.3. C3PO

Posledným v tejto recenzii je C3PO, výkonný rámec pre pripojenie JDBC4 a združovanie výpisov, ktorý vyvinul Steve Waldman:

verejná trieda C3poDataSource {súkromná statická ComboPooledDataSource cpds = nová ComboPooledDataSource (); static {try {cpds.setDriverClass ("org.h2.Driver"); cpds.setJdbcUrl ("jdbc: h2: mem: test"); cpds.setUser ("užívateľ"); cpds.setPassword ("heslo"); } catch (PropertyVetoException e) {// zvládnuť výnimku}} verejné statické pripojenie getConnection () vyvolá SQLException {return cpds.getConnection (); } súkromný zdroj C3poDataSource () {}}

Ako sa dalo očakávať, získanie spoločného spojenia s C3poDataSource trieda je podobná ako v predchádzajúcich príkladoch:

Pripojenie con = C3poDataSource.getConnection ();

4. Jednoduchá implementácia

Aby sme lepšie pochopili základnú logiku združovania pripojení, vytvorme jednoduchú implementáciu.

Začnime s voľne prepojeným dizajnom, založeným iba na jednom jedinom rozhraní:

verejné rozhranie ConnectionPool {Pripojenie getConnection (); boolean releaseConnection (pripojenie pripojenia); Reťazec getUrl (); Reťazec getUser (); Reťazec getPassword (); }

The ConnectionPool rozhranie definuje verejné API základného fondu pripojení.

Poďme si vytvoriť implementáciu, ktorá poskytuje základné funkcie vrátane získania a uvoľnenia združeného pripojenia:

verejná trieda BasicConnectionPool implementuje ConnectionPool {private String url; súkromný používateľ reťazca; súkromné ​​reťazcové heslo; private List connectionPool; private List usedConnections = new ArrayList (); private static int INITIAL_POOL_SIZE = 10; public static BasicConnectionPool create (String url, String user, String password) throws SQLException {List pool = new ArrayList (INITIAL_POOL_SIZE); for (int i = 0; i <INITIAL_POOL_SIZE; i ++) {pool.add (createConnection (url, user, password)); } vrátiť nový BasicConnectionPool (adresa URL, používateľ, heslo, skupina); } // štandardné konštruktory @Override public Connection getConnection () {Connection connection = connectionPool .remove (connectionPool.size () - 1); usedConnections.add (pripojenie); spätné pripojenie; } @Override public boolean releaseConnection (pripojenie) {connectionPool.add (pripojenie); návrat usedConnections.remove (pripojenie); } súkromné ​​statické pripojenie createConnection (adresa URL reťazca, používateľ reťazca, heslo reťazca) vyvolá príkaz SQLException {návrat DriverManager.getConnection (adresa URL, používateľ, heslo); } public int getSize () {return connectionPool.size () + usedConnections.size (); } // štandardné vyhľadávače}

Aj keď je dosť naivná, BasicConnectionPool poskytuje minimálnu funkcionalitu, ktorú by sme očakávali od typickej implementácie združovania pripojení.

Stručne povedané, trieda inicializuje fond pripojení na základe súboru ArrayList ktorý uchováva 10 pripojení, ktoré možno ľahko znova použiť.

Je možné vytvoriť spojenie JDBC s DriverManager triedy a s implementáciami Datasource.

Pretože je oveľa lepšie udržiavať vytváranie databázy pripojení agnostické, použili sme prvú z nich v rámci vytvoriť () statická továrenská metóda.

V tomto prípade sme metódu umiestnili do BasicConnectionPool, pretože toto je jediná implementácia rozhrania.

V zložitejšom dizajne s viacerými ConnectionPool implementácií, bolo by lepšie umiestniť ich do rozhrania, čím získate flexibilnejší dizajn a vyššiu úroveň súdržnosti.

Najdôležitejším bodom, ktorý je tu potrebné zdôrazniť, je, že akonáhle je bazén vytvorený, pripojenia sa načítajú z fondu, takže nie je potrebné vytvárať nové.

Ďalej keď sa uvoľní pripojenie, skutočne sa vráti späť do fondu, takže ho môžu ostatní klienti znova použiť.

Neexistuje žiadna ďalšia interakcia so základnou databázou, napríklad explicitné volanie do databázy Pripojenie je ukončené () metóda.

5. Pomocou BasicConnectionPool Trieda

Ako sa dalo očakávať, pomocou nášho BasicConnectionPool trieda je priama.

Vytvorme jednoduchý test jednotky a získajme združené pripojenie H2 v pamäti:

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

6. Ďalšie vylepšenia a refaktoring

Samozrejme, existuje veľa priestoru na vylepšenie / rozšírenie súčasnej funkcionality našej implementácie združovania pripojení.

Napríklad by sme mohli refaktorovať getConnection () metóda a pridať podporu pre maximálnu veľkosť fondu. Ak sa vykonajú všetky dostupné pripojenia a aktuálna veľkosť fondu je menšia ako nakonfigurované maximum, metóda vytvorí nové pripojenie.

Pred odovzdaním klientovi by sme tiež mohli dodatočne overiť, či je pripojenie získané z fondu stále živé.

@Override public Connection getConnection () hodí SQLException {if (connectionPool.isEmpty ()) {if (usedConnections.size () <MAX_POOL_SIZE) {connectionPool.add (createConnection (url, user, password)); } else {hodiť novú RuntimeException ("Bola dosiahnutá maximálna veľkosť bazéna, nie sú k dispozícii žiadne pripojenia!"); }} Pripojenie pripojenia = connectionPool .remove (connectionPool.size () - 1); if (! connection.isValid (MAX_TIMEOUT)) {connection = createConnection (url, user, password); } usedConnections.add (pripojenie); spätné pripojenie; } 

Všimnite si, že metóda teraz hodí SQLException, čo znamená, že budeme musieť aktualizovať aj podpis rozhrania.

Alebo by sme mohli pridať metódu na elegantné vypnutie našej inštancie fondu pripojení:

public void shutdown () hodí SQLException {usedConnections.forEach (this :: releaseConnection); pre (Pripojenie c: connectionPool) {c.close (); } connectionPool.clear (); }

V implementáciách pripravených na produkciu by fond pripojení mal poskytovať množstvo ďalších funkcií, ako napríklad schopnosť sledovať pripojenia, ktoré sa momentálne používajú, podpora združeného výpisu príkazov atď.

Pretože bude všetko jednoduché, vynecháme, ako implementovať tieto ďalšie funkcie, a kvôli prehľadnosti implementáciu zaistíme, že nebude bezpečná pre iné vlákna.

7. Záver

V tomto článku sme sa podrobne zaoberali tým, čo je združovanie pripojení, a naučili sme sa, ako zaviesť vlastnú implementáciu združovania pripojení.

Samozrejme, nemusíme začínať od nuly vždy, keď chceme do svojich aplikácií pridať plnohodnotnú vrstvu združovania pripojení.

Preto sme najskôr vytvorili jednoduché zhrnutie, ktoré zobrazuje niektoré z najpopulárnejších rámcov oblasti pripojení, takže môžeme mať jasnú predstavu o tom, ako s nimi pracovať, a vyzdvihnúť ten, ktorý najlepšie vyhovuje našim požiadavkám.

Ako obvykle sú všetky ukážky kódu zobrazené v tomto článku k dispozícii na GitHub.