0

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:

  1. 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ối try-catch để xử lý.

  2. 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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí