PS.各位大蝦,小弟初來咋到,如有不足,敬請諒解,還需各位大蝦一一指教出來。
首先,數據庫連接池負責分配、管理和釋放數據庫連接,它允許應用程序重復使用一個現有的數據庫連接,而不是再重新建立一個;釋放空閑時間超過最大空閑時間的數據庫連接來避免因為沒有釋放數據庫連接而引起的數據庫連接遺漏。這項技術能明顯提高對數據庫操作的性能。(ps.百度百科)
先說說數據庫連接池的流程,首先是配置文件包含(數據庫連接地址,數據庫用戶名,數據庫密碼,數據庫驅動,連接池數量,開始初始化數量,自動增長數量)這些內容。
然后讀取配置文件,初始化線程池,獲取連接池中連接使用時更改其狀態,如連接池中連接不夠用則自動創建對應配置文件中數量的連接,並判斷是否超過連接池最大數量,當連接使用完后,重新將連接放入池中供其他線程使用,從而達到可復用的效果,下面有使用連接池和沒有使用連接池的對比測試。好了,廢話不多說,直接上代碼:
1. 首先配置文件
對應的數據庫的各種信息,以及數據庫連接池初始化時數量,最大連接數量以及自動增長數量:
jdbcDriver = com.mysql.jdbc.Driver jdbcUrl = jdbc:mysql://localhost:3306/boot?useUnicode=true&characterEncoding=UTF-8 username = root password = 123456 initConnectCount = 20 maxConnects = 100 incrementCount = 3
2.封裝一個連接類
該類中包含一個數據庫連接,一個是否使用的標記以及一個close方法,用來將該連接置為可用狀態,從而達到數據庫連接的可復用,減少持續創建新連接的資源消耗
package com.example.demo.pool; import java.io.IOException; import java.io.InputStream; import java.sql.*; import java.util.Properties; import java.util.Vector; /** * 連接類 * * @author Administrator */ public class PoolConnection { /** * 數據庫連接 */ private Connection conn = null; /** * 標記該連接是否使用 */ private boolean isUse = false; /* * 構造方法 * */ public PoolConnection(Connection conn, boolean isUse) { this.conn = conn; this.isUse = isUse; } /** * 查詢實現 */ public ResultSet queryBySql(String sql) { Statement sm = null; ResultSet rs = null; try { sm = conn.createStatement(); rs = sm.executeQuery(sql); } catch (SQLException e) { e.printStackTrace(); } return rs; } public Connection getConn() { return conn; } public void setConn(Connection conn) { this.conn = conn; } public boolean isUse() { return isUse; } public void setUse(boolean use) { isUse = use; } /** * 將該連接置為可用狀態 */ public void close() { this.isUse = false; } }
3.連接池接口
對外提供的連接池的接口
package com.example.demo.pool; import java.sql.Connection; public interface IPool { /** * 獲取連接池中可用連接 * */ PoolConnection getConnection();
/**
* 獲取一個數據庫連接(不使用連接池)
* */
Connection getConnectionNoPool();
}
4.連接池實現類以及對應方法
首先加載對應配置文件中信息,初始化數據庫連接池,然后用synchronized來實現多線程情況下線程安全的獲取可用連接
package com.example.demo.pool; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.Driver; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; import java.util.Vector; /** * @author Administrator */ public class JdbcPool implements IPool { private static String jdbcDriver; private static String jdbcUrl; private static String username; private static String password; private static Integer initConnectCount; private static Integer maxConnects; private static Integer incrementCount; private static Vector<PoolConnection> connections = new Vector<>(); /** * 通過實例初始化塊來初始化 * */
{
//讀取對應的配置文件,加載入properties中,並設置到對應的參數中 InputStream is = JdbcPool.class.getClassLoader().getResourceAsStream("jdbc.properties"); Properties properties = new Properties(); try { properties.load(is); } catch (IOException e) { e.printStackTrace(); } jdbcDriver = properties.getProperty("jdbcDriver"); jdbcUrl = properties.getProperty("jdbcUrl"); username = properties.getProperty("username"); password = properties.getProperty("password"); initConnectCount = Integer.valueOf(properties.getProperty("initConnectCount")); maxConnects = Integer.valueOf(properties.getProperty("maxConnects")); incrementCount = Integer.valueOf(properties.getProperty("incrementCount")); try { /* * 注冊jdbc驅動 * */ Driver driver = (Driver) Class.forName(jdbcDriver).newInstance(); DriverManager.registerDriver(driver); /* * 根據initConnectCount來初始化連接池 * */ createConnections(initConnectCount); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } /** * 獲取可用連接 * */ @Override public PoolConnection getConnection() { if (connections.isEmpty()) { System.out.println("連接池中沒有連接"); throw new RuntimeException("連接池中沒有連接"); } return getActiveConnection(); } /** * 同步方法來獲取連接池中可用連接,在多線程情況下,只有一個線程訪問該方法來獲取連接,防止由於多線程情況下多個線程獲取同一個連接從而引起出錯 */ private synchronized PoolConnection getActiveConnection() { /* * 通過循環來獲取可用連接,若獲取不到可用連接,則依靠無限循環來繼續獲取 * */ while (true) { for (PoolConnection con : connections) { if (!con.isUse()) { Connection trueConn = con.getConn(); try { //驗證連接是否失效 0表示不校驗超時 if (!trueConn.isValid(0)) { con.setConn(DriverManager.getConnection(jdbcUrl, username, password)); } } catch (SQLException e) { e.printStackTrace(); } con.setUse(true); return con; } } /* * 根據連接池中連接數量從而判斷是否增加對應的數量的連接 * */ if (connections.size() <= maxConnects - incrementCount) { createConnections(incrementCount); } else if (connections.size() < maxConnects && connections.size() > maxConnects - incrementCount) { createConnections(maxConnects - connections.size()); } } } /* * 創建對應數量的連接並放入連接池中 * */ private void createConnections(int count) { for (int i = 0; i < count; i++) { if (maxConnects > 0 && connections.size() >= maxConnects) { System.out.println("連接池中連接數量已經達到最大值"); throw new RuntimeException("連接池中連接數量已經達到最大值"); } try { Connection connection = DriverManager.getConnection(jdbcUrl, username, password); /* * 將連接放入連接池中,並將狀態設為可用 * */ connections.add(new PoolConnection(connection, false)); } catch (SQLException e) { e.printStackTrace(); } } } /* * 獲取連接池中連接數量 * */ public int getSize() { return connections.size(); }
@Override
public Connection getConnectionNoPool() {
Connection connection = null;
try {
connection = DriverManager.getConnection(jdbcUrl, username, password);
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
}
4.連接池維護類(單例模式)
通過靜態內部類來實現連接池的單例模式
package com.example.demo.pool; /** * @author Administrator */ public class PoolManager { /** * 靜態內部類實現連接池的單例 * */ private static class CreatePool{ private static JdbcPool pool = new JdbcPool(); } public static JdbcPool getInstance(){ return CreatePool.pool; } }
最后,上測試方法:我起了2000個線程,通過new兩個CountDownLatch(不懂的,問度娘,就不一一解釋了)其中一個來實現線程的同時並發執行,不要問為什么是2000不是20000,因為20000個線程本人的渣電腦直接OOM了,所以就2000個測試,然后通過調用另外一個CountDownLatch的await方法來實現等待所有線程全完成從而統計總共花費時間。
熟話說,沒有對比就沒有傷害,我們先來用普通沒有連接池的測試一下2000個連接並行執行的時間,代碼如下:
package com.example.demo.pool; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.concurrent.CountDownLatch; /** * @author Administrator */ public class JdbcNoPoolMain { static final int threadSize = 2000; static JdbcPool jdbcPool = PoolManager.getInstance(); static CountDownLatch countDownLatch1 = new CountDownLatch(1); static CountDownLatch countDownLatch2 = new CountDownLatch(threadSize); public static void main(String[] args) throws InterruptedException { threadTest(); } public static void threadTest() throws InterruptedException { long time1 = System.currentTimeMillis(); for (int i = 0; i < threadSize; i++) { new Thread(new Runnable() { @Override public void run() { try { //使得線程阻塞到coutDownLatch1為0時才執行 countDownLatch1.await(); selectNoPool(); //每個獨立子線程執行完后,countDownLatch2減1 countDownLatch2.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } }).start(); } //將countDownLatch1置為0,從而使線程並發執行 countDownLatch1.countDown(); //等待countDownLatch2變為0時才繼續執行 countDownLatch2.await(); long time2 = System.currentTimeMillis(); System.out.println("thread size: "+threadSize+" no use pool :" + (time2 - time1)); } public static void selectNoPool() throws SQLException { Connection conn = jdbcPool.getConnectionNoPool(); Statement sm = null; ResultSet rs = null; try { sm = conn.createStatement(); rs = sm.executeQuery("select * from user"); } catch (SQLException e) { e.printStackTrace(); } try { while (rs.next()) { System.out.println(Thread.currentThread().getName() + " ==== " + "name: " + rs.getString("name") + " age: " + rs.getInt("age")); } Thread.sleep(100); } catch (SQLException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } rs.close(); sm.close(); conn.close(); } }
下圖顯示運行顯示2000個線程同時並發運行花費24080ms
下面是使用數據庫連接池的測試類:
package com.example.demo.pool; import java.sql.ResultSet; import java.sql.SQLException; import java.util.concurrent.CountDownLatch; /** * @author Administrator */ public class JdbcPoolMain { static final int threadSize = 2000; static JdbcPool jdbcPool = PoolManager.getInstance(); static CountDownLatch countDownLatch1 = new CountDownLatch(1); static CountDownLatch countDownLatch2 = new CountDownLatch(threadSize); public static void main(String[] args) throws InterruptedException { threadTest(); } public static void select() throws SQLException { PoolConnection conn = jdbcPool.getConnection(); ResultSet rs = conn.queryBySql("select * from user"); try { while(rs.next()){ System.out.println(Thread.currentThread().getName()+" ==== "+"name: "+rs.getString("name")+" age: "+rs.getInt("age")); } Thread.sleep(100); } catch (SQLException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } rs.close(); conn.close(); } public static void threadTest() throws InterruptedException { long time1 = System.currentTimeMillis(); for(int i=0;i<threadSize;i++){ new Thread(new Runnable() { @Override public void run() { try {
//阻塞到當countDownLatch1為0才執行 countDownLatch1.await(); select();
//將countDownLatch2減1 countDownLatch2.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } }).start(); } //將countDownLatch1減1,從而使所有子線程同時並發執行 countDownLatch1.countDown(); //等待countDownLatch2為0時繼續執行 countDownLatch2.await(); long time2 = System.currentTimeMillis(); System.out.println("pool size:"+jdbcPool.getSize()); System.out.println("thread size: "+threadSize+" use pool :" + (time2 - time1)); } }
運行結果如下,2000個線程同時並發運行,花費了6065ms,是沒有使用線程池的1/4,並且顯示線程執行完后池中連接數量為74,還沒有達到100,從而知道2000個線程獲取連接基本都是連接的復用已存在的連接,從而提高代碼效率。
以上就是一個簡單的java數據庫連接池的實現,當然還有很多不足之處,例如沒有對連接池動態回收之類的,當並發少時,連接池中連接數量還是維持在峰值,進行獲取連接時候獲取的前面的連接,從而使的連接池中后面的連接獲取不到,造成資源的浪費等等。。。