JDBC/InvocationHandler動態代理實現資料庫連線池、資料來源
阿新 • • 發佈:2018-12-25
Java的JDBC程式設計中,資料庫的連線和關閉是十分耗費資源的。當對資料庫的訪問不是很頻繁時,可以在每次訪問資料庫時建立一個連線,用完之後關閉。但是,對於一個複雜的資料庫應用,頻繁的建立、關閉連線,會極大的減低系統性能,造成瓶頸。所以可以使用資料庫連線池來達到連線資源的共享,使得對於資料庫的連線可以使高效、安全的複用。
MyDataSource 實現資料庫連線池
通過自定義資料庫了連線MyConnection(包裹了真正的Connection),使用一個LinkedList存放MyConnection,當一個數據庫連線使用完成後,重新新增到LinkedList中,實現連線的複用。資料庫連線池初始化時,構造多個數據庫連線,雖然此時比較耗時,但是能夠實現連線池的複用,提高效率。
package com.jdbc.datasource;
import java.sql.*;
import java.util.LinkedList;
/**
* 資料庫連線池
* 自定義實現資料來源
*
*/
public class MyDataSource {
private static final String URL = "jdbc:postgresql://localhost:5432/db";
private static final String USER = "postgres";
private static final String PASSWORD = "root" ;
private static final int INI_COUNT = 5; // 初始連線數
private static final int MAX_COUNT = 10; // 最大連線數
public int curCount= 0; // 當前連線數
// add remove頻繁,LinkedList 效率由於 ArrayList
LinkedList<Connection> connsPool = new LinkedList<Connection>();
/**
* 初始構造多個數據庫連線
*/
public MyDataSource() {
try {
for (int i = 0; i < INI_COUNT; i++) {
this.connsPool.add(this.createConnection());
curCount++;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 建立連線
* @return
* @throws SQLException
*/
public Connection createConnection() throws SQLException {
Connection realConn = DriverManager.getConnection(URL, USER, PASSWORD);
// MyConnection conn = new MyConnection(this, realConn);
/*** Connection 的代理類,繫結真正的Connection,攔截 close()方法 ***/
MyConnectionHandler pHandler = new MyConnectionHandler(this);
return pHandler.bind(realConn);
}
/**
* 釋放
* @param conn 資料庫連線
*/
public void free(Connection conn) {
this.connsPool.addLast(conn);
}
/**
* 獲取連線
* @return
* @throws SQLException
*/
public Connection getConnection() throws SQLException {
Connection conn = null;
/*** 同步加鎖 ***/
synchronized (connsPool) {
if (this.connsPool.size() > 0) {
conn = this.connsPool.removeFirst();
return conn;
} else if (curCount < MAX_COUNT) { // 連線池裡面沒有連線,且當前連線數沒有達到最大連線
this.curCount++; // 建立新連線
conn = this.createConnection();
return conn;
}
throw new SQLException(" 連線池裡已無可用連線 ... ");
}
}
}
MyConnection 實現 Connection介面方式
將自定義類MyConnection實現Connection介面,重寫close()方法,關閉時重新放入連線池,其他的非close()方法則直接轉交給真正的Connection實現即可。該方式的缺點是,需要實現Connection介面的所有方法。
package com.jdbc.datasource;
import java.sql.*;
import java.util.*;
import java.util.concurrent.Executor;
/**
* MyConnection 代理 Connection,實現 Connection介面
* 相當於Connection的子類,可以和Connection一樣操作
* 代理了所有對真正的Connection操作
* 重要:close()方法重寫,關閉時重新放入連線池
* 不足:需要重寫所有方法
* 改進:改用Proxy代理模式實現,只對close()方法進行攔截,不修改其他方法
*
*/
public class MyConnection implements Connection {
private MyDataSource myDataSource = null; // 資料來源
private Connection realConn = null; // 真正的connection
private static final int MAX_USE_COUNT = 5; // 最大使用次數
private int curUseCount = 0; // 當前使用次數
MyConnection(MyDataSource myDataSource, Connection realConn) {
this.myDataSource = myDataSource;
this.realConn = realConn;
}
/**
* close()方法重寫
* 未超過最大使用次數,關閉時重新放入連線池
* @throws SQLException
*/
@Override
public void close() throws SQLException {
curUseCount++;
if (curUseCount < MAX_USE_COUNT) {
this.myDataSource.connsPool.addLast(this);
} else {
this.realConn.close();
this.myDataSource.curCount--;
}
}
/**
* 其他方法直接交給realConn實現
* @throws SQLException
*/
@Override
public boolean isClosed() throws SQLException {
return this.realConn.isClosed();
}
//其他方法略...
}
InvocationHandler動態代理實現方式
MyConnectionHandler類實現InvocationHandler介面,最主要包括兩個步驟:
- Proxy.newProxyInstance()方法
this.warpedConn = (Connection) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] { Connection.class }, this);
使用包裹後的Connection代理真正的Connection - 重寫invoke()方法
public Object invoke(Object proxy, Method method, Object[] args)
攔截代理對應的方法。
package com.jdbc.datasource;
import java.lang.reflect.*;
import java.sql.Connection;
/**
* Connection 代理類
* 攔截 close()方法
*
*/
public class MyConnectionHandler implements InvocationHandler {
private Connection realConn = null; // 真正的連線
private Connection warpedConn = null; // 包裹的連線(代理連線)
private MyDataSource myDataSource = null; // 連線池
private static final int MAX_USE_COUNT = 5; // 最大使用次數
private int curUseCount = 0; // 當前使用次數
/**
* 構造方法
* @param myDataSource 連線池
*/
public MyConnectionHandler(MyDataSource myDataSource) {
this.myDataSource = myDataSource;
}
/**
* 繫結真正的連線到包裹連線
* @param realConn 真正的連線
* @return
*/
public Connection bind(Connection realConn) {
/*** 真正的連線,用於資料庫的其他操作 ***/
this.realConn = realConn;
/*** 代理模式動態生成類 ,實現了Connection.Class 介面, 其方法作用在當前handler上 ***/
/*** 用於攔截 realConn.close() 方法 ***/
this.warpedConn = (Connection) Proxy.newProxyInstance(this.getClass()
.getClassLoader(), new Class[] { Connection.class }, this);
return this.warpedConn; // 返回包裹後的連線
}
/**
* 重寫 invoke() 方法
* 攔截close()方法,關閉連線是重新放回到資料庫連線池
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*** 攔截 realConn.close() 方法 ***/
if (method.getName().equals("close")) {
curUseCount++;
if (curUseCount < MAX_USE_COUNT) {
this.myDataSource.connsPool.addLast(this.warpedConn);
} else {
this.realConn.close();
this.myDataSource.curCount--;
}
}
/*** 其他方法,直接執行到realConn上 ***/
return method.invoke(this.realConn, args);
}
}
Apache Commons DBCP 資料來源
資料來源一般不需要自己實現,apache DBCP資料來源就是一種很好的開源實現。
將資料來源的相關配置資訊以配置檔案的方式進行設定,讀取配置檔案,生成資料來源即可。DBCP資料來源會自動管理資料庫連線池,JDBC操作直接從資料來源中獲取Connection即可。
DBCP資料來源配置資訊:
#連線設定
driverClassName=org.postgresql.Driver
url=jdbc:postgresql://localhost:5432/db
username=postgres
password=root
#<!-- 初始化連線 -->
initialSize=10
#最大連線數量
maxActive=50
#<!-- 最大空閒連線 -->
maxIdle=20
#<!-- 最小空閒連線 -->
minIdle=5
#<!-- 超時等待時間以毫秒為單位 6000毫秒/1000等於60秒 -->
maxWait=60000
#JDBC驅動建立連線時附帶的連線屬性屬性的格式必須為這樣:[屬性名=property;]
#注意:"user" 與 "password" 兩個屬性會被明確地傳遞,因此這裡不需要包含他們。
connectionProperties=useUnicode=true;characterEncoding=gbk
#指定由連線池所建立的連線的自動提交(auto-commit)狀態。
defaultAutoCommit=true
#driver default 指定由連線池所建立的連線的只讀(read-only)狀態。
#如果沒有設定該值,則“setReadOnly”方法將不被呼叫。(某些驅動並不支援只讀模式,如:Informix)
defaultReadOnly=
#driver default 指定由連線池所建立的連線的事務級別(TransactionIsolation)。
#可用值為下列之一:(詳情可見javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
JdbcUtil類使用DBCP資料來源:
package com.utils;
import java.io.*;
import java.sql.*;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
//import com.jdbc.datasource.MyDataSource;
public class JdbcUtil {
/* private static final String URL = "jdbc:postgresql://localhost:5432/db";
private static final String USER = "postgres";
private static final String PASSWORD = "root";*/
// 使用自定義資料來源
// private static MyDataSource myDataSource = null;
// 使用Apache的框架,提供資料來源,實現了DataSource介面
private static DataSource myDataSource = null;
/**
* 私有構造方法
*/
private JdbcUtil() {
}
/**
* 靜態程式碼註冊驅動
*/
static {
try {
Class.forName("org.postgresql.Driver").newInstance();
// 使用自定義資料來源
// myDataSource = new MyDataSource();
// 使用Apache的框架,提供資料來源,實現了DataSource介面
Properties prop = new Properties();
InputStream is = JdbcUtil.class.getClassLoader().
getResourceAsStream("dbcpconfig.properties");
prop.load(is);
myDataSource = BasicDataSourceFactory.createDataSource(prop);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 獲取datasource
* @return
*/
public static final DataSource GetDataSource() {
return myDataSource;
}
/**
* 獲取資料庫連線
* @return
*/
public static final Connection GetConnection() {
Connection conn = null;
try {
// conn = DriverManager.getConnection(URL, USER, PASSWORD);
conn = myDataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
/**
* 關閉相關資源
* @param rs
* @param st
* @param conn
*/
public static final void Free(ResultSet rs, Statement st, Connection conn) {
try {
if (rs != null)
rs.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (st != null)
st.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (conn != null)
try {
conn.close();
// myDataSource.free(conn);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}