首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Swing JTable 數據綁定實戰:從二維數組到 TableModel 的完整遷移指南

     編輯:Java 相關知識

前言

本文承接上一篇,針對同一個 JTable 綁定數據庫的需求,提供更詳細的「從方案一遷移到方案二」的重構實戰步驟,並補充 MyEclipse 可視化設計器的具體操作截圖流程、異常處理模式、以及生產環境常用的分頁加載擴展。

快速回顧:兩種綁定方式對比

維度方案一:二維數組 + DefaultTableModel方案二:自定義 TableModel
代碼量極少(~10 行)中等(~80 行)
內存佔用全量載入可按需載入/分頁
編輯支持需手動監聽模型事件原生支持 setValueAt
排序/過濾需外部實現TableRowSorter 原生支持
渲染定制困難覆寫 getColumnClass/渲染器
適用階段原型/演示/小數據生產/中大數據/複雜交互

重構實戰:五步從方案一遷移到方案二

步驟 1:創建領域模型類

public class User {
    private Long id;
    private String username;
    private String email;
    private Integer status; // 1=啟用, 0=禁用

    // Getter/Setter 省略
    // 建議加 toString() 方便調試
}

步驟 2:實現 UserTableModel(繼承 AbstractTableModel)

public class UserTableModel extends AbstractTableModel {
    private static final String[] COLUMNS = {"ID", "用戶名", "郵箱", "狀態"};
    private final List data = new ArrayList<>();
    private final UserDao dao = new UserDao();

    @Override public int getRowCount() { return data.size(); }
    @Override public int getColumnCount() { return COLUMNS.length; }
    @Override public String getColumnName(int col) { return COLUMNS[col]; }

    @Override
    public Class getColumnClass(int col) {
        // 讓 JTable 自動選擇渲染器:Long→右對齊,Boolean→勾選框
        if (col == 0) return Long.class;
        if (col == 3) return Boolean.class;
        return String.class;
    }

    @Override
    public Object getValueAt(int row, int col) {
        User u = data.get(row);
        switch (col) {
            case 0: return u.getId();
            case 1: return u.getUsername();
            case 2: return u.getEmail();
            case 3: return u.getStatus() == 1; // Boolean 顯示為勾選框
            default: return null;
        }
    }

    // --- 編輯支持 ---
    @Override public boolean isCellEditable(int row, int col) { return col == 3; }

    @Override
    public void setValueAt(Object val, int row, int col) {
        if (col == 3 && val instanceof Boolean) {
            User u = data.get(row);
            u.setStatus((Boolean) val ? 1 : 0);
            dao.updateStatus(u.getId(), u.getStatus()); // 同步 DB
            fireTableCellUpdated(row, col);
        }
    }

    // --- 數據操作 ---
    public void reload() {
        try (ResultSet rs = dao.query("SELECT id, username, email, status FROM t_user")) {
            data.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"));
                data.add(u);
            }
            fireTableDataChanged();
        } catch (SQLException e) {
            throw new RuntimeException("加載用戶數據失敗", e);
        }
    }

    public void add(User u) {
        dao.insert(u);
        data.add(u);
        fireTableRowsInserted(data.size() - 1, data.size() - 1);
    }

    public void remove(int row) {
        User u = data.remove(row);
        dao.delete(u.getId());
        fireTableRowsDeleted(row, row);
    }
}

步驟 3:在 MyEclipse 可視化設計器中綁定 Model

  1. 打開 *.java 設計視圖,選中 JTable 組件。
  2. 屬性面板找到 model 屬性,點擊右側「...」按鈕。
  3. 在彈出的對話框中選擇 Custom Code(或 Model from Code)。
  4. 輸入靜態工廠方法:com.example.model.UserTableModel.createInstance()。
  5. 點擊 OK,設計器會在生成的代碼中調用該方法設置模型。

MyEclipse JTable model 屬性設置

選擇 Custom Code

填寫工廠方法路徑

步驟 4:提供靜態工廠方法(供設計器調用)

public class UserTableModel extends AbstractTableModel {
    // ... 前面代碼 ...

    /** 設計器調用的靜態工廠方法 */
    public static UserTableModel createInstance() {
        UserTableModel model = new UserTableModel();
        // 異步加載避免阻塞 UI 初始化
        SwingUtilities.invokeLater(model::reload);
        return model;
    }
}

