Panduan Sederhana untuk Connection Pooling di Java

1. Ikhtisar

Connection pooling adalah pola akses data yang terkenal, yang tujuan utamanya adalah untuk mengurangi overhead yang terlibat dalam melakukan koneksi database dan membaca / menulis operasi database.

Singkatnya, kumpulan koneksi, pada tingkat paling dasar, implementasi cache koneksi database , yang dapat dikonfigurasi agar sesuai dengan persyaratan tertentu.

Dalam tutorial ini, kita akan membuat ringkasan singkat dari beberapa kerangka kerja penyatuan koneksi yang populer, dan kita akan belajar bagaimana menerapkan dari awal kumpulan koneksi kita sendiri.

2. Mengapa Connection Pooling?

Pertanyaannya retoris, tentu saja.

Jika kami menganalisis urutan langkah-langkah yang terlibat dalam siklus hidup koneksi database pada umumnya, kami akan memahami alasannya:

  1. Membuka koneksi ke database menggunakan driver database
  2. Membuka soket TCP untuk membaca / menulis data
  3. Membaca / menulis data melalui soket
  4. Menutup koneksi
  5. Menutup soket

Menjadi jelas bahwa koneksi database adalah operasi yang cukup mahal , dan dengan demikian, harus dikurangi seminimal mungkin dalam setiap kasus penggunaan yang mungkin (dalam kasus edge, dihindari).

Di sinilah implementasi penggabungan koneksi berperan.

Dengan hanya menerapkan wadah koneksi basis data, yang memungkinkan kami untuk menggunakan kembali sejumlah koneksi yang ada, kami dapat secara efektif menghemat biaya untuk melakukan sejumlah besar perjalanan basis data yang mahal, sehingga meningkatkan kinerja keseluruhan aplikasi berbasis basis data kami.

3. Kerangka Kerja Pooling Koneksi JDBC

Dari perspektif pragmatis, menerapkan kumpulan koneksi dari bawah ke atas tidak ada gunanya, mengingat jumlah kerangka kerja penyatuan koneksi "siap-perusahaan" yang tersedia di luar sana.

Dari yang didaktik, yang menjadi tujuan artikel ini, bukan.

Meski begitu, sebelum kita belajar bagaimana mengimplementasikan pool koneksi dasar, pertama-tama mari kita tunjukkan beberapa framework pooling koneksi yang populer.

3.1. Apache Commons DBCP

Mari kita mulai ringkasan singkat ini dengan Apache Commons DBCP Component, kerangka kerja JDBC penyatuan koneksi berfitur lengkap:

public class DBCPDataSource { private static BasicDataSource ds = new BasicDataSource(); static { ds.setUrl("jdbc:h2:mem:test"); ds.setUsername("user"); ds.setPassword("password"); ds.setMinIdle(5); ds.setMaxIdle(10); ds.setMaxOpenPreparedStatements(100); } public static Connection getConnection() throws SQLException { return ds.getConnection(); } private DBCPDataSource(){ } }

Dalam kasus ini, kami telah menggunakan kelas pembungkus dengan blok statis untuk dengan mudah mengkonfigurasi properti DBCP.

Berikut cara mendapatkan koneksi gabungan dengan kelas DBCPDataSource :

Connection con = DBCPDataSource.getConnection();

3.2. HikariCP

Selanjutnya, mari kita lihat HikariCP, kerangka kerja penyatuan koneksi JDBC secepat kilat yang dibuat oleh Brett Wooldridge (untuk detail lengkap tentang cara mengonfigurasi dan mendapatkan hasil maksimal dari HikariCP, silakan lihat artikel ini):

public class HikariCPDataSource { private static HikariConfig config = new HikariConfig(); private static HikariDataSource ds; static { config.setJdbcUrl("jdbc:h2:mem:test"); config.setUsername("user"); config.setPassword("password"); config.addDataSourceProperty("cachePrepStmts", "true"); config.addDataSourceProperty("prepStmtCacheSize", "250"); config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); ds = new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return ds.getConnection(); } private HikariCPDataSource(){} }

Demikian pula, berikut cara mendapatkan koneksi gabungan dengan kelas HikariCPDataSource :

Connection con = HikariCPDataSource.getConnection();

3.3. C3PO

Terakhir dalam ulasan ini adalah C3PO, koneksi JDBC4 yang kuat dan kerangka penyatuan pernyataan yang dikembangkan oleh Steve Waldman:

