结合使用 JDBC 和 GUI API

samplesCoffeesFrame.java演示了如何将 JDBC 与 GUI API(特别是 Swing API)集成在一起。它在表中显示COFFEES数据库表的内容,并包含使您可以向表中添加行的字段和按钮。以下是此示例的屏幕截图:

Sample CoffeeFrames.java 的屏幕截图

该示例包含五个文本字段,它们与COFFEES表中的每一列相对应。它还包含三个按钮:

  • 在表格中添加行 :根据在文本字段中 Importing 的数据在 samples 的表格中添加一行。

  • 更新数据库 :根据 samples 表中的数据更新表COFFEES

  • 放弃更改 :检索COFFEES表的内容,替换 samples 表中的现有数据。

此 samples(需要CoffeesTableModel)演示了将 JDBC 与 Swing API 集成的以下常规步骤:

Implementing javax.swing.event.TableModel

TableModelinterface使 Java Swing 应用程序可以 管理JTable对象中的数据。samplesCoffeesTableModel.java实现了此interface。它指定JTable对象应如何从RowSet对象检索数据并将其显示在表中。

注意 :尽管此示例显示了 Swing 应用程序中COFFEES表的内容,但只要其数据可以用String对象表示,则CoffeesTableModel类适用于任何 SQL 表。 (但是,对于其他 SQL 表,必须修改允许用户向CoffeesFrame类中指定的COFFEES中添加行的字段.)

在实现interfaceTableModel的方法之前,类CoffeeTableModel的构造函数将初始化这些实现方法所需的各种成员变量,如下所示:

public CoffeesTableModel(CachedRowSet rowSetArg)
    throws SQLException {

    this.coffeesRowSet = rowSetArg;
    this.metadata = this.coffeesRowSet.getMetaData();
    numcols = metadata.getColumnCount();

    // Retrieve the number of rows.
    this.coffeesRowSet.beforeFirst();
    this.numrows = 0;
    while (this.coffeesRowSet.next()) {
        this.numrows++;
    }
    this.coffeesRowSet.beforeFirst();
}

下面介绍在此构造函数中初始化的成员变量:

  • CachedRowSet coffeesRowSet:存储表COFFEES的内容。

此示例使用RowSet对象,尤其是CachedRowSet对象,而不是ResultSet对象,这有两个原因。 CachedRowSet对象使应用程序的用户无需连接数据库就可以更改其中包含的数据。另外,因为CachedRowSet对象是 JavaBeans 组件,所以当某些事情发生时,它可以通知其他组件。在此示例中,将新行添加到CachedRowSet对象时,它将通知正在将其数据呈现在表中的 Swing 组件刷新自身并显示新行。

  • ResultSetMetaData metadata:检索表COFFEES中的列数以及每列的名称。

  • int numcols, numrows:分别在表COFFEES中存储列数和行数。

CoffeesTableModel.java示例通过TableModelinterface实现了以下方法:

  • Class<?> getColumnClass(int columnIndex):为列中的所有单元格值返回最特定的超类。

  • int getColumnCount():返回模型中的列数。

  • String getColumnName(int columnIndex):返回参数columnIndex指定的列的名称。

  • int getRowCount():返回模型中的行数。

  • Object getValueAt(int rowIndex, int columnIndex):返回列columnIndex和行rowIndex相交的单元格的值。

  • boolean isCellEditable(int rowIndex, int columnIndex):如果可以编辑列rowIndex和行columnIndex相交的单元格,则返回 true。

由于该示例不允许用户直接编辑表的内容,因此尚未实现以下方法:

  • void addTableModelListener(TableModelListener l):将侦听器添加到每次数据模型发生更改时都会通知的列表。

  • void removeTableModelListener(TableModelListener l):从每次数据模型发生更改时通知的列表中删除一个侦听器。

  • void setValueAt(Object aValue, int rowIndex, int columnIndex):将列columnIndex和行rowIndex的交点处的单元格中的值设置为对象aValue

实现 getColumnCount 和 getRowCount

方法getColumnCountgetRowCount分别返回成员变量numcolsnumrows的值:

public int getColumnCount() {
    return numcols;
}

public int getRowCount() {
    return numrows;
}

Implementing getColumnClass

getColumnClass方法返回指定列的数据类型。为简单起见,此方法返回String类,从而将表中的所有数据转换为String对象。 JTable类使用此方法来确定如何在 GUI 应用程序中呈现数据。

public Class getColumnClass(int column) {
    return String.class;
}

Implementing getColumnName

getColumnName方法返回指定列的名称。 JTable类使用此方法标记其每一列。

public String getColumnName(int column) {
    try {
        return this.metadata.getColumnLabel(column + 1);
    } catch (SQLException e) {
        return e.toString();
    }
}

Implementing getColumnAt

getColumnAt方法检索行集coffeesRowSet中指定行和列的值。 JTable类使用此方法填充其表。请注意,SQL 从 1 开始对其行和列编号,但是TableModelinterface从 0 开始。这就是rowIndexcolumnIndex值增加 1 的原因。

public Object getValueAt(int rowIndex, int columnIndex) {

    try {
        this.coffeesRowSet.absolute(rowIndex + 1);
        Object o = this.coffeesRowSet.getObject(columnIndex + 1);
        if (o == null)
            return null;
        else
            return o.toString();
    } catch (SQLException e) {
        return e.toString();
    }
}

Implementing isCellEditable

因为此示例不允许用户直接编辑表的内容(行由另一个窗口控件添加),所以无论rowIndexcolumnIndex的值如何,此方法都将返回false

