En enkel guide til forbindelsespooling i Java

1. Oversigt

Forbindelsespooling er et velkendt dataadgangsmønster, hvis hovedformål er at reducere omkostningerne involveret i udførelse af databaseforbindelser og læse / skrive databasefunktioner.

I en nøddeskal, en forbindelsespulje er på det mest basale niveau en implementering af cache til databaseforbindelse, som kan konfigureres til at passe til specifikke krav.

I denne vejledning laver vi en hurtig sammenfatning af et par populære pooling-rammer for forbindelser, og vi lærer, hvordan vi implementerer fra bunden vores egen forbindelsespool.

2. Hvorfor forbindelsespooling?

Spørgsmålet er naturligvis retorisk.

Hvis vi analyserer rækkefølgen af ​​trin, der er involveret i en typisk livscyklus for databaseforbindelse, forstår vi hvorfor:

  1. Åbning af en forbindelse til databasen ved hjælp af databasedriveren
  2. Åbning af et TCP-stik til læsning / skrivning af data
  3. Læsning / skrivning af data over stikkontakten
  4. Lukning af forbindelsen
  5. Lukning af soklen

Det bliver tydeligt, at databaseforbindelser er ret dyre operationerog som sådan bør reduceres til et minimum i enhver mulig brugssag (i kanttilfælde, bare undgået).

Her er hvor implementering af forbindelsespooling spiller ind.

Ved simpelthen at implementere en databaseforbindelsescontainer, der giver os mulighed for at genbruge et antal eksisterende forbindelser, kan vi effektivt spare omkostningerne ved at udføre et stort antal dyre databaseture og dermed øge den samlede ydeevne for vores databasedrevne applikationer.

3. JDBC Connection Pooling Frameworks

Fra et pragmatisk perspektiv er det bare meningsløst at implementere en forbindelsespulje fra bunden i betragtning af antallet af "enterprise-ready" forbindelsespooling-rammer derude.

Fra en didaktisk, som er målet med denne artikel, er det ikke.

Alligevel skal vi først, før vi lærer at implementere en grundlæggende forbindelsespulje, fremvise et par populære rammer for forbindelsespooling.

3.1. Apache Commons DBCP

Lad os starte denne hurtige sammenfatning med Apache Commons DBCP-komponent, en fuldt udstyret forbindelse, der samler JDBC-rammer:

offentlig klasse DBCPDataSource {privat statisk BasicDataSource ds = ny BasicDataSource (); statisk {ds.setUrl ("jdbc: h2: mem: test"); ds.setUsername ("bruger"); ds.setPassword ("adgangskode"); ds.setMinIdle (5); ds.setMaxIdle (10); ds.setMaxOpenPreparedStatements (100); } offentlig statisk Forbindelse getConnection () kaster SQLException {return ds.getConnection (); } privat DBCPDataSource () {}}

I dette tilfælde har vi brugt en indpakningsklasse med en statisk blok til let at konfigurere DBCP's egenskaber.

Sådan får du en samlet forbindelse til DBCPDataSource klasse:

Forbindelse con = DBCPDataSource.getConnection ();

3.2. HikariCP

Når vi går videre, lad os se på HikariCP, en lynhurtig JDBC-forbindelsespooling-ramme oprettet af Brett Wooldridge (for alle detaljer om, hvordan du konfigurerer og får mest muligt ud af HikariCP, skal du tjekke denne artikel):

offentlig klasse HikariCPDataSource {privat statisk HikariConfig config = ny HikariConfig (); privat statisk HikariDataSource ds; statisk {config.setJdbcUrl ("jdbc: h2: mem: test"); config.setUsername ("bruger"); config.setPassword ("adgangskode"); config.addDataSourceProperty ("cachePrepStmts", "true"); config.addDataSourceProperty ("prepStmtCacheSize", "250"); config.addDataSourceProperty ("prepStmtCacheSqlLimit", "2048"); ds = ny HikariDataSource (config); } offentlig statisk Forbindelse getConnection () kaster SQLException {return ds.getConnection (); } privat HikariCPDataSource () {}}

På samme måde kan du få en samlet forbindelse til HikariCPDataSource klasse:

Forbindelse con = HikariCPDataSource.getConnection ();

3.3. C3PO

Sidste i denne anmeldelse er C3PO, en kraftfuld JDBC4-forbindelse og rammepooling-ramme udviklet af Steve Waldman:

