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 有幾個主要優點:
- 效能更好:PreparedStatement 會預編譯 SQL 語句,只編譯一次就可以多次執行,而普通 Statement 每次執行都要編譯。對於需要重複執行的 SQL,PreparedStatement 的效能明顯更好。
- 防止 SQL 注入:PreparedStatement 使用引數化查詢,可以有效防止 SQL 注入攻擊。它會自動轉義特殊字元,避免惡意 SQL 被注入。
- 更易於維護:由於使用佔位符,SQL 語句和引數是分離的,程式碼更清晰易讀,也更容易修改維護。
- 支援批次處理:PreparedStatement 可以透過 addBatch() 方法一次性傳送多條 SQL 語句,提高批次處理的效率。
- 更好的型別處理:PreparedStatement 可以為引數設定具體的資料型別,避免型別轉換錯誤。
- 更好的可讀性:使用引數化查詢使 SQL 語句結構更清晰,提高了程式碼的可讀性。
- 快取機制:資料庫可能會快取 PreparedStatement 的執行計劃,進一步提升效能。
這些優點使PreparedStatement
成為執行 SQL 的首選方式,特別是對於需要重複執行或包含使用者輸入的SQL
語句。
ResultSet
假設我們執行了一個從資料庫檢索資料的查詢。結果儲存在一個特殊物件中: ResultSet
。要訪問此資料,您需要一次迭代(迴圈)一行:ResultSet
。
工作原理如下:
- 檢查結果:使用執行查詢後
PreparedStatement
,使用executeQuery
方法獲取一個ResultSet
物件。此物件儲存檢索到的資料。 - 迴圈遍歷行:使用
while
迴圈遍歷ResultSet
。next
方法將ResultSet
游標移動到下一行資料。 只要還有更多行(next
返回true
),迴圈就會繼續。 - 訪問資料:在迴圈內部,根據資料庫列中儲存的資料型別使用適當的
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 操作作為一個不可分割的單元。事務確保了資料的完整性和一致性,通常使用以下幾個關鍵字來控制事務操作:
- BEGIN 或 START TRANSACTION: 標誌事務的開始。
- COMMIT: 提交事務,將所有已執行的操作永久儲存到資料庫。
- ROLLBACK: 回滾事務,取消所有已執行的操作,回到事務開始前的狀態。
在使用事務時,可以將一系列的 SQL 操作組合在一起,確保它們要麼全部成功執行並提交,要麼全部失敗並回滾,以維護資料的完整性。這在處理複雜的資料庫操作或需要原子性的資料更新時特別有用。
連線池
連線池維護一個預先建立的資料庫連線池。當應用程式需要連線時,它會從池中借用一個連線,而不是從頭開始建立一個新連線。與每次建立連線相比,這可以節省時間和資源。使用 MySQL 連線池有以下幾個好處:
- 效能最佳化: 連線池在應用啟動時預先建立了一定數量的資料庫連線,並管理這些連線的複用和釋放。這樣可以避免頻繁地建立和銷燬連線,從而減少了資料庫的負擔和響應時間,提高了應用的效能。
- 資源管理: 連線池能夠限制同時開啟的連線數量,防止應用程式過載資料庫伺服器。它還可以對連線進行有效的管理,如超時檢測、空閒連線的回收等,確保資料庫資源得到有效利用。
- 併發處理: 連線池允許多個執行緒併發地從池中獲取連線,執行資料庫操作,並在完成後釋放連線。這種併發處理能力提高了應用程式的吞吐量和響應速度。
- 連線的複用: 連線池可以重複使用已經建立的連線,避免了頻繁地重新建立連線的開銷,提高了資料庫操作的效率和穩定性。
- 連線的配置和監控: 連線池通常提供了配置引數和監控功能,可以對連線池的行為進行調整和監視,如最大連線數、最小空閒連線數、連線超時等,有助於最佳化資料庫訪問的管理和效能調優。
透過使用連線池,可以有效地管理資料庫連線,提升應用的效能、穩定性和可擴充套件性,是開發和部署高效資料庫應用的重要手段之一。
錯誤和異常
與資料庫互動的道路很少是一帆風順的。當出現問題時,會丟擲異常來表示潛在問題。在 JDBC
領域,比如 SQLException
是我們的主要敵人。
處理 SQLException
假設 Java
應用程式與資料庫互動,但發生了錯誤(例如查詢中的拼寫錯誤或網路問題)。如果不處理 SQLException
丟擲的錯誤,可能導致程式異常,或者程序退出。正確識別處理 SQLException
能帶來下面好處:
- 更好的錯誤診斷:透過正確識別 SQLException,可以準確定位資料庫操作中的具體問題,如連線失敗、語法錯誤或約束違反等。
- 增強應用程式穩定性:適當處理 SQLException 可以防止未處理的異常導致應用程式崩潰,提高系統的穩定性和可靠性。
- 改善使用者體驗:可以根據不同型別的 SQLException 提供更有意義的錯誤訊息給使用者,而不是顯示通用的資料庫錯誤。
- 支援更好的異常恢復:對不同型別的 SQLException 進行分類處理,可以實現更精細的異常恢復策略,如自動重試或回滾事務。
常見 SQLException
以下是SQLExceptions
您可能會遇到的一些常見問題以及處理策略:
- SQLSyntaxErrorException:這表示您的 SQL 查詢中存在語法錯誤。請仔細檢查您的查詢是否存在拼寫錯誤、缺少分號或語法錯誤。
- SQLNonTransientException:這表示非瞬時錯誤,這意味著不太可能透過立即重試操作來解決。這可能是資料庫訪問問題、未找到表或許可權錯誤。分析特定的錯誤訊息並採取適當的措施,例如修復查詢或檢查許可權。
- SQLTransientException:這表示暫時性錯誤,這意味著可以透過重試操作來解決。示例包括網路問題、超時或資料庫過載。您可以在再次嘗試操作之前以合理的延遲實現重試邏輯。
- 資料截斷:當您嘗試將資料插入超出其定義大小限制的列時,會發生這種情況。檢查您的資料並進行調整以適應列的限制。
記錄異常
雖然捕獲和處理異常至關重要,但記錄錯誤為除錯和監控提供了寶貴的工具。以下是記錄重要性的原因:
- 詳細資訊:日誌可以捕獲比錯誤訊息更詳細的資訊,例如時間、涉及的使用者和導致錯誤的特定查詢。
- 故障排除:日誌對於解決開發過程中可能不會立即顯現的問題至關重要。
- 監控:日誌可以幫助您監控應用程式和資料庫互動的整體執行狀況,在潛在問題造成重大中斷之前發現它們。
在處理 SQLException
時,必須要考慮資料安全性的問題,而且要放在首要的位置。
- 服務端功能測試
- 效能測試專題
- Java、Groovy、Go、Python
- 單元&白盒&工具合集
- 測試方案&BUG&爬蟲&UI 自動化
- 測試理論雞湯
- 社群風采&影片合集