public class C3poDataSource { private static ComboPooledDataSource cpds = new ComboPooledDataSource(); static { try { cpds.setDriverClass("org.h2.Driver"); cpds.setJdbcUrl("jdbc:h2:mem:test"); cpds.setUser("user"); cpds.setPassword("password"); } catch (PropertyVetoException e) { // handle the exception } } public static Connection getConnection() throws SQLException { return cpds.getConnection(); } private C3poDataSource(){} }

Seperti yang diharapkan, mendapatkan koneksi gabungan dengan kelas C3poDataSource mirip dengan contoh sebelumnya:

Connection con = C3poDataSource.getConnection();

4. Implementasi Sederhana

Untuk lebih memahami logika yang mendasari penggabungan koneksi, mari buat implementasi sederhana.

Mari kita mulai dengan desain yang digabungkan secara longgar, hanya berdasarkan satu antarmuka:

public interface ConnectionPool { Connection getConnection(); boolean releaseConnection(Connection connection); String getUrl(); String getUser(); String getPassword(); }

The ConnectionPool interface defines the public API of a basic connection pool.

Now, let's create an implementation, which provides some basic functionality, including getting and releasing a pooled connection:

public class BasicConnectionPool implements ConnectionPool { private String url; private String user; private String password; 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)); } return new BasicConnectionPool(url, user, password, pool); } // standard constructors @Override public Connection getConnection() { Connection connection = connectionPool .remove(connectionPool.size() - 1); usedConnections.add(connection); return connection; } @Override public boolean releaseConnection(Connection connection) { connectionPool.add(connection); return usedConnections.remove(connection); } private static Connection createConnection( String url, String user, String password) throws SQLException { return DriverManager.getConnection(url, user, password); } public int getSize() { return connectionPool.size() + usedConnections.size(); } // standard getters }

While pretty naive, the BasicConnectionPool class provides the minimal functionality that we'd expect from a typical connection pooling implementation.

In a nutshell, the class initializes a connection pool based on an ArrayList that stores 10 connections, which can be easily reused.

It's possible to create JDBC connections with the DriverManager class and with Datasource implementations.

As it's much better to keep the creation of connections database agnostic, we've used the former, within the create() static factory method.

In this case, we've placed the method within the BasicConnectionPool, because this is the only implementation of the interface.

In a more complex design, with multiple ConnectionPool implementations, it'd be preferable to place it in the interface, therefore getting a more flexible design and a greater level of cohesion.

The most relevant point to stress here is that once the pool is created, connections are fetched from the pool, so there's no need to create new ones.

Furthermore, when a connection is released, it's actually returned back to the pool, so other clients can reuse it.

There's no any further interaction with the underlying database, such as an explicit call to the Connection's close() method.

5. Using the BasicConnectionPool Class

As expected, using our BasicConnectionPool class is straightforward.

Let's create a simple unit test and get a pooled in-memory H2 connection:

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

6. Further Improvements and Refactoring

Of course, there's plenty of room to tweak/extend the current functionality of our connection pooling implementation.

For instance, we could refactor the getConnection() method, and add support for maximum pool size. If all available connections are taken, and the current pool size is less than the configured maximum, the method will create a new connection.

Also, we could additionally verify whether the connection obtained from the pool is still alive, before passing it to the client.

@Override public Connection getConnection() throws SQLException { if (connectionPool.isEmpty()) { if (usedConnections.size() < MAX_POOL_SIZE) { connectionPool.add(createConnection(url, user, password)); } else { throw new RuntimeException( "Maximum pool size reached, no available connections!"); } } Connection connection = connectionPool .remove(connectionPool.size() - 1); if(!connection.isValid(MAX_TIMEOUT)){ connection = createConnection(url, user, password); } usedConnections.add(connection); return connection; } 

Note that the method now throws SQLException, meaning we'll have to update the interface signature as well.

Or, we could add a method to gracefully shut down our connection pool instance:

public void shutdown() throws SQLException { usedConnections.forEach(this::releaseConnection); for (Connection c : connectionPool) { c.close(); } connectionPool.clear(); }

In production-ready implementations, a connection pool should provide a bunch of extra features, such as the ability for tracking the connections that are currently in use, support for prepared statement pooling, and so forth.

Karena kami akan menyederhanakannya, kami akan menghilangkan cara menerapkan fitur tambahan ini dan menjaga agar penerapannya tetap tidak aman untuk masalah kejelasan.

7. Kesimpulan

Dalam artikel ini, kami melihat secara mendalam apa itu penggabungan koneksi dan mempelajari cara menggulung implementasi penggabungan koneksi kami sendiri.

Tentu saja, kami tidak harus memulai dari awal setiap kali ingin menambahkan lapisan penyatuan koneksi berfitur lengkap ke aplikasi kami.

Itulah mengapa kami pertama kali membuat ringkasan sederhana yang menampilkan beberapa kerangka kerja kumpulan koneksi paling populer, sehingga kami dapat memiliki gagasan yang jelas tentang cara bekerja dengan mereka, dan mengambil yang paling sesuai dengan kebutuhan kami.

Seperti biasa, semua contoh kode yang ditampilkan dalam artikel ini tersedia di GitHub.