Java DB 是 Sun (現 Oracle) 釋出的、基於 Apache Derby 的 100% Java 輕量級關聯式資料庫,僅 2 MB、無需獨立進程、可直接嵌入桌面應用。本文以「通訊錄」範例,完整示範從下載、安裝、NetBeans 整合、JDBC 連線、建立資料表、CRUD 操作到應用程式部署的全流程。
lib/derby.jar。Tools → Libraries → New Library → "JavaDBEmbedded" → Add JAR/Folder → 選擇 derby.jar → Javadoc 標籤指向 docs/javadoc。AddressFrame:主視窗 (JFrame),協調子元件事件。AddressPanel:顯示/編輯單筆記錄的表單。AddressListPanel:左側列表,持有 ListEntry (封裝主鍵 ID)。AddressActionPanel:工具列按鈕 (新增/刪除/編輯/儲存/取消)。AddressDao:資料存取物件 (DAO),封裝所有 JDBC 操作,達成業務邏輯與持久層解耦。Address:POJO,對應 APP.ADDRESS 資料表欄位。Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
驅動類別在 derby.jar 內,無需額外安裝。
// 以使用者家目錄下 .addressbook 為系統目錄
String sysDir = System.getProperty("user.home") + "/.addressbook";
System.setProperty("derby.system.home", sysDir);
// 連線 URL:建立/開啟 DefaultAddressBook,帶入帳密
String url = "jdbc:derby:DefaultAddressBook;create=true;user=dbuser;password=dbuserpwd";
Connection conn = DriverManager.getConnection(url);
derby.system.home 統一管理所有資料庫檔案位置。create=true 讓首次連線自動建立資料庫目錄與檔案。Properties 物件中。
String ddl = "CREATE TABLE APP.ADDRESS (" +
"ID INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1) PRIMARY KEY," +
"LASTNAME VARCHAR(30), FIRSTNAME VARCHAR(30), MIDDLENAME VARCHAR(30)," +
"PHONE VARCHAR(20), EMAIL VARCHAR(30)," +
"ADDRESS1 VARCHAR(30), ADDRESS2 VARCHAR(30)," +
"CITY VARCHAR(30), STATE VARCHAR(30)," +
"POSTALCODE VARCHAR(20), COUNTRY VARCHAR(30))";
try (Statement stmt = conn.createStatement()) {
stmt.execute(ddl);
}
ID 使用 Derby 內建 IDENTITY 自動遞增。VARCHAR,對應 Java String (UTF-16)。
public class AddressDao {
private Connection conn;
private PreparedStatement stmtInsert, stmtUpdate, stmtDelete, stmtSelectAll, stmtSelectById;
public AddressDao() {
// 初始化連線與 PreparedStatements (略)
stmtInsert = conn.prepareStatement(
"INSERT INTO APP.ADDRESS (LASTNAME, FIRSTNAME, MIDDLENAME, PHONE, EMAIL, " +
"ADDRESS1, ADDRESS2, CITY, STATE, POSTALCODE, COUNTRY) VALUES (?,?,?,?,?,?,?,?,?,?,?)",
Statement.RETURN_GENERATED_KEYS); // 取回自動生成的 ID
stmtUpdate = conn.prepareStatement(
"UPDATE APP.ADDRESS SET LASTNAME=?, FIRSTNAME=?, MIDDLENAME=?, PHONE=?, EMAIL=?, " +
"ADDRESS1=?, ADDRESS2=?, CITY=?, STATE=?, POSTALCODE=?, COUNTRY=? WHERE ID=?");
stmtDelete = conn.prepareStatement("DELETE FROM APP.ADDRESS WHERE ID=?");
stmtSelectById = conn.prepareStatement("SELECT * FROM APP.ADDRESS WHERE ID=?");
stmtSelectAll = conn.prepareStatement("SELECT * FROM APP.ADDRESS ORDER BY LASTNAME, FIRSTNAME");
}
public int saveRecord(Address addr) throws SQLException {
bindAddress(stmtInsert, addr);
stmtInsert.executeUpdate();
try (ResultSet keys = stmtInsert.getGeneratedKeys()) {
if (keys.next()) return keys.getInt(1);
}
return -1;
}
public boolean editRecord(Address addr) throws SQLException {
bindAddress(stmtUpdate, addr);
stmtUpdate.setInt(12, addr.getId());
return stmtUpdate.executeUpdate() == 1;
}
public boolean deleteRecord(int id) throws SQLException {
stmtDelete.setInt(1, id);
return stmtDelete.executeUpdate() == 1;
}
public Address findById(int id) throws SQLException {
stmtSelectById.setInt(1, id);
try (ResultSet rs = stmtSelectById.executeQuery()) {
return rs.next() ? mapRow(rs) : null;
}
}
public List findAll() throws SQLException {
List list = new ArrayList<>();
try (ResultSet rs = stmtSelectAll.executeQuery()) {
while (rs.next()) list.add(mapRow(rs));
}
return list;
}
private void bindAddress(PreparedStatement ps, Address a) throws SQLException {
ps.setString(1, a.getLastName());
ps.setString(2, a.getFirstName());
ps.setString(3, a.getMiddleName());
ps.setString(4, a.getPhone());
ps.setString(5, a.getEmail());
ps.setString(6, a.getAddress1());
ps.setString(7, a.getAddress2());
ps.setString(8, a.getCity());
ps.setString(9, a.getState());
ps.setString(10, a.getPostalCode());
ps.setString(11, a.getCountry());
}
private Address mapRow(ResultSet rs) throws SQLException {
Address a = new Address();
a.setId(rs.getInt("ID"));
a.setLastName(rs.getString("LASTNAME"));
// ... 其餘欄位映射
return a;
}
}
dao.saveRecord() → 取回 ID → 更新 AddressListPanel。dao.findById(id) 填充表單 → 修改 → 「儲存」 → dao.editRecord()。dao.deleteRecord(id) → 移除列表項目。嵌入式模式下,發布僅需兩個 JAR:
derby.jar。使用 Ant/Maven 打包為單一可執行 JAR (含 Class-Path: derby.jar Manifest),或用 jlink (JDK 9+) 產生自含執行映像。資料庫檔案位於使用者 ~/.addressbook/DefaultAddressBook,隨使用者資料漫遊,無需管理員權限。
| 問題 | 原因 | 解決 |
|---|---|---|
啟動報 java.sql.SQLException: Database 'DefaultAddressBook' not found | 忘記 create=true 或系統目錄錯誤 | 檢查 derby.system.home 設定與 URL 參數 |
並發存取拋 LOCK_TIMEOUT | 嵌入式模式同一 JVM 內單執行緒安全,多執行緒需同步 | DAO 方法加 synchronized 或改用連接池 (Derby 支援網路伺服器模式) |
| 中文亂碼 | 資料庫建立時未指定 territory/collation | URL 加 ;territory=zh_TW;collation=TERRITORY_BASED |