步驟 5:在 Frame 中添加工具欄操作(新增/刪除/刷新)

public class UserManageFrame extends JFrame {
    private JTable table;
    private UserTableModel tableModel;

    public UserManageFrame() {
        initComponents(); // 設計器生成代碼,含 table.setModel(UserTableModel.createInstance())
        tableModel = (UserTableModel) table.getModel();

        // 啟用排序
        TableRowSorter sorter = new TableRowSorter<>(tableModel);
        table.setRowSorter(sorter);

        // 工具欄按鈕事件
        btnRefresh.addActionListener(e -> loadDataAsync());
        btnAdd.addActionListener(e -> showAddDialog());
        btnDelete.addActionListener(e -> deleteSelected());
    }

    private void loadDataAsync() {
        new SwingWorker() {
            @Override protected Void doInBackground() {
                tableModel.reload();
                return null;
            }
            @Override protected void done() {
                JOptionPane.showMessageDialog(UserManageFrame.this, "刷新完成");
            }
        }.execute();
    }

    private void deleteSelected() {
        int[] rows = table.getSelectedRows();
        if (rows.length == 0) return;
        // 從大到小刪除避免索引偏移
        for (int i = rows.length - 1; i >= 0; i--) {
            int modelRow = table.convertRowIndexToModel(rows[i]);
            tableModel.remove(modelRow);
        }
    }
}

生產環境擴展:分頁加載大數據量

當表數據超過 1 萬行,全量載入會導致界面卡頓、內存飆升。改造 UserTableModel 實現分頁:

核心修改點

public class PagedUserTableModel extends AbstractTableModel {
    private static final int PAGE_SIZE = 100;
    private final List pageData = new ArrayList<>();
    private int currentPage = 0;
    private int totalRows = 0;
    private final UserDao dao = new UserDao();

    // getRowCount 返回總行數,JTable 滾動條按總行數渲染
    @Override public int getRowCount() { return totalRows; }

    @Override
    public Object getValueAt(int row, int col) {
        // 計算行所在頁
        int page = row / PAGE_SIZE;
        int offset = row % PAGE_SIZE;

        // 若未緩存該頁,後台加載
        if (page != currentPage) {
            loadPageAsync(page);
            return "載入中..."; // 佔位文本
        }
        // 頁面已緩存,返回數據
        if (offset < pageData.size()) {
            User u = pageData.get(offset);
            return switch (col) {
                case 0 -> u.getId();
                case 1 -> u.getUsername();
                case 2 -> u.getEmail();
                case 3 -> u.getStatus() == 1;
                default -> null;
            };
        }
        return null;
    }

    private void loadPageAsync(int page) {
        currentPage = page;
        pageData.clear();
        int offset = page * PAGE_SIZE;
        new SwingWorker() {
            @Override protected Void doInBackground() {
                try (ResultSet rs = dao.queryPage("SELECT ... LIMIT ?, ?", offset, PAGE_SIZE)) {
                    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"));
                        pageData.add(u);
                    }
                    // 首次加載需查總數
                    if (totalRows == 0) {
                        totalRows = dao.count("SELECT COUNT(*) FROM t_user");
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                return null;
            }
            @Override protected void done() {
                fireTableDataChanged(); // 觸發視圖重繪
            }
        }.execute();
    }
}

異常處理與健壯性模式

異常場景處理策略
數據庫連接失敗DAO 拋出運行時異常,Model 捕獲並顯示錯誤對話框,不阻塞 UI
編輯時數據庫更新失敗setValueAt 中捕獲異常,回滾模型值,彈出錯誤提示
並發修改衝突引入版本號字段,更新時樂觀鎖檢查
大量數據導出 Excel後台線程分批查詢寫入 SXSSFWorkbook,進度條反饋

總結

通過「領域模型 → 自定義 TableModel → 設計器綁定 → 異步加載 → 分頁擴展」五步重構,可將簡單的 JTable 綁定升級為生產級組件。關鍵在于:數據與視圖分離、異步非阻塞、事件驅動刷新、按需加載。掌握這套模式,應對各類 Swing 表格開發需求皆得心應手。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved