JDBC 最佳實踐

FunTester發表於2024-07-16

Java 資料庫連線 (JDBC) 是一個功能強大的 API,它彌補了 Java 應用程式與關聯式資料庫之間的差距。透過利用 JDBC,您可以無縫地與資料庫互動以儲存、檢索和運算元據。但是,要有效使用 JDBC,需要遵循最佳實踐,以確保程式碼的最佳效能、安全性和可維護性。

正如我之前提到的觀點,學習一個新事物,首先要掌握其最佳實踐,下面讓我們來研究一下 JDBC 最佳實踐的內容。

JDBC 環境

在與資料庫進行互動之前,Java 資料庫連線(JDBC)技術依賴於一個關鍵元件——JDBC 驅動程式。這個驅動程式是特定於每種資料庫系統的軟體庫,例如 MySQL、Oracle 或 PostgreSQL。它的作用是將 Java 應用程式中的程式碼轉換為資料庫能夠識別和執行的指令。

驅動程式

在軟體開發中,選擇和查詢合適的資料庫驅動程式是至關重要的一步。以下是一些查詢資料庫驅動程式的方法:

  • 官方文件和網站:大多數資料庫系統(如 MySQL、PostgreSQL、MongoDB 等)都有自己的官方網站和文件。官方文件通常提供了詳細的安裝指南、配置說明以及驅動程式下載連結。透過官方文件查詢資料庫驅動程式是最可靠的途徑。
  • 包管理工具:對於使用現代程式語言的開發者來說,包管理工具是查詢和安裝資料庫驅動程式的便捷途徑。例如,Java 的 Maven 和 Gradle,JavaScript 的 npm 和 yarn,Python 的 pip,Go 的 go mod 等,這些工具都有豐富的庫和驅動程式供選擇。
  • 第三方庫和框架:一些第三方庫和框架(如 Hibernate、Spring Data、Django ORM 等)通常內建或推薦使用特定的資料庫驅動程式。使用這些庫和框架時,可以直接參考其文件,找到合適的驅動程式。

透過以上方法,可以有效地找到並選擇適合專案需求的資料庫驅動程式。確保驅動程式的版本與資料庫和應用程式的版本相容,以避免潛在的相容性問題和效能問題。

連線 URL

有了驅動程式後,就該告訴它在哪裡找到資料庫了。此資訊被打包成一個稱為連線 URL的特殊字串。這就像給聚會發指示:

  • 資料庫型別: 這告訴驅動程式使用哪個直譯器(例如,jdbc:mysql MySQL)。
  • 主機: 資料庫伺服器的地址(通常是計算機名稱或 IP 地址)。
  • 埠: 資料庫監聽的特定埠。
  • 資料庫名稱: 要連線的特定資料庫的名稱。
  • 可選附加功能: 可以新增使用者名稱和密碼以確保安全或資料庫其他設定。

以下是 MySQL 資料庫的連線 URL 示例:

jdbc:mysql://localhost:3306/mydatabase?user=fred&password=secret

在此示例中:

  • jdbc:mysql:告訴驅動程式我們正在使用 MySQL。
  • localhost:資料庫伺服器與我們的 Java 應用程式位於同一臺機器上。
  • 3306:MySQL 的預設埠。
  • mydatabase:我們想要連線的特定資料庫。
  • user=fred&password=secret:訪問資料庫的登入憑據(出於安全原因,這些憑據通常是隱藏的)。

安全、高效使用 JDBC

Java 應用程式中透過電子郵件地址搜尋使用者。雖然該 Statement 物件似乎是一種快速解決方案,但它可能會帶來安全風險和效能瓶頸。

  • 語句和 SQL 注入:
String email = userInput;
String sql = "SELECT * FROM users WHERE email = '" + email + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);

此程式碼段使用Statement容易受到 SQL 注入攻擊。惡意使用者可以輸入類似這樣的電子郵件,user@example.com' OR '1'='1'從而繞過預期的檢查並可能檢索所有使用者資料。

  • 效能

雖然 Statement 看起來比較簡單,但對於具有不同值的重複查詢,其效能可能會較低。即使結構保持不變,資料庫每次都需要重新編譯整個查詢。

PreparedStatement物件提供了一個強大且安全的替代方案:

  • 分離查詢和資料:您可以使用佔位符()定義模板查詢,?以供動態使用者輸入。

String sql = "SELECT * FROM users WHERE email = ?";

  • 稍後繫結變數:執行查詢時,將實際值(如電子郵件地址)繫結到這些佔位符。
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, email); 
ResultSet resultSet = preparedStatement.executeQuery();

PreparedStatement 的好處:

