Java操作資料庫——手動實現資料庫連接池 摘要:本文主要學習瞭如何手動實現一個資料庫連接池,以及在這基礎上的一些改進。 部分內容來自以下博客: https://blog.csdn.net/soonfly/article/details/72731144 一個簡單的資料庫連接池 連接池工具類 連接池 ...
Java操作資料庫——手動實現資料庫連接池
摘要:本文主要學習瞭如何手動實現一個資料庫連接池,以及在這基礎上的一些改進。
部分內容來自以下博客:
https://blog.csdn.net/soonfly/article/details/72731144
一個簡單的資料庫連接池
連接池工具類
連接池使用了線程安全的隊列存儲連接資源,保證了線程安全。
提供了獲取連接和釋放連接的方法,實現了連接資源的迴圈使用。
在對線程進行技術時,使用原子類,保證了線程計數在多線程環境下的安全。
代碼如下:
1 public class DataPoolUtils { 2 // 活動連接,使用線程安全的隊列 3 private static LinkedBlockingQueue<Connection> busy = new LinkedBlockingQueue<Connection>(); 4 // 空閑連接,使用線程安全的隊列 5 private static LinkedBlockingQueue<Connection> idle = new LinkedBlockingQueue<Connection>(); 6 // 已創建連接數,使用原子操作類實現線程安全 7 private static AtomicInteger createCount = new AtomicInteger(0); 8 // 最大連接數 9 private static int maxConnection = 5; 10 // 最大等待毫秒數 11 private static int maxWaitTimeout = 1000; 12 13 /** 14 * 創建連接 15 * @return 16 * @throws Exception 17 */ 18 private Connection createConnection() throws Exception { 19 Properties pros = new Properties(); 20 InputStream is = DataPoolUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"); 21 pros.load(is); 22 String driverClass = pros.getProperty("driverClass"); 23 Class.forName(driverClass); 24 String url = pros.getProperty("url"); 25 String user = pros.getProperty("user"); 26 String password = pros.getProperty("password"); 27 return DriverManager.getConnection(url, user, password); 28 } 29 30 /** 31 * 關閉連接 32 * @param connection 33 */ 34 private void closeConnection(Connection connection) { 35 try { 36 if (!connection.isClosed()) { 37 connection.close(); 38 } 39 } catch (SQLException e) { 40 e.printStackTrace(); 41 } 42 } 43 44 /** 45 * 獲取連接 46 * @return 47 * @throws Exception 48 */ 49 public Connection getConnection() throws Exception { 50 // 嘗試獲取空閑連接 51 Connection connection = idle.poll(); 52 if (connection == null) { 53 // 嘗試創建連接,使用雙重CAS檢查現有連接數是否小於最大連接數 54 if (createCount.get() < maxConnection) { 55 if (createCount.incrementAndGet() <= maxConnection) { 56 connection = createConnection(); 57 } else { 58 createCount.decrementAndGet(); 59 } 60 } 61 // 嘗試等待獲取空閑連接,實現超時等待機制 62 if (connection == null) { 63 connection = idle.poll(maxWaitTimeout, TimeUnit.MILLISECONDS); 64 if (connection == null) { 65 throw new Exception("獲取連接超時"); 66 } 67 } 68 } 69 busy.offer(connection); 70 return connection; 71 } 72 73 /** 74 * 歸還連接 75 * @param connection 76 */ 77 public void releaseConnection(Connection connection) { 78 // 處理空連接 79 if (connection == null) { 80 createCount.decrementAndGet(); 81 return; 82 } 83 // 處理移除失敗的連接 84 boolean removeResult = busy.remove(connection); 85 if (!removeResult) { 86 closeConnection(connection); 87 createCount.decrementAndGet(); 88 return; 89 } 90 // 處理已經關閉的連接 91 try { 92 if (connection.isClosed()) { 93 createCount.decrementAndGet(); 94 return; 95 } 96 } catch (SQLException e) { 97 e.printStackTrace(); 98 } 99 // 處理添加失敗的連接 100 boolean offerResult = idle.offer(connection); 101 if (!offerResult) { 102 closeConnection(connection); 103 createCount.decrementAndGet(); 104 return; 105 } 106 } 107 }
測試連接池的業務類
為了能夠實現線程的迴圈使用,需要調用線程池的釋放連接資源的方法,而不是將連接資源直接關閉。
代碼如下:
1 public class TestPool { 2 // 根據配置文件里的名稱創建連接池 3 private static DataPoolUtils pool = new DataPoolUtils(); 4 5 /** 6 * 主程式 7 */ 8 public static void main(String[] args) { 9 // 模擬多次對資料庫的查詢操作 10 for (int i = 0; i < 6; i++) { 11 new Thread(new Runnable() { 12 @Override 13 public void run() { 14 select(); 15 } 16 }, "線程" + i).start(); 17 } 18 } 19 20 /** 21 * 查詢程式 22 */ 23 public static void select() { 24 Connection conn = null; 25 PreparedStatement pstmt = null; 26 ResultSet rs = null; 27 // 獲取連接並執行SQL 28 try { 29 conn = pool.getConnection(); 30 pstmt = conn.prepareStatement("select * from student where id = 906"); 31 rs = pstmt.executeQuery(); 32 while (rs.next()) { 33 System.out.println(Thread.currentThread().getName() + "\t" + rs.getString(1) + "\t" + rs.getString(2) + "\t" + rs.getString("address")); 34 } 35 } catch (Exception e) { 36 e.printStackTrace(); 37 } finally { 38 // 釋放資源 39 try { 40 rs.close(); 41 } catch (SQLException e) { 42 e.printStackTrace(); 43 } 44 try { 45 pstmt.close(); 46 } catch (SQLException e) { 47 e.printStackTrace(); 48 } 49 /* 50 try { 51 conn.close(); 52 } catch (SQLException e) { 53 e.printStackTrace(); 54 } 55 */ 56 pool.releaseConnection(conn); 57 } 58 } 59 }
使用動態代理修改原生連接的關閉方法
改進說明
簡單的資料庫連接池已經有了,但是在使用的時候如果調用了原生的關閉方法,會導致連接不能重覆使用。
利用之前學過的動態代理進行改進,使調用關閉方法的時候執行的仍然是連接池裡的釋放資源的方法。
在 DataPoolUtils 工具類里添加動態代理的相關內部類:
1 /** 2 * 代理處理類 3 */ 4 class ConnectionInvocationHandler implements InvocationHandler{ 5 private Connection connection; 6 private DataPoolUtils dpu; 7 8 public ConnectionInvocationHandler(DataPoolUtils dpu, Connection connection) { 9 this.dpu = dpu; 10 this.connection = connection; 11 } 12 13 @Override 14 public Object invoke(Object proxy, Method method, Object[] args) 15 throws Throwable { 16 // 對原生的關閉方法進行修改 17 if(method.getName().equals("close")){ 18 dpu.releaseConnection(connection); 19 return null; 20 }else{ 21 return method.invoke(connection, args); 22 } 23 } 24 }
修改 DataPoolUtils 工具類中 public Connection getConnection() 方法的返回值,將返回值改為使用動態代理後的值:
1 return (Connection) Proxy.newProxyInstance( 2 Connection.class.getClassLoader(), 3 new Class[] { Connection.class }, 4 new ConnectionInvocationHandler(this, connection));
修改 TestPool 業務類中的 public static void select() 方法,將釋放連接改為關閉連接:
1 try { 2 conn.close(); 3 } catch (SQLException e) { 4 e.printStackTrace(); 5 }
註意說明
在工具類的 getConnection() 方法中返回代理類,而不是在工具類的 createConnection() 方法中返回,是因為通過後者得到的對象是要放到活動隊列里的,如果在後者中返回代理對象,那麼就會導致活動隊列里的對象都是代理對象。
那麼在執行代理對象的 close() 方法時,經過動態代理後,實際上是執行的是被代理對象的 releaseConnection() 方法,也就是將被代理對象從活動隊列放到空閑隊列,但因為活動隊列里存放的都是代理對象,導致無法通過被代理對象從活動隊列將代理對象放到空閑隊列,進而導致連接資源並沒有得到迴圈利用。