Apache Derby Bài 20: Đóng gói ứng dụng Java Desktop (JavaFX/Swing) tích hợp sẵn Derby Database
1. Triết lý "Không cài đặt" (Zero-Installation)
Sức mạnh lớn nhất của Apache Derby ở chế độ Embedded là khả năng cung cấp một hệ quản trị cơ sở dữ liệu quan hệ mạnh mẽ cho các ứng dụng Desktop (Swing, JavaFX) hoặc hệ thống POS (Point of Sale) mà người dùng cuối không cần biết đến sự tồn tại của nó.
Họ không cần cài đặt MySQL, không cần cấu hình cổng mạng, không cần tạo tài khoản mật khẩu admin. Mọi thứ chỉ đơn giản là: nhấp đúp vào tệp .jar (hoặc .exe) và ứng dụng tự động chạy với một kho dữ liệu nội bộ bảo mật, toàn vẹn.
Tuy nhiên, để đạt được trải nghiệm mượt mà này, lập trình viên cần giải quyết 3 bài toán khi đóng gói: vị trí lưu file dữ liệu, kịch bản tạo bảng lần đầu chạy, và đóng gói thư viện.
2. Chiến lược định vị thư mục dữ liệu
Trong môi trường IDE, chuỗi jdbc:derby:my_db sẽ tạo thư mục ở gốc dự án. Nhưng khi đóng gói, nếu bạn dùng đường dẫn tương đối, cơ sở dữ liệu sẽ bị rải rác ở bất cứ nơi nào người dùng gọi lệnh chạy ứng dụng (Desktop, thư mục Download, hoặc gây lỗi quyền truy cập nếu chạy ở ổ C:\Program Files).
Best Practice: Trỏ vị trí lưu trữ database về thư mục thư mục cá nhân của người dùng (User Home Directory) hoặc thư mục AppData.
Java
// Lấy đường dẫn thư mục gốc của User hệ điều hành (C:\Users\TenUser hoặc /home/user)
String userHome = System.getProperty("user.home");
// Tạo một thư mục ẩn riêng cho ứng dụng của bạn
String dbPath = userHome + "/.phan_mem_cua_toi/local_db";
// Chuỗi kết nối JDBC tuyệt đối
String jdbcUrl = "jdbc:derby:" + dbPath + ";create=true";
Cách làm này đảm bảo ứng dụng luôn tìm thấy dữ liệu dù người dùng di chuyển tệp thực thi đi bất cứ đâu.
3. Kịch bản khởi tạo lược đồ (Auto-DDL) cho lần chạy đầu tiên
Khi người dùng mở ứng dụng lần đầu, cơ sở dữ liệu được tạo ra nhưng hoàn toàn trống rỗng (chưa có bảng). Bạn cần viết một phương thức chạy ngầm khi khởi động để tự động nạp lược đồ (schema) và dữ liệu mẫu.
Java
public void khoiTaoDatabase(Connection conn) {
try (Statement stmt = conn.createStatement()) {
// Cố gắng tạo bảng
String ddl = "CREATE TABLE danh_muc (id INT PRIMARY KEY, ten VARCHAR(100))";
stmt.executeUpdate(ddl);
// Nếu tạo thành công, tức là lần chạy đầu tiên -> Thêm dữ liệu mẫu
stmt.executeUpdate("INSERT INTO danh_muc VALUES (1, 'Mặc định')");
System.out.println("Cài đặt cơ sở dữ liệu lần đầu thành công!");
} catch (SQLException e) {
// Mã X0Y32: Bảng đã tồn tại -> Bỏ qua, đây không phải lần chạy đầu tiên
if ("X0Y32".equals(e.getSQLState())) {
System.out.println("Cơ sở dữ liệu đã sẵn sàng, bỏ qua bước khởi tạo.");
} else {
System.err.println("Lỗi khởi tạo CSDL: " + e.getMessage());
}
}
}
4. Tích hợp Shutdown an toàn vào luồng sự kiện GUI
Như đã học ở Bài 18, bạn phải gọi lệnh shutdown=true để bảo vệ dữ liệu. Trong các ứng dụng có giao diện (GUI), thay vì dùng Runtime ShutdownHook (đôi khi không đáng tin cậy nếu GUI bị ép đóng), hãy gắn thẳng lệnh tắt DB vào sự kiện tắt cửa sổ của Framework.
Trong JavaFX:
Java
@Override
public void start(Stage primaryStage) {
// ... setup giao diện ...
primaryStage.setOnCloseRequest(event -> {
System.out.println("Đang đóng giao diện, tiến hành tắt Derby...");
try {
DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (SQLException e) {
if ("XJ015".equals(e.getSQLState())) {
System.out.println("Đã tắt Derby an toàn.");
}
}
});
}
Trong Java Swing:
Java
JFrame frame = new JFrame("My App");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new java.awt.event.WindowAdapter() {
@Override
public void windowClosing(java.awt.event.WindowEvent windowEvent) {
// Xử lý tắt Derby tại đây, sau đó gọi System.exit(0)
}
});
5. Đóng gói thành Fat JAR (Uber JAR) với Maven
Nếu bạn chỉ build ra file .jar bình thường, file đó sẽ không chứa derby.jar. Khi người dùng chạy, hệ thống sẽ báo lỗi ClassNotFoundException. Bạn cần một công cụ gom toàn bộ mã nguồn của bạn VÀ thư viện Derby thành một file .jar duy nhất (gọi là Fat JAR).
Sử dụng maven-assembly-plugin bằng cách thêm khối sau vào pom.xml:
XML
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<!-- Trỏ tới class chứa hàm main() của bạn -->
<mainClass>com.hieu.derby.MainApp</mainClass>
</manifest>
</archive>
<descriptorRefs>
<!-- Tham số chỉ định việc đóng gói toàn bộ thư viện con vào -->
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Mở Terminal và chạy lệnh:
Bash
mvn clean package
Kết quả trong thư mục target/ sẽ xuất hiện một tệp có đuôi -jar-with-dependencies.jar. Bạn chỉ cần gửi duy nhất tệp này cho khách hàng. Khách hàng có thể nhấp đúp hoặc chạy java -jar ten-file.jar để sử dụng ứng dụng với đầy đủ tính năng cơ sở dữ liệu ngầm.
6. Tổng kết Phần 3
Chúc mừng bạn đã hoàn tất Phần 3 của Series! Chúng ta đã nắm vững toàn bộ quy trình tích hợp Apache Derby vào ứng dụng Java thực tế: từ cài đặt Driver, thao tác lệnh DML an toàn với PreparedStatement, xử lý ngoại lệ theo mã SQLState, cho đến đóng gói xuất bản.
Ở chế độ Embedded, Derby bị giới hạn bởi việc chỉ một JVM (một người dùng) được mở DB tại một thời điểm. Nếu dự án của bạn lớn lên và cần nhiều máy tính kết nối vào kho dữ liệu chung, chúng ta phải chuyển sang kiến trúc Client-Server.
Khởi động Phần 4: Vận hành Derby Network Server Mode với bài tiếp theo: Apache Derby Bài 21: Khởi động và dừng Derby Network Server qua giao diện dòng lệnh.
All rights reserved