Java實(shí)現(xiàn)數(shù)據(jù)庫連接池簡易教程
一、引言
池化技術(shù)在Java中應(yīng)用的很廣泛,簡而論之,使用對(duì)象池存儲(chǔ)某個(gè)實(shí)例數(shù)受限制的實(shí)例,開發(fā)者從對(duì)象池中獲取實(shí)例,使用完之后再換回對(duì)象池,從而在一定程度上減少了系統(tǒng)頻繁創(chuàng)建對(duì)象銷毀對(duì)象的開銷。Java線程池和數(shù)據(jù)庫連接池就是典型的應(yīng)用,但并非所有的對(duì)象都適合拿來池化,對(duì)于創(chuàng)建開銷比較小的對(duì)象拿來池化反而會(huì)影響性能,因?yàn)榫S護(hù)對(duì)象池也需要一定的資源開銷,對(duì)于創(chuàng)建開銷較大,又頻繁創(chuàng)建使用的對(duì)象,采用池化技術(shù)會(huì)極大提高性能。
業(yè)界有很多成熟的數(shù)據(jù)庫連接池,比如C3P0,DBCP,Proxool以及阿里的Druid。很多以及開源,在GitHub可以找到源碼,開發(fā)者可以根據(jù)自己的需求結(jié)合各種連接池的特點(diǎn)和性能進(jìn)行選擇。本文僅是為了了解學(xué)習(xí)池化技術(shù),實(shí)現(xiàn)的一個(gè)簡單的數(shù)據(jù)庫連接池,如有錯(cuò)誤,還望批評(píng)指正。
二、設(shè)計(jì)
主要類和接口
.ConnectionParam - 數(shù)據(jù)庫連接池參數(shù)類,負(fù)責(zé)配置數(shù)據(jù)庫連接以及連接池相關(guān)參數(shù)。使用Builder實(shí)現(xiàn)。
driver url user password - 連接數(shù)據(jù)庫所需
minConnection - 最小連接數(shù)
maxConnection - 最大連接數(shù)
minIdle - 最小空閑連接數(shù)
maxWait - 最長等待時(shí)間
private final String driver; private final String url; private final String user; private final String password; private final int minConnection; private final int maxConnection; private final int minIdle; private final long maxWait;
.ConnectionPool - 數(shù)據(jù)庫連接池
ConnectionPool構(gòu)造方法聲明為保護(hù),禁止外部創(chuàng)建,交由ConnectionPoolFactory統(tǒng)一管理。
ConnectionPool實(shí)現(xiàn)DataSource接口,重新getConnection()方法。
ConnectionPool持有兩個(gè)容器 - 一個(gè)Queue存儲(chǔ)空閑的Connection,另一個(gè)Vector(考慮到同步)存儲(chǔ)正在使用的Connection。
當(dāng)開發(fā)者使用數(shù)據(jù)庫連接時(shí),從Queue中獲取,沒有則返回空;使用完成close連接時(shí),則放回Vector。
ConnectionPool提供了一個(gè)簡單的基于minIdle和maxConnection的動(dòng)態(tài)擴(kuò)容機(jī)制。
private static final int INITIAL_SIZE = 5; private static final String CLOSE_METHOD = "close"; private static Logger logger; private int size; private ConnectionParam connectionParam; private ArrayBlockingQueue<Connection> idleConnectionQueue; private Vector<Connection> busyConnectionVector;
.ConnectionPoolFactory - 連接池管理類
ConnectionPoolFactory持有一個(gè)靜態(tài)ConcurrentHashMap用來存儲(chǔ)連接池對(duì)象。
ConnectionPoolFactory允許創(chuàng)建多個(gè)不同配置不同數(shù)據(jù)庫的連接池。
開發(fā)者首次需要使用特定的名稱注冊(cè)(綁定)連接池,以后每次從指定的連接池獲取Connection。
如果連接池不再使用,開發(fā)者可以注銷(解綁)連接池。
private static Map<String, ConnectionPool> poolMap = new ConcurrentHashMap<>();
public static Connection getConnection(String poolName) throws SQLException {
nameCheck(poolName);
ConnectionPool connectionPool = poolMap.get(poolName);
return connectionPool.getConnection();
}
public static void registerConnectionPool(String name, ConnectionParam connectionParam) {
registerCheck(name);
poolMap.put(name, new ConnectionPool(connectionParam));
}
// Let GC
public static void unRegisterConnectionPool(String name) {
nameCheck(name);
final ConnectionPool connectionPool = poolMap.get(name);
poolMap.remove(name);
new Thread(new Runnable() {
@Override
public void run() {
connectionPool.clear();
}
}).start();
}
核心代碼
數(shù)據(jù)庫連接池核心代碼在于getConnection()方法,通常,開發(fā)者處理完數(shù)據(jù)庫操作后,都會(huì)調(diào)用close()方法,Connection此時(shí)應(yīng)該被關(guān)閉并釋放資源。而在數(shù)據(jù)庫連接池中,用戶調(diào)用close()方法,不應(yīng)直接關(guān)閉Connection,而是要放回池中,重復(fù)使用,這里就用到Java動(dòng)態(tài)代理機(jī)制,getConnection返回的并不是“真正”的Connection,而是自定義的代理類(此處使用匿名類),當(dāng)用戶調(diào)用close()方法時(shí),進(jìn)行攔截,放回池中。有關(guān)動(dòng)態(tài)代理,可以參看另一篇博客《Java動(dòng)態(tài)代理簡單應(yīng)用》
@Override
public Connection getConnection() throws SQLException {
try {
final Connection connection = idleConnectionQueue.poll(connectionParam.getMaxWait(), TimeUnit.MILLISECONDS);
if (connection == null) {
logger.info(emptyMsg());
ensureCapacity();
return null;
}
busyConnectionVector.add(connection);
return (Connection) Proxy.newProxyInstance(this.getClass().getClassLoader(),
new Class[]{Connection.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!method.getName().equals(CLOSE_METHOD)) {
return method.invoke(connection, args);
} else {
idleConnectionQueue.offer(connection);
busyConnectionVector.remove(connection);
return null;
}
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
二、使用
首先用戶構(gòu)建數(shù)據(jù)庫連接池參數(shù)(ConnectionParam),包括driver、url、user、password必須項(xiàng),可以自定義minConnection、maxConnection等可選項(xiàng),如果不設(shè)置,則使用系統(tǒng)默認(rèn)值,這是使用Builder構(gòu)建含有大量屬性的好處,其中包括必須屬性和可選屬性。然后向ConnectionPoolFactory使用特定的名稱注冊(cè)連接池,最后通過調(diào)用ConnectionPoolFactory靜態(tài)工廠方法獲取Connection。
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "root";
ConnectionParam connectionParam = new ConnectionParam.ConnectionParamBuilder(driver, url, user, password).build();
ConnectionPoolFactory.registerConnectionPool("test", connectionParam);
Connection connection = ConnectionPoolFactory.getConnection("test");
三、代碼
.ParamConfiguration
package database.config;
import java.io.Serializable;
/**
* DataBase Connection Parameters
* Created by Michael Wong on 2016/1/18.
*/
public class ParamConfiguration implements Serializable {
public static final int MIN_CONNECTION = 5;
public static final int MAX_CONNECTION = 50;
public static final int MIN_IDLE = 5;
public static final long MAX_WAIT = 30000;
private ParamConfiguration() {}
}
.Builder
package database;
/**
* Builder
* Created by Michael Wong on 2016/1/18.
*/
public interface Builder<T> {
T build();
}
.ConnectionParam
package database;
import database.config.ParamConfiguration;
/**
* DataBase Connection Parameters
* Created by Michael Wong on 2016/1/18.
*/
public class ConnectionParam {
private final String driver;
private final String url;
private final String user;
private final String password;
private final int minConnection;
private final int maxConnection;
private final int minIdle;
private final long maxWait;
private ConnectionParam(ConnectionParamBuilder builder) {
this.driver = builder.driver;
this.url = builder.url;
this.user = builder.user;
this.password = builder.password;
this.minConnection = builder.minConnection;
this.maxConnection = builder.maxConnection;
this.minIdle = builder.minIdle;
this.maxWait = builder.maxWait;
}
public String getDriver() {
return this.driver;
}
public String getUrl() {
return this.url;
}
public String getUser() {
return this.user;
}
public String getPassword() {
return this.password;
}
public int getMinConnection() {
return this.minConnection;
}
public int getMaxConnection() {
return this.maxConnection;
}
public int getMinIdle() {
return this.minIdle;
}
public long getMaxWait() {
return this.maxWait;
}
public static class ConnectionParamBuilder implements Builder<ConnectionParam> {
// Required parameters
private final String driver;
private final String url;
private final String user;
private final String password;
// Optional parameters - initialized to default value
private int minConnection = ParamConfiguration.MIN_CONNECTION;
private int maxConnection = ParamConfiguration.MAX_CONNECTION;
private int minIdle = ParamConfiguration.MIN_IDLE;
// Getting Connection wait time
private long maxWait = ParamConfiguration.MAX_WAIT;
public ConnectionParamBuilder(String driver, String url, String user, String password) {
this.driver = driver;
this.url = url;
this.user = user;
this.password = password;
}
public ConnectionParamBuilder minConnection(int minConnection) {
this.minConnection = minConnection;
return this;
}
public ConnectionParamBuilder maxConnection(int maxConnection) {
this.maxConnection = maxConnection;
return this;
}
public ConnectionParamBuilder minIdle(int minIdle) {
this.minIdle = minIdle;
return this;
}
public ConnectionParamBuilder maxWait(int maxWait) {
this.maxWait = maxWait;
return this;
}
@Override
public ConnectionParam build() {
return new ConnectionParam(this);
}
}
}
.ConnectionPool
package database.factory;
import database.ConnectionParam;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Vector;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
/**
* Connection Pool
* Created by Michael Wong on 2016/1/18.
*/
public class ConnectionPool implements DataSource {
private static final int INITIAL_SIZE = 5;
private static final String CLOSE_METHOD = "close";
private static Logger logger;
private int size;
private ConnectionParam connectionParam;
private ArrayBlockingQueue<Connection> idleConnectionQueue;
private Vector<Connection> busyConnectionVector;
protected ConnectionPool(ConnectionParam connectionParam) {
this.connectionParam = connectionParam;
int maxConnection = connectionParam.getMaxConnection();
idleConnectionQueue = new ArrayBlockingQueue<>(maxConnection);
busyConnectionVector = new Vector<>();
logger = Logger.getLogger(this.getClass().getName());
initConnection();
}
private void initConnection() {
int minConnection = connectionParam.getMinConnection();
int initialSize = INITIAL_SIZE < minConnection ? minConnection : INITIAL_SIZE;
try {
Class.forName(connectionParam.getDriver());
for (int i = 0; i < initialSize + connectionParam.getMinConnection(); i++) {
idleConnectionQueue.put(newConnection());
size++;
}
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
@Override
public Connection getConnection() throws SQLException {
try {
final Connection connection = idleConnectionQueue.poll(connectionParam.getMaxWait(), TimeUnit.MILLISECONDS);
if (connection == null) {
logger.info(emptyMsg());
ensureCapacity();
return null;
}
busyConnectionVector.add(connection);
return (Connection) Proxy.newProxyInstance(this.getClass().getClassLoader(),
new Class[]{Connection.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!method.getName().equals(CLOSE_METHOD)) {
return method.invoke(connection, args);
} else {
idleConnectionQueue.offer(connection);
busyConnectionVector.remove(connection);
return null;
}
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
private Connection newConnection() throws SQLException {
String url = connectionParam.getUrl();
String user = connectionParam.getUser();
String password = connectionParam.getPassword();
return DriverManager.getConnection(url, user, password);
}
protected int size() {
return size;
}
protected int idleConnectionQuantity() {
return idleConnectionQueue.size();
}
protected int busyConnectionQuantity() {
return busyConnectionVector.size();
}
private void ensureCapacity() throws SQLException {
int minIdle = connectionParam.getMinIdle();
int maxConnection = connectionParam.getMaxConnection();
int newCapacity = size + minIdle;
newCapacity = newCapacity > maxConnection ? maxConnection : newCapacity;
int growCount = 0;
if (size < newCapacity) {
try {
for (int i = 0; i < newCapacity - size; i++) {
idleConnectionQueue.put(newConnection());
growCount++;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
size = size + growCount;
}
protected void clear() {
try {
while (size-- > 0) {
Connection connection = idleConnectionQueue.take();
connection.close();
}
} catch (InterruptedException | SQLException e) {
e.printStackTrace();
}
}
private String emptyMsg() {
return "Database is busy, please wait...";
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
.ConnectionPoolFactory
package database.factory;
import database.ConnectionParam;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Connection Pool Factory
* Created by Michael Wong on 2016/1/18.
*/
public class ConnectionPoolFactory {
private ConnectionPoolFactory() {}
private static Map<String, ConnectionPool> poolMap = new ConcurrentHashMap<>();
public static Connection getConnection(String poolName) throws SQLException {
nameCheck(poolName);
ConnectionPool connectionPool = poolMap.get(poolName);
return connectionPool.getConnection();
}
public static void registerConnectionPool(String name, ConnectionParam connectionParam) {
registerCheck(name);
poolMap.put(name, new ConnectionPool(connectionParam));
}
// Let GC
public static void unRegisterConnectionPool(String name) {
nameCheck(name);
final ConnectionPool connectionPool = poolMap.get(name);
poolMap.remove(name);
new Thread(new Runnable() {
@Override
public void run() {
connectionPool.clear();
}
}).start();
}
public static int size(String poolName) {
nameCheck(poolName);
return poolMap.get(poolName).size();
}
public static int getIdleConnectionQuantity(String poolName) {
nameCheck(poolName);
return poolMap.get(poolName).idleConnectionQuantity();
}
public static int getBusyConnectionQuantity(String poolName) {
nameCheck(poolName);
return poolMap.get(poolName).busyConnectionQuantity();
}
private static void registerCheck(String name) {
if (name == null) {
throw new IllegalArgumentException(nullName());
}
}
private static void nameCheck(String name) {
if (name == null) {
throw new IllegalArgumentException(nullName());
}
if (!poolMap.containsKey(name)) {
throw new IllegalArgumentException(notExists(name));
}
}
private static String nullName() {
return "Pool name must not be null";
}
private static String notExists(String name) {
return "Connection pool named " + name + " does not exists";
}
}
四、測試
JUnit單元測試
package database.factory;
import database.ConnectionParam;
import org.junit.Test;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
/**
* ConnectionPoolFactory Test
* Created by Michael Wong on 2016/1/20.
*/
public class ConnectionPoolFactoryTest {
@Test
public void testGetConnection() throws SQLException {
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "root";
ConnectionParam connectionParam = new ConnectionParam.ConnectionParamBuilder(driver, url, user, password).build();
ConnectionPoolFactory.registerConnectionPool("test", connectionParam);
List<Connection> connectionList = new ArrayList<>();
for(int i = 0; i < 12; i++) {
connectionList.add(ConnectionPoolFactory.getConnection("test"));
}
print();
close(connectionList);
print();
connectionList.clear();
for(int i = 0; i < 12; i++) {
connectionList.add(ConnectionPoolFactory.getConnection("test"));
}
print();
close(connectionList);
ConnectionPoolFactory.unRegisterConnectionPool("test");
}
@Test(expected = IllegalArgumentException.class)
public void testException() {
try {
ConnectionPoolFactory.getConnection("test");
} catch (SQLException e) {
e.printStackTrace();
}
}
private void close(List<Connection> connectionList) throws SQLException {
for(Connection conn : connectionList) {
if (conn != null) {
conn.close();
}
}
}
private void print() {
System.out.println("idle: " + ConnectionPoolFactory.getIdleConnectionQuantity("test"));
System.out.println("busy: " + ConnectionPoolFactory.getBusyConnectionQuantity("test"));
System.out.println("size: " + ConnectionPoolFactory.size("test"));
}
}
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。
相關(guān)文章
Java基礎(chǔ)之Unsafe內(nèi)存操作不安全類詳解
Java是面向?qū)ο笳Z言,在使用Java編程時(shí),大多數(shù)情況下都不會(huì)直接操作內(nèi)存,而且Java也不提倡直接操作內(nèi)存,但是Java中到底有沒有可以直接操作內(nèi)存的工具類呢?有!Java中提供Unsafe類可以用來來直接操作內(nèi)存,文中詳細(xì)介紹了Unsafe內(nèi)存操作不安全類,需要的朋友可以參考下2021-06-06
Java在長字符串中查找短字符串的實(shí)現(xiàn)多種方法
這篇文章主要介紹了Java在長字符串中查找短字符串的實(shí)現(xiàn)多種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
SpringBoot 如何實(shí)時(shí)刷新靜態(tài)文件
這篇文章主要介紹了SpringBoot如何實(shí)時(shí)刷新靜態(tài)文件,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
Intellij Idea新建SpringBoot項(xiàng)目方式
這篇文章主要介紹了Intellij Idea新建SpringBoot項(xiàng)目方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-09-09
SpringBoot整合Java Web三大件的詳細(xì)過程
這篇文章主要介紹了SpringBoot整合Java Web三大件的詳細(xì)過程,注冊(cè)自定義的Servlet、Filter、Listener組件到springboot內(nèi)嵌的Servlet容器,讓它們發(fā)揮自己的作用,需要的朋友可以參考下2025-04-04

