Apache Derby Bài 18: Quản lý vòng đời cơ sở dữ liệu nhúng và cơ chế tắt database (shutdown=true)
1. Tại sao phải tắt cơ sở dữ liệu một cách tường minh?
Khác với các hệ quản trị chạy dịch vụ độc lập (như MySQL, SQL Server) nơi bạn chỉ cần đóng đối tượng Connection là đủ, Apache Derby trong chế độ Embedded chia sẻ vòng đời trực tiếp với ứng dụng Java (JVM) của bạn.
Khi bạn gọi conn.close(), bạn chỉ đóng phiên làm việc của một người dùng. Engine của Derby vẫn tiếp tục chạy ngầm trong bộ nhớ, duy trì các thành phần sau:
-
Bộ đệm trang dữ liệu (Page Cache): Lưu trữ các thay đổi chưa kịp ghi xuống đĩa cứng (thư mục
seg0/). -
Khóa tệp (File Lock): Tệp
db.lckvẫn đang khóa chặt thư mục dữ liệu, ngăn không cho tiến trình khác truy cập. -
Nhật ký giao dịch (WAL): Dữ liệu tạm chưa được đồng bộ (checkpoint).
Nếu ứng dụng Java đột ngột kết thúc (bị người dùng tắt hoặc gọi System.exit(0)) mà không thông báo cho Derby, hệ thống sẽ rơi vào trạng thái tắt không an toàn.
2. Hậu quả của việc tắt ứng dụng đột biến
Nhờ cơ chế Write-Ahead Logging (WAL) tuân thủ tính chất ACID, dữ liệu của bạn thường không bị mất khi ứng dụng tắt đột ngột. Tuy nhiên, nó sẽ gây ra các vấn đề:
-
Khởi động chậm ở lần tiếp theo: Ở lần kết nối tới, Derby phát hiện hệ thống tắt không an toàn. Engine sẽ tự động kích hoạt tiến trình Crash Recovery (Phục hồi sau sự cố), đọc lại toàn bộ tệp nhật ký
.datđể tái tạo trạng thái dữ liệu. Quá trình này làm tăng đáng kể thời gian khởi tạo (boot time). -
Khóa tệp bị treo: Tệp
db.lckkhông được dọn dẹp sạch sẽ, đôi khi gây lỗi "Database is locked" nếu cấu hình file system không tự động giải phóng lock khi JVM sập.
3. Cú pháp tắt cơ sở dữ liệu an toàn (Clean Shutdown)
Để thực hiện Clean Shutdown, chúng ta gọi phương thức DriverManager.getConnection() với chuỗi URL có chứa tham số shutdown=true. Không cần cung cấp username/password khi gọi lệnh shutdown cục bộ.
Có hai cấp độ tắt:
Cấp độ 1: Tắt một cơ sở dữ liệu cụ thể
Chỉ ngắt và dọn dẹp bộ nhớ cho một database duy nhất. Engine Derby vẫn chạy để phục vụ các database khác trong cùng dự án.
Java
DriverManager.getConnection("jdbc:derby:my_embedded_db;shutdown=true");
Cấp độ 2: Tắt toàn bộ hệ thống Derby Engine
Dọn dẹp và ngắt mọi database đang mở, sau đó tắt hoàn toàn động cơ Derby khỏi JVM. Thường dùng khi đóng hẳn ứng dụng Java.
Java
DriverManager.getConnection("jdbc:derby:;shutdown=true");
4. Ngoại lệ là thành công: Đặc thù thiết kế của Derby
Đây là điểm khiến 100% người mới học Derby bối rối: Khi lệnh shutdown thực thi thành công, Derby LUÔN LUÔN ném ra một ngoại lệ SQLException.
Đội ngũ phát triển Apache lý giải hành vi này: Việc tắt database làm đứt gãy kết nối mạng lưới nội bộ, do đó cần một tín hiệu mạnh mẽ (Exception) để ngắt luồng thực thi, báo hiệu cho ứng dụng biết tài nguyên đã hoàn toàn bị thu hồi.
Bạn phải bắt (catch) ngoại lệ này và kiểm tra SQLState để xác nhận tắt thành công:
-
Mã
08006: Database cụ thể đã được tắt thành công. -
Mã
XJ015: Toàn bộ Derby Engine đã được tắt thành công.
5. Thực hành: Tích hợp Shutdown Hook trong ứng dụng Java
Cách chuyên nghiệp nhất để quản lý vòng đời Derby là đăng ký một ShutdownHook với JVM. Đoạn mã này sẽ luôn tự động chạy khi ứng dụng kết thúc (ngay cả khi người dùng nhấn Ctrl+C trên console).
Java
package com.hieu.derby;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DerbyLifecycleManager {
private static final String DB_URL = "jdbc:derby:my_embedded_db;create=true";
public static void main(String[] args) {
System.out.println("1. Ứng dụng khởi động...");
// Đăng ký luồng tự động tắt database khi JVM chuẩn bị thoát
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
shutdownDerbyEngine();
}));
// Khởi tạo kết nối làm việc bình thường
try {
DriverManager.getConnection(DB_URL);
System.out.println("2. Đã mở kết nối CSDL và xử lý nghiệp vụ.");
// Giả lập thời gian làm việc của ứng dụng
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("3. Ứng dụng kết thúc chu trình làm việc chính.");
// Khi kết thúc main(), JVM bắt đầu dọn dẹp và gọi ShutdownHook
}
private static void shutdownDerbyEngine() {
System.out.println("=> ShutdownHook kích hoạt: Đang tiến hành tắt Derby Engine...");
boolean isShutdownSuccessful = false;
try {
// Lệnh tắt toàn bộ hệ thống Derby
DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (SQLException e) {
// Kiểm tra SQLState để xác nhận lệnh tắt có thành công không
if ("XJ015".equals(e.getSQLState())) {
isShutdownSuccessful = true;
} else {
System.err.println("=> Lỗi không xác định khi tắt Derby: " + e.getMessage());
}
}
if (isShutdownSuccessful) {
System.out.println("=> Derby Engine đã được tắt và dọn dẹp bộ nhớ an toàn!");
}
}
}
6. Tổng kết
-
Không bao giờ để ứng dụng Embedded thoát mà bỏ qua bước gửi lệnh
shutdown=true. -
Việc tắt an toàn giúp Derby thực thi quá trình checkpoint (ghi dữ liệu từ RAM xuống đĩa), xóa file lock
db.lckvà đảm bảo lần khởi động tiếp theo diễn ra tức thì. -
Sử dụng
Runtime.getRuntime().addShutdownHook()là Best Practice để bao bọc vòng đời của database vào vòng đời của JVM. -
Ghi nhớ logic xử lý ngoại lệ ngược đời của Derby: Bắt được
SQLExceptionvới mã08006hoặcXJ015đồng nghĩa với thành công.
Bài tiếp theo sẽ cung cấp cho bạn một hệ thống từ điển mã lỗi để chủ động xử lý các tình huống ngoại lệ trong quá trình vận hành: Apache Derby Bài 19: Xử lý lỗi, ngoại lệ SQL (SQLException, SQLWarning) và mã lỗi Derby.
All rights reserved