Apache Derby Bài 19: Xử lý lỗi, ngoại lệ SQL (SQLException, SQLWarning) và mã lỗi Derby
1. Kiến trúc xử lý lỗi trong JDBC và Apache Derby
Trong quá trình làm việc với cơ sở dữ liệu, lỗi có thể xảy ra ở bất kỳ khâu nào: sai cú pháp SQL, vi phạm ràng buộc dữ liệu, mất kết nối, hoặc xung đột giao dịch. Apache Derby tuân thủ hoàn toàn tiêu chuẩn JDBC của Java, chuyển giao mọi thông báo lỗi và cảnh báo thông qua hai lớp chính thuộc gói java.sql:
-
SQLException: Lỗi nghiêm trọng khiến luồng thực thi hiện tại bị gián đoạn. Giao dịch (Transaction) liên quan thường bị đình chỉ hoặc rollback tự động. Bạn bắt buộc phải dùng khốitry-catchđể xử lý. -
SQLWarning: Các cảnh báo không làm sập chương trình. Câu lệnh vẫn được thực thi thành công nhưng Derby muốn thông báo một thông tin phụ (ví dụ: dữ liệu bị cắt xén, quyền truy cập bị hạn chế). Cảnh báo bị bỏ qua âm thầm trừ khi bạn chủ động kiểm tra.
2. Giải phẫu cấu trúc của SQLException
Khi bắt được một đối tượng SQLException, bạn không nên chỉ in ra e.getMessage(). Derby cung cấp 4 phương thức quan trọng để bóc tách chính xác nguyên nhân cốt lõi:
-
getMessage(): Chuỗi văn bản mô tả lỗi chi tiết (dành cho con người đọc). -
getSQLState(): Chuỗi 5 ký tự phân loại lỗi theo chuẩn SQL:2003. Trong Derby, mã bắt đầu bằng số (như23,42) là mã chuẩn quốc tế; mã bắt đầu bằng chữ (nhưXJ,X0) là mã đặc thù của Derby. Đây là thông tin quan trọng nhất để lập trình logic xử lý tự động. -
getErrorCode(): Số nguyên thể hiện mức độ nghiêm trọng (Severity Level) trong Derby (từ 0 đến 50000). Số càng cao, lỗi càng nghiêm trọng (ví dụ 40000 là hỏng Database, 50000 là sập toàn bộ hệ thống). -
getNextException(): Derby có cơ chế chuỗi ngoại lệ (Exception Chaining). Một lỗi gốc có thể kéo theo nhiều lỗi phụ. Bạn cần lặp qua chuỗi này để không bỏ sót thông tin.
3. Bảng tra cứu SQLState phổ biến trong Derby
Thay vì so sánh chuỗi getMessage() (vốn có thể thay đổi theo ngôn ngữ derbyLocale), các hệ thống chuyên nghiệp luôn dựa vào mã SQLState để rẽ nhánh xử lý.
| Nhóm lỗi | SQLState | Ý nghĩa & Nguyên nhân | Cách khắc phục |
|---|---|---|---|
| Ràng buộc | 23505 | Duplicate Key: Chèn giá trị trùng vào cột Primary Key hoặc Unique. | Bắt lỗi và thông báo cho người dùng đổi ID/Email khác. |
| Ràng buộc | 23503 | Foreign Key Violation: Xóa cha khi còn con, hoặc chèn con thiếu cha. | Kiểm tra lại logic nghiệp vụ hoặc bổ sung bản ghi cha. |
| Cú pháp/Lược đồ | 42X05 | Table/View Not Found: Bảng không tồn tại. | Kiểm tra sai chính tả tên bảng hoặc quên tạo bảng. |
| Cú pháp/Lược đồ | X0Y32 | Table Already Exists: Cố tạo bảng đã có sẵn. | Bỏ qua ngoại lệ này nếu dùng trong script khởi tạo database. |
| Kết nối / Hệ thống | XJ040 | Database Locked: Lỗi khi khởi động (chế độ Embedded) do thư mục bị khóa. | Đảm bảo JVM khác hoặc công cụ ij đã đóng hoàn toàn kết nối. |
| Giao dịch | 40001 | Deadlock: Bế tắc giao dịch do tranh chấp tài nguyên. | Ứng dụng nên tự động retry lại (thử lại) câu lệnh SQL đó. |
| Vòng đời | 08006 / XJ015 | Successful Shutdown: Database hoặc Engine đã tắt an toàn. | Đây là tính năng, không phải lỗi. Ghi log thành công. |
4. Bắt và trích xuất SQLWarning
Khác với SQLException, SQLWarning không được ném ra qua throw, mà nó được đính kèm vào các đối tượng Connection, Statement, hoặc ResultSet.
Bạn phải chủ động gọi phương thức getWarnings() ngay sau khi thực thi SQL:
Java
// Giả sử stmt là một Statement vừa chạy lệnh
SQLWarning warning = stmt.getWarnings();
while (warning != null) {
System.out.println("CẢNH BÁO SQLState: " + warning.getSQLState());
System.out.println("Nội dung: " + warning.getMessage());
// Lấy cảnh báo tiếp theo trong chuỗi
warning = warning.getNextWarning();
}
// Xóa cảnh báo sau khi đọc xong để không bị trộn lẫn với lệnh tiếp theo
stmt.clearWarnings();
5. Thực hành: Xây dựng trình xử lý ngoại lệ chuyên nghiệp
Đoạn mã sau mô phỏng kịch bản chèn dữ liệu vi phạm khóa chính và minh họa cách bóc tách chuỗi SQLException chuẩn mực cho ứng dụng thực tế.
Java
package com.hieu.derby;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class DerbyErrorHandlerDemo {
private static final String DB_URL = "jdbc:derby:my_embedded_db;create=true";
public static void main(String[] args) {
try (Connection conn = DriverManager.getConnection(DB_URL)) {
// Cố tình chèn 2 bản ghi có cùng ID để ép Derby văng lỗi 23505
String sql = "INSERT INTO nhan_vien (id, ho_ten) VALUES (?, ?)";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, 999);
pstmt.setString(2, "Nguyen A");
pstmt.executeUpdate();
pstmt.setInt(1, 999); // Trùng ID!
pstmt.setString(2, "Nguyen B");
pstmt.executeUpdate();
}
} catch (SQLException e) {
System.err.println("--- PHÁT HIỆN LỖI CƠ SỞ DỮ LIỆU ---");
xulyChuoiNgoaiLe(e);
}
}
/**
* Phương thức phân tích bóc tách toàn bộ chuỗi lỗi của Derby
*/
private static void xulyChuoiNgoaiLe(SQLException e) {
SQLException currentException = e;
while (currentException != null) {
String sqlState = currentException.getSQLState();
int errorCode = currentException.getErrorCode();
String message = currentException.getMessage();
System.out.println("SQLState: " + sqlState + " | ErrorCode: " + errorCode);
System.out.println("Chi tiết: " + message);
// Phân nhánh logic dựa trên mã trạng thái
if ("23505".equals(sqlState)) {
System.out.println("=> [HƯỚNG XỬ LÝ]: Dữ liệu bị trùng lặp khóa chính hoặc duy nhất. Yêu cầu nhập lại.");
} else if ("40001".equals(sqlState)) {
System.out.println("=> [HƯỚNG XỬ LÝ]: Xung đột giao dịch (Deadlock), hệ thống nên tự động chạy lại thao tác này.");
} else if (errorCode >= 40000) {
System.out.println("=> [HƯỚNG XỬ LÝ]: Lỗi hệ thống nghiêm trọng, cần kiểm tra toàn vẹn ổ cứng và thư mục database.");
}
System.out.println("-----------------------------------");
// Tiếp tục duyệt lỗi tiếp theo trong chuỗi
currentException = currentException.getNextException();
}
}
}
Kiến thức về xử lý ngoại lệ giúp ứng dụng Java của bạn trở nên bền bỉ, không bị crash (đóng băng) trước các dữ liệu ngoại lai. Khi đã nắm vững cách quản lý kết nối, thao tác lệnh và bắt lỗi, chúng ta đã sẵn sàng để đóng gói ứng dụng đưa vào thực tiễn sử dụng.
All rights reserved