public boolean isCellEditable(int rowIndex, int columnIndex) {
    return false;
}

Implementing javax.sql.RowSetListener

CoffeesFrame类仅实现RowSetListener rowChangedinterface中的一种方法。当用户向表中添加一行时,将调用此方法。

public void rowChanged(RowSetEvent event) {

    CachedRowSet currentRowSet =
        this.myCoffeesTableModel.coffeesRowSet;

    try {
        currentRowSet.moveToCurrentRow();
        myCoffeesTableModel = new CoffeesTableModel(
            myCoffeesTableModel.getCoffeesRowSet());
        table.setModel(myCoffeesTableModel);

    } catch (SQLException ex) {

        JDBCTutorialUtilities.printSQLException(ex);

        // Display the error in a dialog box.

        JOptionPane.showMessageDialog(
            CoffeesFrame.this,
            new String[] {
                // Display a 2-line message
                ex.getClass().getName() + ": ",
                ex.getMessage()
            }
        );
    }
}

此方法更新 GUI 应用程序中的表。

布置摆幅组件

CoffeesFrame类的构造函数初始化并布置 Swing 组件。以下语句检索COFFEES表的内容,将内容存储在CachedRowSet对象myCachedRowSet中,并初始化JTable Swing 组件:

CachedRowSet myCachedRowSet = getContentsOfCoffeesTable();
myCoffeesTableModel = new CoffeesTableModel(myCachedRowSet);
myCoffeesTableModel.addEventHandlersToRowSet(this);

// Displays the table   
table = new JTable(); 
table.setModel(myCoffeesTableModel);

如前所述,此示例使用RowSet对象(尤其是CachedRowSet对象)代替了ResultSet对象来表示COFFEES表的内容。

方法CoffeesFrame.getContentsOfCoffeesTable检索表COFFEES的内容。

方法CoffeesTableModel.addEventHandlersToRowSet将在CoffeesFrame类中定义的事件处理程序(方法rowChanged)添加到行集成员变量CoffeesTableModel.coffeesRowSet。这使类CoffeesFrame可以将任何事件通知行集coffeesRowSet,特别是当用户单击按钮 向表添加行 ,更新数据库 或放弃更改 时。当行集coffeesRowSet收到这些更改之一的通知时,将调用方法CoffeesFrame.rowChanged

语句table.setModel(myCoffeesTableModel)指定它使用CoffeesTableModel对象myCoffeesTableModel填充JTable Swing 组件table

以下语句指定CoffeesFrame类使用布局GridBagLayout布置其 Swing 组件:

Container contentPane = getContentPane();
contentPane.setComponentOrientation(
    ComponentOrientation.LEFT_TO_RIGHT);
contentPane.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();

有关使用布局GridBagLayout的更多信息,请参见使用 JFC/Swing 创建 GUI中的如何使用 GridBagLayout

请参阅CoffeesFrame.java的源代码,以了解如何将此示例的 Swing 组件添加到布局GridBagLayout

为按钮添加侦听器

以下语句将一个侦听器添加到“向表添加行”按钮:

button_ADD_ROW.addActionListener(
    new ActionListener() {
      
    public void actionPerformed(ActionEvent e) {

        JOptionPane.showMessageDialog(
            CoffeesFrame.this, new String[] {
                "Adding the following row:",
                "Coffee name: [" +
                textField_COF_NAME.getText() +
                "]",
                "Supplier ID: [" +
                textField_SUP_ID.getText() + "]",
                "Price: [" +
                textField_PRICE.getText() + "]",
                "Sales: [" +
                textField_SALES.getText() + "]",
                "Total: [" +
                textField_TOTAL.getText() + "]"
            }
        );

        try {
            myCoffeesTableModel.insertRow(
                textField_COF_NAME.getText(),
                Integer.parseInt(textField_SUP_ID.getText().trim()),
                Float.parseFloat(textField_PRICE.getText().trim()),
                Integer.parseInt(textField_SALES.getText().trim()),
                Integer.parseInt(textField_TOTAL.getText().trim())
            );
        } catch (SQLException sqle) {
            displaySQLExceptionDialog(sqle);
        }
    }
});

当用户单击此按钮时,它将执行以下操作:

  • 创建一个消息对话框,显示要添加到表中的行。

  • 调用方法CoffeesTableModel.insertRow,该方法将行添加到成员变量CoffeesTableModel.coffeesRowSet

如果抛出SQLException,则方法CoffeesFrame.displaySQLExceptionDialog将创建一个消息对话框,显示SQLException的内容。

以下语句将监听器添加到按钮“更新数据库”:

button_UPDATE_DATABASE.addActionListener(
    new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            try {
                myCoffeesTableModel.coffeesRowSet.acceptChanges();
                msgline.setText("Updated database");
            } catch (SQLException sqle) {
                displaySQLExceptionDialog(sqle);
                // Now revert back changes
                try {
                    createNewTableModel();
                    msgline.setText("Discarded changes");
                } catch (SQLException sqle2) {
                    displaySQLExceptionDialog(sqle2);
                }
            }
        }
    }
);

当用户单击此按钮时,表COFFEES将更新为行集myCoffeesTableModel.coffeesRowSet的内容。

以下语句将一个侦听器添加到“放弃更改”按钮:

button_DISCARD_CHANGES.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        try {
            createNewTableModel();
        } catch (SQLException sqle) {
            displaySQLExceptionDialog(sqle);
        }
    }
});

当用户单击此按钮时,将调用方法CoffeesFrame.createNewTableModel,该方法用COFFEES表的内容重新填充JTable组件。