Een eenvoudige gids voor pooling van verbindingen in Java

1. Overzicht

Pooling van verbindingen is een bekend gegevenstoegangspatroon, waarvan het belangrijkste doel is om de overhead te verminderen die gepaard gaat met het uitvoeren van databaseverbindingen en het lezen / schrijven van databasebewerkingen.

In een notendop, een verbindingspool is, op het meest basale niveau, een cache-implementatie voor databaseverbindingen, die kan worden geconfigureerd om aan specifieke vereisten te voldoen.

In deze zelfstudie maken we een korte samenvatting van een aantal populaire frameworks voor het poolen van verbindingen en leren we hoe we onze eigen verbindingspool vanaf het begin kunnen implementeren.

2. Waarom pooling van verbindingen?

De vraag is natuurlijk retorisch.

Als we de opeenvolging van stappen analyseren die betrokken zijn bij een typische levenscyclus van een databaseverbinding, zullen we begrijpen waarom:

  1. Een verbinding met de database openen met behulp van het databasestuurprogramma
  2. Openen van een TCP-socket voor het lezen / schrijven van gegevens
  3. Gegevens lezen / schrijven via de socket
  4. De verbinding verbreken
  5. Het stopcontact sluiten

Het wordt duidelijk dat databaseverbindingen zijn vrij dure bewerkingen, en als zodanig moeten worden teruggebracht tot een minimum in elk mogelijk gebruik (in randgevallen, gewoon vermeden).

Hier komen implementaties van verbindingspooling om de hoek kijken.

Door simpelweg een databaseverbindingscontainer te implementeren, waarmee we een aantal bestaande verbindingen kunnen hergebruiken, kunnen we effectief besparen op de kosten van het uitvoeren van een groot aantal dure databasetrips, waardoor de algehele prestaties van onze databasegestuurde applicaties worden verbeterd.

3. JDBC-frameworks voor het poolen van verbindingen

Vanuit een pragmatisch perspectief is het vanaf de basis implementeren van een pool van verbindingen gewoon zinloos, gezien het aantal beschikbare "enterprise-ready" frameworks voor het poolen van verbindingen.

Van een didactische, wat het doel van dit artikel is, is het dat niet.

Maar voordat we leren hoe we een basispool van verbindingen kunnen implementeren, laten we eerst een paar populaire frameworks voor het poolen van verbindingen laten zien.

3.1. Apache Commons DBCP

Laten we beginnen met deze snelle samenvatting met Apache Commons DBCP Component, een volledig functionele JDBC-framework voor het poolen van verbindingen:

openbare klasse DBCPDataSource {privé statische BasicDataSource ds = nieuwe BasicDataSource (); statische {ds.setUrl ("jdbc: h2: mem: test"); ds.setUsername ("gebruiker"); ds.setPassword ("wachtwoord"); ds.setMinIdle (5); ds.setMaxIdle (10); ds.setMaxOpenPreparedStatements (100); } openbare statische verbinding getConnection () gooit SQLException {return ds.getConnection (); } privé DBCPDataSource () {}}

In dit geval hebben we een wrapper-klasse met een statisch blok gebruikt om de eigenschappen van DBCP eenvoudig te configureren.

Hier leest u hoe u een gepoolde verbinding krijgt met het DBCPDataSource klasse:

Verbinding con = DBCPDataSource.getConnection ();

3.2. HikariCP

Laten we verder kijken naar HikariCP, een razendsnel JDBC-framework voor het poolen van verbindingen gemaakt door Brett Wooldridge (voor de volledige details over hoe je HikariCP configureert en het meeste uit HikariCP haalt, bekijk dan dit artikel):

