在 Java Swing 桌面應用開發中,JTable 是展示表格數據的核心組件。本文詳細講解兩種將數據庫查詢結果綁定到 JTable 的經典方案:二維數組模式與TableModel 模式,並提供完整代碼、關鍵步驟截圖說明及常見問題處理。
| 構造器 | 參數 | 適用場景 |
|---|---|---|
JTable(Object[][] rowData, Object[] columnNames) | 二維數組數據 + 列名數組 | 數據量較小、一次性加載、結構固定 |
JTable(TableModel dm) | 實現 TableModel 接口的數據模型 | 大數據量、動態更新、需要自定義渲染/編輯 |
ResultSet。ResultSet 轉換為 Object[][] 二維數組。String[]。DefaultTableModel(data, columnNames) 並設置給 JTable。public class UserDao {
// 假設已有獲取 ResultSet 的方法(使用預編譯)
public ResultSet getResultSet(String sql, Object[] params) throws SQLException {
// ... 省略連接、預編譯、設置參數、執行查詢代碼
return resultSet;
}
/** 將 ResultSet 轉為二維數組 */
public Object[][] resultSetToArray(ResultSet rs) throws SQLException {
// 需可滾動結果集以獲取行數
rs.last();
int rowCount = rs.getRow();
rs.beforeFirst();
ResultSetMetaData meta = rs.getMetaData();
int colCount = meta.getColumnCount();
Object[][] data = new Object[rowCount][colCount];
int rowIdx = 0;
while (rs.next()) {
Object[] row = new Object[colCount];
for (int i = 0; i < colCount; i++) {
row[i] = rs.getObject(i + 1); // 保留原始類型,由渲染器格式化
}
data[rowIdx++] = row;
}
return data;
}
}
public class UserTableFrame extends JFrame {
private JTable userTable;
public UserTableFrame() {
initComponents();
loadData();
}
private void loadData() {
try {
UserDao dao = new UserDao();
ResultSet rs = dao.getResultSet("SELECT id, username, email FROM t_user", null);
Object[][] data = dao.resultSetToArray(rs);
String[] columns = {"ID", "用戶名", "郵箱"};
userTable.setModel(new DefaultTableModel(data, columns));
} catch (SQLException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(this, "數據加載失敗: " + e.getMessage());
}
}
}
實現 AbstractTableModel(或繼承 DefaultTableModel),封裝數據訪問邏輯,JTable 僅作為視圖組件,按需請求數據。
public class UserTableModel extends AbstractTableModel {
private final List userList = new ArrayList<>();
private final String[] columnNames = {"ID", "用戶名", "郵箱", "狀態"};
private final UserDao userDao = new UserDao();
// --- AbstractTableModel 必須實現的三個方法 ---
@Override
public int getRowCount() {
return userList.size();
}
@Override
public int getColumnCount() {
return columnNames.length;
}
@Override
public String getColumnName(int column) {
return columnNames[column];
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
User u = userList.get(rowIndex);
switch (columnIndex) {
case 0: return u.getId();
case 1: return u.getUsername();
case 2: return u.getEmail();
case 3: return u.getStatus() == 1 ? "啟用" : "禁用";
default: return null;
}
}
// --- 可選:支持單元格編輯 ---
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return columnIndex == 3; // 僅允許編輯狀態列
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
if (columnIndex == 3) {
User u = userList.get(rowIndex);
u.setStatus("啟用".equals(aValue) ? 1 : 0);
// 同步更新數據庫
userDao.updateStatus(u.getId(), u.getStatus());
fireTableCellUpdated(rowIndex, columnIndex);
}
}
// --- 業務方法 ---
public void loadData() {
try (ResultSet rs = userDao.getResultSet("SELECT id, username, email, status FROM t_user", null)) {
userList.clear();
while (rs.next()) {
User u = new User();
u.setId(rs.getLong("id"));
u.setUsername(rs.getString("username"));
u.setEmail(rs.getString("email"));
u.setStatus(rs.getInt("status"));
userList.add(u);
}
fireTableDataChanged(); // 通知視圖刷新
} catch (SQLException e) {
e.printStackTrace();
}
}
public void addUser(User u) {
userDao.insert(u);
userList.add(u);
fireTableRowsInserted(userList.size() - 1, userList.size() - 1);
}
public void removeUser(int row) {
User u = userList.remove(row);
userDao.delete(u.getId());
fireTableRowsDeleted(row, row);
}
}
JTable 到面板。JTable → Properties → model 屬性。com.example.model.UserTableModel.createInstance()。TableModel 實例。public class UserTableModel extends AbstractTableModel {
// ... 前面的代碼 ...
public static UserTableModel createInstance() {
UserTableModel model = new UserTableModel();
model.loadData(); // 初始化加載數據
return model;
}
}
getRowCount 返回總數,getValueAt 按需查詢)。TableRowSorter)、過濾、單元格渲染/編輯。TableModel 事件機制(fireTableXxx 系列)。覆寫 getColumnClass(int columnIndex) 返回具體類型,JTable 會自動使用對應渲染器:
@Override
public Class> getColumnClass(int columnIndex) {
if (columnIndex == 0) return Long.class;
if (columnIndex == 3) return Boolean.class; // 狀態用勾選框渲染
return String.class;
}
不將所有數據載入 List,而是在 getValueAt 時按需查詢(需緩存當前頁數據),或使用 SwingX JXTable 等增強組件。
數據庫操作耗時,必須在後台線程(SwingWorker)執行,完成後在 EDT 更新模型:
new SwingWorker() {
@Override
protected Void doInBackground() {
model.loadData(); // 後台查詢
return null;
}
@Override
protected void done() {
model.fireTableDataChanged(); // EDT 刷新視圖
}
}.execute();
| 場景 | 推薦方案 | 理由 |
|---|---|---|
| 原型演示、數據<1000行、無編輯需求 | 方案一:二維數組 | 代碼最少,快速見效 |
| 生產環境、需編輯/排序/過濾、數據>1000行 | 方案二:自定義 TableModel | 架構清晰、擴展性強、效能可控 |
| 超大數據量(>10萬行)、分頁/虛擬滾動 | 方案二 + 分頁緩存 或 JXTable | 避免全量載入內存 |
掌握 TableModel 機制是 Swing 表格開發的進階關鍵。建議初學者先用方案一跑通流程,再重構為方案二,體會 MVC 分離帶來的代碼組織優勢。