offentlig klasse C3poDataSource {privat statisk ComboPooledDataSource cpds = ny ComboPooledDataSource (); statisk {prøv {cpds.setDriverClass ("org.h2.Driver"); cpds.setJdbcUrl ("jdbc: h2: mem: test"); cpds.setUser ("bruger"); cpds.setPassword ("adgangskode"); } fange (PropertyVetoException e) {// håndtere undtagelsen}} offentlig statisk Forbindelse getConnection () kaster SQLException {return cpds.getConnection (); } privat C3poDataSource () {}}

Som forventet at få en samlet forbindelse til C3poDataSource klasse svarer til de tidligere eksempler:

Forbindelse con = C3poDataSource.getConnection ();

4. En simpel implementering

For bedre at forstå den underliggende logik i forbindelse med pooling af forbindelser, lad os oprette en simpel implementering.

Lad os starte med et løst koblet design baseret på kun en enkelt grænseflade:

offentlig grænseflade ConnectionPool {Connection getConnection (); boolsk frigivelse Connection (Connection connection); String getUrl (); String getUser (); String getPassword (); }

Det ConnectionPool interface definerer den offentlige API for en grundlæggende forbindelsespulje.

Lad os nu oprette en implementering, der giver nogle grundlæggende funktioner, herunder at få og frigive en samlet forbindelse:

offentlig klasse BasicConnectionPool implementerer ConnectionPool {privat String url; privat streng bruger; privat strengadgangskode; privat List connectionPool; privat liste usedConnections = ny ArrayList (); privat statisk int INITIAL_POOL_SIZE = 10; offentlig statisk BasicConnectionPool create (String url, String user, String password) kaster SQLException {List pool = new ArrayList (INITIAL_POOL_SIZE); til (int i = 0; i <INITIAL_POOL_SIZE; i ++) {pool.add (createConnection (url, bruger, adgangskode)); } returner ny BasicConnectionPool (url, bruger, adgangskode, pool); } // standardkonstruktører @ Override public Connection getConnection () {Connection connection = connectionPool .remove (connectionPool.size () - 1); usedConnections.add (forbindelse); returforbindelse } @ Override public boolean releaseConnection (Connection connection) {connectionPool.add (forbindelse); returner usedConnections.remove (forbindelse); } privat statisk forbindelse createConnection (String url, String user, String password) kaster SQLException {return DriverManager.getConnection (url, user, password); } public int getSize () {return connectionPool.size () + usedConnections.size (); } // standard getters}

Mens det er temmelig naivt, BasicConnectionPool klasse giver den minimale funktionalitet, som vi forventer af en typisk implementering af forbindelse til pooling.

I en nøddeskal initialiserer klassen en forbindelsespool baseret på en ArrayList der gemmer 10 forbindelser, som let kan genbruges.

Det er muligt at oprette JDBC-forbindelser med DriverManager klasse og med datakildeimplementeringer.

Da det er meget bedre at holde oprettelsen af ​​forbindelsesdatabase agnostisk, har vi brugt den tidligere inden for skab() statisk fabriksmetode.

I dette tilfælde har vi placeret metoden i BasicConnectionPool, fordi dette er den eneste implementering af grænsefladen.

I et mere komplekst design med flere ConnectionPool implementeringer, ville det være at foretrække at placere det i grænsefladen og derfor få et mere fleksibelt design og et større niveau af samhørighed.

Det mest relevante punkt at understrege her er, at når puljen er oprettet, forbindelser hentes fra puljen, så der er ingen grund til at oprette nye.

Desuden, når en forbindelse frigives, returneres den faktisk tilbage til puljen, så andre kunder kan genbruge den.

Der er ingen yderligere interaktion med den underliggende database, såsom et eksplicit opkald til Forbindelsen er tæt () metode.

5. Brug af BasicConnectionPool Klasse

Som forventet bruger vi vores BasicConnectionPool klasse er ligetil.

Lad os oprette en simpel enhedstest og få en samlet H2-forbindelse i hukommelsen:

@Test offentligt nårCalledgetConnection_thenCorrect () {ConnectionPool connectionPool = BasicConnectionPool .create ("jdbc: h2: mem: test", "bruger", "password"); assertTrue (connectionPool.getConnection (). isValid (1)); }

6. Yderligere forbedringer og refactoring

Selvfølgelig er der masser af plads til at tilpasse / udvide den aktuelle funktionalitet i vores implementering af forbindelsespooling.

For eksempel kunne vi refaktorere getConnection () metode, og tilføj support til maksimal poolstørrelse. Hvis alle tilgængelige forbindelser tages, og den aktuelle poolstørrelse er mindre end det konfigurerede maksimum, opretter metoden en ny forbindelse.

Vi kunne desuden kontrollere, om forbindelsen opnået fra puljen stadig er i live, før vi sender den til klienten.

@ Override public Connection getConnection () kaster SQLException {if (connectionPool.isEmpty ()) {if (usedConnections.size () <MAX_POOL_SIZE) {connectionPool.add (createConnection (url, user, password)); } ellers {kast ny RuntimeException ("Maksimal poolstørrelse nået, ingen tilgængelige forbindelser!"); }} Forbindelsesforbindelse = connectionPool .remove (connectionPool.size () - 1); hvis (! forbindelse.isValid (MAX_TIMEOUT)) {forbindelse = createConnection (url, bruger, adgangskode); } usedConnections.add (forbindelse); returforbindelse } 

Bemærk, at metoden nu kaster SQLException, hvilket betyder, at vi også skal opdatere grænsefladesignaturen.

Eller vi kunne tilføje en metode til at lukke vores instans til forbindelsespuljer med yndefuldhed:

offentlig ugyldig nedlukning () kaster SQLException {usedConnections.forEach (dette :: releaseConnection); til (Forbindelse c: connectionPool) {c.close (); } connectionPool.clear (); }

I produktionsklare implementeringer skal en forbindelsespulje tilbyde en masse ekstra funktioner, såsom evnen til at spore de forbindelser, der i øjeblikket er i brug, understøttelse af forberedt sætningspooling og så videre.

Da vi holder tingene enkle, udelader vi, hvordan vi implementerer disse ekstra funktioner og holder implementeringen ikke-trådsikker af hensyn til klarheden.

7. Konklusion

I denne artikel tog vi et dybtgående kig på, hvad forbindelsespooling er, og lærte, hvordan vi rullede vores egen forbindelsepooling-implementering.

Selvfølgelig behøver vi ikke starte fra bunden hver gang vi vil tilføje et komplet poolinglag til forbindelser til vores applikationer.

Derfor lavede vi først en simpel sammenfatning, der viste nogle af de mest populære forbindelsespoolrammer, så vi kan få en klar idé om, hvordan vi kan arbejde med dem og vælge den, der bedst passer til vores krav.

Som sædvanligt er alle kodeeksempler vist i denne artikel tilgængelige på GitHub.