資料庫通路
通路資料庫主要有以下幾個步驟:
加載資料庫驅動
建立資料庫連接配接
執行通路操作并處理執行結果
關閉連接配接,釋放資源
在每一次請求資料庫都要經曆上述過程,建立連接配接和釋放資源也都是些重複性的動作,當請求量比較大時,資源是個很大的浪費。如何優化呢,可以使用連接配接池。
連接配接池
資料庫連接配接池負責配置設定、管理和釋放資料庫連接配接,它允許應用程式重複使用一個現有的資料庫連接配接,而不是再重建立立一個;釋放空閑時間超過最大空閑時間的資料庫連接配接來避免因為沒有釋放資料庫連接配接而引起的資料庫連接配接遺漏。
原理分析
資料庫連接配接是通路資料庫必須的,可以在系統初始化時提前建立一定數量的連接配接,儲存起來,當有建立連接配接的請求過來時,就直接拿出來,标記為使用中(避免與其他請求拿到同一個),使用完後,再放回連接配接池中。過程如下

系統在啟動時初始化連接配接池;
向連接配接池請求可用的資料庫連接配接;
如果沒有擷取到可用的資料庫連接配接,并且連接配接池中連接配接的數量小于最大連接配接數,則按照規定的步長給連接配接池中添加連接配接,然後再擷取,如果連接配接池中的數量已經到了最大連接配接數還沒有擷取到可用的連接配接,則等待其他請求釋放了連接配接後再擷取;
使用擷取到的資料庫連接配接請求資料庫;
将資料庫連接配接放回連接配接池,供其他連接配接使用;
簡單模拟實作
public class Pool {
private String driver = null;//資料庫驅動
private String url = null;//連接配接位址
private String username = null;//使用者
private String password = null;//密碼
//初始化連接配接數
private static int initSize = 2;
//池中最大連接配接數
private static int maxSize = 5;
//每次建立的連接配接數
private static int stepSize = 2;
//逾時時間
private static int timeout = 2000;
//用來儲存建立的資料庫連接配接
private List connectionPool = new ArrayList();
private Lock lock = new ReentrantLock();
public Pool(String driver, String url, String username, String password) throws Exception {
this.driver = driver;
this.url = url;
this.username = username;
this.password = password;
//建立連接配接池時初始化initSize個資料庫連接配接放入池中
resizePool(initSize);
}
private void resizePool(int num) throws Exception {
//池中現有的連接配接數
int currentNum = connectionPool.size();
//池中的連接配接數不能超過設定的最大連接配接數
if (maxSize < currentNum + num) {
num = maxSize - currentNum;
}
//建立連接配接放入池中
for(int i=0; i
PooledConnection conn = newPooledConnection();
connectionPool.add(conn);
}
}
private PooledConnection newPooledConnection() throws Exception {
Connection conn = createConnection();//資料庫連接配接
PooledConnection pconn = new PooledConnection(conn);//連接配接池中的連接配接
return pconn;
}
private Connection createConnection() throws Exception {
//加載驅動
this.getClass().getClassLoader().loadClass(driver);
//建立連接配接
Connection conn = null;
conn = DriverManager.getConnection(url, username, password);
return conn;
}
public synchronized Connection getConnection() throws Exception {
Connection conn = null;
//從連接配接池中擷取連接配接
if(connectionPool.size() > 0){
//擷取一個空閑的資料庫連接配接
conn = getFreeConnFromPool();
//沒有擷取到連接配接
while(conn == null){
//隔2秒 重新擷取
System.out.println(Thread.currentThread().getName() + " 等待擷取連接配接");
Thread.sleep(2000);
conn = getFreeConnFromPool();
}
}
return conn;
}
private Connection getFreeConnFromPool() throws Exception {
Connection conn = null;
//擷取可用的連接配接
conn = findAvailableConn();
//沒有擷取到可用的連接配接
if(conn == null){
//重新添加資料庫連接配接到連接配接池中
resizePool(stepSize);
//擷取可用的連接配接
conn = findAvailableConn();
}
return conn;
}
private Connection findAvailableConn() throws Exception {
Connection conn = null;
if(connectionPool.size() > 0){
for(PooledConnection cip : connectionPool){
if(!cip.isBusy()){
conn = cip.getConn();
cip.setBusy(true);//擷取後将目前連接配接狀态标記為 執行
//判斷目前連接配接是否可用
if(!conn.isValid(timeout)){
//conn.isValid如果連接配接未關閉且有效,則傳回true
//目前連接配接池連接配接的資料庫連接配接有問題,建立一個新的資料庫連接配接代替它
conn = createConnection();
cip.setConn(conn);
}
break;
}
}
}
return conn;
}
public void returnConnToPool(Connection conn){
for (PooledConnection cip : connectionPool) {
if (cip.getConn() == conn) {
cip.setBusy(false);//設定為空閑
System.out.println(Thread.currentThread().getName() + " 釋放了連接配接");
break;
}
}
}
}
public class PooledConnection {
//資料庫連接配接
private Connection conn;
//用于辨別目前資料庫連接配接的狀态 true:執行 false:空閑
private boolean busy;
public PooledConnection(Connection conn) {
this.conn = conn;
}
// 此處省略get set方法
}
測試
public class App {
public static void main(String[] args) throws Exception {
//建立一個連接配接池
Pool pool = new Pool("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/test",
"root", "123456");
//建立7個線程,模拟并發
Thread[] threads = new Thread[7];
for(int i=0;i
int t = i * 1000;
threads[i] = new Thread(()->{
Connection conn = null;
try {
conn = pool.getConnection();
if(conn != null){
System.out.println(Thread.currentThread().getName()+"擷取到連接配接 "+conn);
Thread.sleep(3000 + t);//模拟每個連接配接使用時間不等
pool.returnConnToPool(conn);
}else{
System.out.println(Thread.currentThread().getName()+" 沒有擷取到連接配接");
}
} catch (Exception e) {
e.printStackTrace();
}
}, "Thread-"+i);
}
for(Thread thread : threads){
thread.start();
}
}
}
測試結果
可以看出,請求數超過池中的最大數時,多餘的請求會進入等待狀态,等到其他的連接配接被釋放後才會擷取到連接配接,線程0和線程5用的同一個連接配接,線程1和6用的同一個連接配接,實作了資源的重複利用,沒有在去重新建立和關閉連接配接,節省了完成這些工作需要的時間,提高了效率。