Connection pooling (nhóm kết nối) là một mô hình truy cập dữ liệu với mục đích chính là giảm thiểu việc sử dụng tài nguyên của ứng dụng trong quá trình kết nối và truy vấn cơ sở dữ liệu. Nó cho phép tạo và duy trì một nhóm các kết nối (connection) dùng chung, nhằm tăng hiệu suất cho các ứng dụng bằng cách sử dụng lại các kết nối đã tồn tại thay vì tạo mới mỗi lần có yêu cầu.
Tại sao cần Connection Pooling?
Để hiểu tại sao chúng ta cần connection pooling, hãy cùng phân tích một chu trình của một kết nối từ ứng dụng đến cơ sở dữ liệu:
- Mở kết nối đến database sử dụng database driver.
- Mở một TCP socket để đọc/ghi dữ liệu.
- Đọc/ghi dữ liệu từ socket.
- Đóng kết nối.
- Đóng socket.
Chúng ta có thể thấy rằng một loạt các hoạt động cần thiết để tạo ra một kết nối như vậy sẽ tốn rất nhiều tài nguyên của ứng dụng. Vì thế, những hành động này nên được giảm thiểu trong mọi trường hợp sử dụng.
Connection pooling giúp giảm tải cho ứng dụng bằng cách triển khai một vùng chứa kết nối đến cơ sở dữ liệu, cho phép chúng ta sử dụng lại các kết nối hiện có. Điều này giúp giảm thiểu hoạt động tốn tài nguyên và tăng hiệu suất của ứng dụng.
JDBC Connection Pooling
Trong Java, chúng ta có nhiều thư viện hỗ trợ connection pooling mạnh mẽ như Apache Commons DBCP 2, HikariCP và C3P0. Việc triển khai connection pooling trong Java đơn giản, chỉ cần một vài bước cấu hình.
Apache Commons DBCP 2
Apache Commons DBCP 2 là một thư viện mạnh mẽ với đầy đủ tính năng hỗ trợ connection pooling. Để sử dụng DBCP 2, chúng ta cần thêm dependency vào Maven:
org.apache.commons
commons-dbcp2
2.7.0
Chúng ta cần cấu hình một vài thuộc tính như số lượng connection tối thiểu, tối đa và tổng số connection có thể có. Dưới đây là ví dụ sử dụng Apache Commons DBCP 2:
import org.apache.commons.dbcp2.BasicDataSource;
public class DBCP2Demo {
private static BasicDataSource dataSource = null;
static {
dataSource = new BasicDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/empdb?useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setMinIdle(5);
dataSource.setMaxIdle(10);
dataSource.setMaxTotal(25);
}
public static void main(String[] args) throws SQLException {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = dataSource.getConnection();
statement = connection.createStatement();
resultSet = statement.executeQuery("select * from tblemployee");
while (resultSet.next()) {
System.out.println("empId:" + resultSet.getInt("empId"));
System.out.println("empName:" + resultSet.getString("empName"));
System.out.println("dob:" + resultSet.getDate("dob"));
System.out.println("designation:" + resultSet.getString("designation"));
}
} finally {
resultSet.close();
statement.close();
connection.close();
}
}
}
HikariCP
HikariCP là một thư viện connection pooling rất phổ biến trong cộng đồng Java. Để sử dụng HikariCP, chúng ta cần thêm dependency vào Maven:
com.zaxxer
HikariCP
3.4.5
Chúng ta có thể dễ dàng cấu hình HikariCP trong Java code với các thuộc tính như idleTimeout
(thời gian sống của một connection khi không hoạt động), connectionTimeout
(thời gian client đợi một connection), autoCommit
(tự động commit) và maxPoolSize
(số lượng connection tối đa có thể giữ). Dưới đây là ví dụ sử dụng HikariCP:
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
public class HikariCPDemo {
private static HikariDataSource dataSource = null;
static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/empdb");
config.setUsername("root");
config.setPassword("root");
config.addDataSourceProperty("minimumIdle", "5");
config.addDataSourceProperty("maximumPoolSize", "25");
dataSource = new HikariDataSource(config);
}
public static void main(String[] args) throws SQLException {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = dataSource.getConnection();
statement = connection.createStatement();
resultSet = statement.executeQuery("select * from tblemployee");
while (resultSet.next()) {
System.out.println("empId:" + resultSet.getInt("empId"));
System.out.println("empName:" + resultSet.getString("empName"));
System.out.println("dob:" + resultSet.getDate("dob"));
System.out.println("designation:" + resultSet.getString("designation"));
}
} finally {
resultSet.close();
statement.close();
connection.close();
}
}
}
C3P0
C3P0 là một thư viện connection pooling lâu đời và được sử dụng phổ biến với Hibernate. Để sử dụng C3P0, chúng ta cần thêm dependency vào Maven:
com.mchange
c3p0
0.9.5.5
Chúng ta có thể cấu hình các thuộc tính như driverClass
(JDBC driver), jdbcUrl
(chuỗi kết nối database), initialPoolSize
(số lượng connection được tạo lúc khởi chạy ứng dụng), maxPoolSize
(số lượng connection tối đa có thể giữ), minPoolSize
(số lượng connection tối thiểu cần duy trì) và maxIdleTime
(thời gian sống của một connection khi không hoạt động). Dưới đây là ví dụ sử dụng C3P0:
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class C3P0Demo {
private static ComboPooledDataSource comboPooledDataSource = null;
static {
comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/empdb?useSSL=false");
comboPooledDataSource.setUser("root");
comboPooledDataSource.setPassword("root");
comboPooledDataSource.setMinPoolSize(3);
comboPooledDataSource.setAcquireIncrement(3);
comboPooledDataSource.setMaxPoolSize(30);
}
public static void main(String[] args) throws SQLException {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = comboPooledDataSource.getConnection();
statement = connection.createStatement();
resultSet = statement.executeQuery("select * from tblemployee");
while (resultSet.next()) {
System.out.println("empId:" + resultSet.getInt("empId"));
System.out.println("empName:" + resultSet.getString("empName"));
System.out.println("dob:" + resultSet.getDate("dob"));
System.out.println("designation:" + resultSet.getString("designation"));
}
} finally {
resultSet.close();
statement.close();
connection.close();
}
}
}
Triển khai một Connection Pooling đơn giản
Để hiểu rõ hơn về connection pooling, chúng ta sẽ triển khai một connection pooling đơn giản để hiểu cơ chế hoạt động.
Trước tiên, chúng ta cần thiết kế một interface ConnectionPool
định nghĩa các API sử dụng trong connection pooling:
public interface ConnectionPool {
Connection getConnection();
boolean releaseConnection(Connection connection);
String getUrl();
String getUser();
String getPassword();
}
Tiếp theo, chúng ta sẽ tạo một class triển khai ConnectionPool
interface:
import java.util.ArrayList;
import java.util.List;
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);
}
private BasicConnectionPool(String url, String user, String password, List connectionPool) {
this.url = url;
this.user = user;
this.password = password;
this.connectionPool = connectionPool;
}
@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();
}
// các hàm getter
}
Ở ví dụ trên, chúng ta đã tạo một connection pool lưu trữ trong ArrayList
với 10 connection có thể sử dụng lại. Khi các connection được trả lại, chúng sẽ được giữ lại trong connectionPool
và được sử dụng lại trong những lần tiếp theo.
Lưu ý rằng các bạn có thể sử dụng DriverManager
class và DataSource
mà chúng tôi không đề cập ở trên, nhưng các bạn có thể dễ dàng triển khai với JDBC Driver.