Java使用動態代理編寫資料庫連線池
阿新 • • 發佈:2018-12-20
在上一篇部落格中,將5個連線放到棧裡,當做資料庫連線池使用,加快了效率。程式碼如下:
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.ResourceBundle;
import java.util.Stack;
public class DataPool {
private static Stack<Connection> pool = new Stack<Connection>();
static {
try {
//讀取配置檔案的資訊
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
String Driver = bundle.getString("jdbc.driver");
String url = bundle.getString("jdbc.url");
String user = bundle.getString("jdbc.username");
String password = bundle.getString("jdbc.password");
Integer size = Integer.valueOf(bundle.getString("jdbc.initialSize" ));
Class.forName(Driver);//// 另一種寫法new com.mysql.jdbc.Driver();相當於driver寫入記憶體
for (int i = 0; i < size; i++) {
Connection connection = DriverManager.getConnection(url, user, password);//建立連線
pool.push(connection);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConn () {
return pool.pop();
}
public static void close(Connection connection) {
pool.push(connection);
}
但是有一個問題,如果當多執行緒連線資料庫時,當超過5個執行緒連線資料庫時,那麼在第6個執行緒包括之後就連線不到資料庫了。 這裡用了java的動態代理來代理資料庫的連線,攔截連線的close方法,來控制資料庫連線池的連線數量,防止多執行緒訪問時出現獲取不到連線的情況,並且給代理的連線加上一個時間屬性,和實時監控的執行緒。初始化5個連線放到棧裡當做資料庫連線池,如果少於5個連線資料庫時,則去呼叫真正的連線方法,且當連線用完時,將連線的時間清零,放回連線池裡。如果大於5個連線資料庫時,連線池沒有連線時,則建立新的連線放進連線池裡,當連線關閉時,如果連線池裡已有5個連線,多餘的連線如果超過10秒沒被使用,則關閉連線。 實現程式碼如下:
package pool;
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.util.Stack;
public class PoolServer {
// 代理連線類
public class ConnProxy {
// 連線屬性
private Connection conn;
// 用於計時的屬性
private int idle;
public int getIdle() {
return idle;
}
public void setIdle(int idle) {
this.idle = idle;
}
public void setConn(Connection conn) {
this.conn = conn;
}
public Connection getConn() {
return conn;
}
//
public ConnProxy() {
// 設定空閒時間,如果空閒時間超過10秒,則回收,關閉連線
new Thread(() -> {
try {
while (true) {
Thread.sleep(1000);
idle += 1000;
if (idle > 10000) {
synchronized (Object.class) {
if (pool.size() > 5) {
conn.close();
pool.remove(ConnProxy.this);
break;
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
// 使用棧定義資料庫連線池
private Stack<ConnProxy> pool = new Stack<ConnProxy>();
// 初始化連線
{
try {
Class.forName("com.mysql.jdbc.Driver");
for (int i = 0; i < 5; i++) {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/dvdstore", "root", "root");
ConnProxy connProxy = new ConnProxy();
connProxy.setConn(conn);
pool.push(connProxy);
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 獲取連線
// 這裡加了鎖,防止在多執行緒情況下,會同時去獲取連線導致空棧
public synchronized Connection getConn() {
// 如果資料庫池裡沒連線的時候,則建立新的連線放入資料庫連線池
if (pool.isEmpty()) {
for (int i = 0; i < 3; i++) {
try {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/dvdstore", "root",
"root");
ConnProxy connProxy = new ConnProxy();
connProxy.setConn(conn);
pool.push(connProxy);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// ***
// 有連線,則從棧裡獲取連線
final ConnProxy p = pool.pop();
// 使用java動態代理來代理連線類,攔截close這個方法
return (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), new Class[] { Connection.class },
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
synchronized (Object.class) {
// 當連線close則將連線重新放回資料庫連線池
if (method.getName().equals("close")) {
p.setIdle(0);// 被使用過的idle從0開始
pool.push(p);
System.out.println(pool.size());
return null;// 不讓其呼叫真正的close方法
} else {// 當connect不是執行close方法,而是執行其他操作,例如增刪改查等操作
Connection conn = p.getConn();
return method.invoke(conn, args);// 呼叫其真正的connetion方法
}
}
}
});
}
// 測試
public static void main(String[] args) throws Exception {
final PoolServer poolServer = new PoolServer();
for (int i = 0; i < 5; i++) {
new Thread(()-> {
try {
Connection conn = poolServer.getConn();
conn.close();
// System.out.println(conn);
// conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
System.in.read();
}
}