openbare klasse HikariCPDataSource {privé statische HikariConfig config = nieuwe HikariConfig (); privé statische HikariDataSource ds; statische {config.setJdbcUrl ("jdbc: h2: mem: test"); config.setUsername ("gebruiker"); config.setPassword ("wachtwoord"); config.addDataSourceProperty ("cachePrepStmts", "true"); config.addDataSourceProperty ("prepStmtCacheSize", "250"); config.addDataSourceProperty ("prepStmtCacheSqlLimit", "2048"); ds = nieuwe HikariDataSource (config); } openbare statische verbinding getConnection () gooit SQLException {return ds.getConnection (); } privé HikariCPDataSource () {}}

Op dezelfde manier kunt u als volgt een gepoolde verbinding krijgen met het HikariCPDataSource klasse:

Verbinding con = HikariCPDataSource.getConnection ();

3.3. C3PO

De laatste in deze recensie is C3PO, een krachtige JDBC4-verbinding en framework voor het poolen van verklaringen, ontwikkeld door Steve Waldman:

openbare klasse C3poDataSource {privé statische ComboPooledDataSource cpds = nieuwe ComboPooledDataSource (); statisch {probeer {cpds.setDriverClass ("org.h2.Driver"); cpds.setJdbcUrl ("jdbc: h2: mem: test"); cpds.setUser ("gebruiker"); cpds.setPassword ("wachtwoord"); } catch (PropertyVetoException e) {// de uitzondering afhandelen}} openbare statische verbinding getConnection () genereert SQLException {return cpds.getConnection (); } privé C3poDataSource () {}}

Zoals verwacht, krijgt u een gepoolde verbinding met het C3poDataSource class is vergelijkbaar met de vorige voorbeelden:

Verbinding con = C3poDataSource.getConnection ();

4. Een eenvoudige implementatie

Laten we een eenvoudige implementatie maken om de onderliggende logica van pooling van verbindingen beter te begrijpen.

Laten we beginnen met een losjes gekoppeld ontwerp, gebaseerd op slechts één enkele interface:

openbare interface ConnectionPool {Connection getConnection (); boolean releaseConnection (verbinding verbinding); String getUrl (); String getUser (); String getPassword (); }

De ConnectionPool interface definieert de openbare API van een basisverbindingspool.

Laten we nu een implementatie maken die enkele basisfunctionaliteit biedt, waaronder het verkrijgen en vrijgeven van een gepoolde verbinding:

openbare klasse BasicConnectionPool implementeert ConnectionPool {private String url; privé String-gebruiker; privé String-wachtwoord; privélijst connectionPool; privélijst usedConnections = nieuwe ArrayList (); privé statische int INITIAL_POOL_SIZE = 10; openbare statische BasicConnectionPool create (String-url, String-gebruiker, String-wachtwoord) genereert SQLException {List pool = new ArrayList (INITIAL_POOL_SIZE); voor (int i = 0; i <INITIAL_POOL_SIZE; i ++) {pool.add (createConnection (url, gebruiker, wachtwoord)); } retourneer nieuwe BasicConnectionPool (url, gebruiker, wachtwoord, pool); } // standard constructors @Override public Connection getConnection () {Connection connection = connectionPool .remove (connectionPool.size () - 1); usedConnections.add (verbinding); retourverbinding; } @Override openbare booleaanse releaseConnection (verbinding verbinding) {connectionPool.add (verbinding); return usedConnections.remove (verbinding); } privé statische verbinding createConnection (String url, String gebruiker, String wachtwoord) gooit SQLException {return DriverManager.getConnection (url, gebruiker, wachtwoord); } public int getSize () {return connectionPool.size () + usedConnections.size (); } // standaard getters}

Hoewel behoorlijk naïef, de BasicConnectionPool class biedt de minimale functionaliteit die we zouden verwachten van een typische implementatie van pooling van verbindingen.

Kortom, de klasse initialiseert een verbindingspool op basis van een ArrayList waarin 10 aansluitingen zijn opgeslagen, die gemakkelijk kunnen worden hergebruikt.

Het is mogelijk om JDBC-verbindingen te maken met de DriverManager class en met Datasource-implementaties.

Omdat het veel beter is om het maken van een verbindingsdatabase agnostisch te houden, hebben we de eerste gebruikt binnen de maken () statische fabrieksmethode.

In dit geval hebben we de methode binnen de BasicConnectionPool, omdat dit de enige implementatie van de interface is.

In een complexer ontwerp, met meerdere ConnectionPool implementaties, zou het beter zijn om het in de interface te plaatsen, waardoor het een flexibeler ontwerp en een grotere mate van samenhang krijgt.

Het meest relevante punt om hier te benadrukken is dat zodra de pool is gemaakt, verbindingen worden opgehaald uit de pool, dus het is niet nodig om nieuwe te maken.

Verder wanneer een verbinding wordt verbroken, wordt deze daadwerkelijk teruggestuurd naar de pool, zodat andere clients deze opnieuw kunnen gebruiken.

Er is geen verdere interactie met de onderliggende database, zoals een expliciete aanroep naar het Verbinding is dichtbij () methode.

5. Gebruik de BasicConnectionPool Klasse

Zoals verwacht, met behulp van onze BasicConnectionPool klasse is eenvoudig.

Laten we een eenvoudige unit-test maken en een gepoolde H2-verbinding in het geheugen krijgen:

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

6. Verdere verbeteringen en herstructurering

Er is natuurlijk voldoende ruimte om de huidige functionaliteit van onze implementatie van pooling van verbindingen aan te passen / uit te breiden.

We kunnen bijvoorbeeld de getConnection () methode en voeg ondersteuning toe voor maximale zwembadgrootte. Als alle beschikbare verbindingen zijn bezet en de huidige poolgrootte kleiner is dan het geconfigureerde maximum, zal de methode een nieuwe verbinding tot stand brengen.

We zouden ook kunnen controleren of de verbinding die is verkregen uit de pool nog steeds actief is, voordat we deze doorgeven aan de client.

@Override openbare verbinding getConnection () gooit SQLException {if (connectionPool.isEmpty ()) {if (usedConnections.size () <MAX_POOL_SIZE) {connectionPool.add (createConnection (url, gebruiker, wachtwoord)); } else {throw new RuntimeException ("Maximale poolgrootte bereikt, geen beschikbare verbindingen!"); }} Verbinding verbinding = connectionPool .remove (connectionPool.size () - 1); if (! connection.isValid (MAX_TIMEOUT)) {connection = createConnection (url, gebruiker, wachtwoord); } usedConnections.add (verbinding); retourverbinding; } 

Merk op dat de methode nu gooit SQLException, wat betekent dat we ook de interfacehandtekening moeten bijwerken.

Of we kunnen een methode toevoegen om onze verbindingspoolinstantie op een elegante manier af te sluiten:

public void shutdown () gooit SQLException {usedConnections.forEach (this :: releaseConnection); voor (Verbinding c: connectionPool) {c.close (); } connectionPool.clear (); }

In implementaties die klaar zijn voor productie, zou een verbindingspool een aantal extra functies moeten bieden, zoals de mogelijkheid om de verbindingen te volgen die momenteel in gebruik zijn, ondersteuning voor voorbereide pooling van instructies, enzovoort.

Omdat we de zaken simpel houden, laten we weg hoe we deze extra functies moeten implementeren en houden we de implementatie ter wille van de duidelijkheid niet-thread-safe.

7. Conclusie

In dit artikel hebben we dieper ingegaan op wat pooling van verbindingen is en hebben we geleerd hoe we onze eigen implementatie van pooling van verbindingen kunnen rollen.

Natuurlijk hoeven we niet elke keer opnieuw te beginnen als we een volledige pooling-laag voor verbindingen aan onze applicaties willen toevoegen.

Daarom hebben we eerst een eenvoudige samenvatting gemaakt met enkele van de meest populaire frameworks voor verbindingspools, zodat we een duidelijk idee hebben over hoe we ermee kunnen werken en degene kunnen kiezen die het beste bij onze vereisten past.

Zoals gewoonlijk zijn alle codevoorbeelden die in dit artikel worden getoond, beschikbaar op GitHub.