PreparedStatement 相比普通的 Statement 有幾個主要優點:

  1. 效能更好:PreparedStatement 會預編譯 SQL 語句,只編譯一次就可以多次執行,而普通 Statement 每次執行都要編譯。對於需要重複執行的 SQL,PreparedStatement 的效能明顯更好。
  2. 防止 SQL 注入:PreparedStatement 使用引數化查詢,可以有效防止 SQL 注入攻擊。它會自動轉義特殊字元,避免惡意 SQL 被注入。
  3. 更易於維護:由於使用佔位符,SQL 語句和引數是分離的,程式碼更清晰易讀,也更容易修改維護。
  4. 支援批次處理:PreparedStatement 可以透過 addBatch() 方法一次性傳送多條 SQL 語句,提高批次處理的效率。
  5. 更好的型別處理:PreparedStatement 可以為引數設定具體的資料型別,避免型別轉換錯誤。
  6. 更好的可讀性:使用引數化查詢使 SQL 語句結構更清晰,提高了程式碼的可讀性。
  7. 快取機制:資料庫可能會快取 PreparedStatement 的執行計劃,進一步提升效能。

這些優點使PreparedStatement成為執行 SQL 的首選方式,特別是對於需要重複執行或包含使用者輸入的SQL語句。

ResultSet

假設我們執行了一個從資料庫檢索資料的查詢。結果儲存在一個特殊物件中: ResultSet。要訪問此資料,您需要一次迭代(迴圈)一行:ResultSet

工作原理如下:

  1. 檢查結果:使用執行查詢後PreparedStatement,使用executeQuery方法獲取一個ResultSet物件。此物件儲存檢索到的資料。
  2. 迴圈遍歷行:使用while迴圈遍歷ResultSetnext方法將ResultSet游標移動到下一行資料。 只要還有更多行(next返回true),迴圈就會繼續。
  3. 訪問資料:在迴圈內部,根據資料庫列中儲存的資料型別使用適當的 getter 方法來訪問當前行中的特定值。以下是一些常見的 getter 方法:
    • getString(int columnIndex):從列中檢索字串值。
    • getInt(int columnIndex):從列中檢索整數值。
    • getDouble(int columnIndex):從列中檢索雙精度浮點值。
    • 對於其他資料型別(如日期、布林值等)也有類似的方法。

下面是一個示例迴圈,它迭代ResultSet並列印每行的電子郵件和年齡:

while (resultSet.next()) {
  String email = resultSet.getString("email");
  int age = resultSet.getInt("age");
  System.out.println("Email: " + email + ", Age: " + age);
}

正確關閉資源

如果ResultSet從圖書館借來的一本書。閱讀完畢(訪問資料)後,使用 close 方法關閉它至關重要。這會釋放物件所持有的資源ResultSet,讓資料庫能夠有效地處理它們。

這是一個很好的做法:

try (ResultSet resultSet = preparedStatement.executeQuery()) {
// 資料遍歷和處理
} catch (SQLException e) {
// 異常處理
}

資料型別

從資料庫檢索資料時,使用 ResultSet 的正確 getter 方法非常重要。根據資料庫列中儲存的資料型別選擇合適的方法。例如,對於儲存字串的 email 列,應該使用 getString 方法來檢索值。使用錯誤的方法(例如對電子郵件字串使用 getInt)可能會導致意外結果甚至異常。

高效的資源管理

想象一下,您的 Java 應用程式與資料庫互動就像訪問圖書館一樣。您需要借用連線來訪問資料,就像借書閱讀一樣。但是,就像讀完書後歸還書一樣,及時關閉連線對於高效的資料庫互動至關重要。

關閉連線

  • 為什麼要關閉連線?

資料庫連線是一種寶貴的資源。不必要地保持它們開啟可能會導致:

  • 資源耗盡:如果您的資料庫保持開啟狀態,則資料庫可能會耗盡其他使用者的可用連線。
  • 效能下降:開啟的連線會消耗資料庫伺服器上的資源,影響整體效能。
  • 結束技巧:try-with-resources ,這是首選方法。即使發生異常,它也會在程式碼塊末尾自動關閉連線(以及其他資源,如 ResultSet)。
try (Connection connection = DriverManager.getConnection(DB_URL, USER, PASSWORD)) {
  // 使用連線資源
} catch (SQLException e) {
  // 處理異常
}

當然你也可以使用 finally 程式碼塊來手動完成這些工作。

Connection connection = null;
try {
  connection = DriverManager.getConnection(DB_URL, USER, PASSWORD);
  // 使用連線資源
} catch (SQLException e) {
  // 處理異常
} finally {
  if (connection != null) {
    connection.close();
  }
}

事務

MySQL 中的事務是一種重要的資料庫功能,用於管理多個 SQL 操作作為一個不可分割的單元。事務確保了資料的完整性和一致性,通常使用以下幾個關鍵字來控制事務操作:

  1. BEGINSTART TRANSACTION: 標誌事務的開始。
  2. COMMIT: 提交事務,將所有已執行的操作永久儲存到資料庫。
  3. ROLLBACK: 回滾事務,取消所有已執行的操作,回到事務開始前的狀態。

