Java操作資料庫——手動實現資料庫連接池

来源:https://www.cnblogs.com/shamao/archive/2019/11/26/11937629.html
-Advertisement-
Play Games

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() 方法,也就是將被代理對象從活動隊列放到空閑隊列,但因為活動隊列里存放的都是代理對象,導致無法通過被代理對象從活動隊列將代理對象放到空閑隊列,進而導致連接資源並沒有得到迴圈利用。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.什麼是redis? Redis 是一個基於記憶體的高性能key-value資料庫。 2.Reids的特點 Redis本質上是一個Key-Value類型的記憶體資料庫,很像memcached,整個資料庫統統載入在記憶體當中進行操作,定期通過非同步操作把資料庫數據flush到硬碟上進行保存。因為是純記憶體操作 ...
  • [TOC] 模板語法 兩種書寫格式: 變數相關 {{ }} 邏輯相關 {% %} 模板傳值 給html頁面傳值的兩種方式 第一種方式 弊端就是:當要傳的變數名很多的時候,就很麻煩 第二種 locals() locals() 會將當前所在的名稱空間中所有的名字全部傳遞給html頁面 傳值 基本數據類型 ...
  • JavaWeb——下載並安裝Tomcat伺服器 摘要:本文主要學習如何下載並安裝Tomcat伺服器。 背景知識 什麼是JavaWeb JavaWeb,是用Java技術來解決相關Web互聯網領域的技術的總稱。Web包括:Web伺服器和Web客戶端兩部分。 Java在最早Web客戶端的應用有Java A ...
  • 現在是北京時間2019年11月28日,大學畢業已經工作四個多月。說來也是奇怪,大學裡面明明主修機械電子工程,幾乎是純機械方向,畢業之後的工作卻與主修的課程毫無關係。因為對機械這一行業毫無興趣,大學裡面花了點時間學了學C,做過一些項目。畢業設計又拿python寫了個爬蟲並且為它做了一個界面(當時整個機 ...
  • 場景 效果 cutecharts的Github: https://github.com/chenjiandongx/cutecharts 註: 博客: https://blog.csdn.net/badao_liumang_qizhi 關註公眾號 霸道的程式猿 獲取編程相關電子書、教程推送與免費下載 ...
  • 一、Tensor 1.1 什麼是Tensor?Tensor的數據類型 Tensor是張量的意思,在TensorFlow中張量可以是標量(scalar)、向量(vector)、矩陣(matrix)、高維度張量(rank>2),像Numpy里的數組就不屬於Tensor。TensorFlow里的常用的數據 ...
  • 慢查詢 一、概念 MySQL的慢查詢,全名是慢查詢日誌,是MySQL提供的一種日誌記錄,用來記錄在MySQL中響應時間超過閥值的語句。 具體環境中,運行時間超過long_query_time值的SQL語句,則會被記錄到慢查詢日誌中。 long_query_time的預設值為10,意思是記錄運行10秒 ...
  • 之前一直是在Ubuntu下進行Python和Django開發,最近換了電腦,把在Virtual Box 下跑的Ubuntu開發機挪過來總是頻繁崩潰,索性就嘗試把開發環境挪到Windows主力機了。 不得不說,巨硬家這幾年在多元並包方面真的是走在了世界前列。特別是VSCode,兩年前已經成為了我在Li ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...