在使用事務時,可以將一系列的 SQL 操作組合在一起,確保它們要麼全部成功執行並提交,要麼全部失敗並回滾,以維護資料的完整性。這在處理複雜的資料庫操作或需要原子性的資料更新時特別有用。

連線池

連線池維護一個預先建立的資料庫連線池。當應用程式需要連線時,它會從池中借用一個連線,而不是從頭開始建立一個新連線。與每次建立連線相比,這可以節省時間和資源。使用 MySQL 連線池有以下幾個好處:

  1. 效能最佳化: 連線池在應用啟動時預先建立了一定數量的資料庫連線,並管理這些連線的複用和釋放。這樣可以避免頻繁地建立和銷燬連線,從而減少了資料庫的負擔和響應時間,提高了應用的效能。
  2. 資源管理: 連線池能夠限制同時開啟的連線數量,防止應用程式過載資料庫伺服器。它還可以對連線進行有效的管理,如超時檢測、空閒連線的回收等,確保資料庫資源得到有效利用。
  3. 併發處理: 連線池允許多個執行緒併發地從池中獲取連線,執行資料庫操作,並在完成後釋放連線。這種併發處理能力提高了應用程式的吞吐量和響應速度。
  4. 連線的複用: 連線池可以重複使用已經建立的連線,避免了頻繁地重新建立連線的開銷,提高了資料庫操作的效率和穩定性。
  5. 連線的配置和監控: 連線池通常提供了配置引數和監控功能,可以對連線池的行為進行調整和監視,如最大連線數、最小空閒連線數、連線超時等,有助於最佳化資料庫訪問的管理和效能調優。

透過使用連線池,可以有效地管理資料庫連線,提升應用的效能、穩定性和可擴充套件性,是開發和部署高效資料庫應用的重要手段之一。

錯誤和異常

與資料庫互動的道路很少是一帆風順的。當出現問題時,會丟擲異常來表示潛在問題。在 JDBC 領域,比如 SQLException 是我們的主要敵人。

處理 SQLException

假設 Java 應用程式與資料庫互動,但發生了錯誤(例如查詢中的拼寫錯誤或網路問題)。如果不處理 SQLException 丟擲的錯誤,可能導致程式異常,或者程序退出。正確識別處理 SQLException 能帶來下面好處:

  1. 更好的錯誤診斷:透過正確識別 SQLException,可以準確定位資料庫操作中的具體問題,如連線失敗、語法錯誤或約束違反等。
  2. 增強應用程式穩定性:適當處理 SQLException 可以防止未處理的異常導致應用程式崩潰,提高系統的穩定性和可靠性。
  3. 改善使用者體驗:可以根據不同型別的 SQLException 提供更有意義的錯誤訊息給使用者,而不是顯示通用的資料庫錯誤。
  4. 支援更好的異常恢復:對不同型別的 SQLException 進行分類處理,可以實現更精細的異常恢復策略,如自動重試或回滾事務。

常見 SQLException

以下是SQLExceptions您可能會遇到的一些常見問題以及處理策略:

  • SQLSyntaxErrorException:這表示您的 SQL 查詢中存在語法錯誤。請仔細檢查您的查詢是否存在拼寫錯誤、缺少分號或語法錯誤。
  • SQLNonTransientException:這表示非瞬時錯誤,這意味著不太可能透過立即重試操作來解決。這可能是資料庫訪問問題、未找到表或許可權錯誤。分析特定的錯誤訊息並採取適當的措施,例如修復查詢或檢查許可權。
  • SQLTransientException:這表示暫時性錯誤,這意味著可以透過重試操作來解決。示例包括網路問題、超時或資料庫過載。您可以在再次嘗試操作之前以合理的延遲實現重試邏輯。
  • 資料截斷:當您嘗試將資料插入超出其定義大小限制的列時,會發生這種情況。檢查您的資料並進行調整以適應列的限制。

記錄異常

雖然捕獲和處理異常至關重要,但記錄錯誤為除錯和監控提供了寶貴的工具。以下是記錄重要性的原因:

  • 詳細資訊:日誌可以捕獲比錯誤訊息更詳細的資訊,例如時間、涉及的使用者和導致錯誤的特定查詢。
  • 故障排除:日誌對於解決開發過程中可能不會立即顯現的問題至關重要。
  • 監控:日誌可以幫助您監控應用程式和資料庫互動的整體執行狀況,在潛在問題造成重大中斷之前發現它們。

在處理 SQLException 時,必須要考慮資料安全性的問題,而且要放在首要的位置。

  • 服務端功能測試
  • 效能測試專題
  • Java、Groovy、Go、Python
  • 單元&白盒&工具合集
  • 測試方案&BUG&爬蟲&UI 自動化
  • 測試理論雞湯
  • 社群風采&影片合集
如果覺得我的文章對您有用,請隨意打賞。您的支援將鼓勵我繼續創作!
打賞支援
暫無回覆。