# 将 JDBC 与 GUI API 结合使用

> 原文： [https://docs.oracle.com/javase/tutorial/jdbc/basics/jdbcswing.html](https://docs.oracle.com/javase/tutorial/jdbc/basics/jdbcswing.html)

示例 `[CoffeesFrame.java](gettingstarted.html)`演示了如何将 JDBC 与 GUI API 集成，特别是 Swing API。它在表中显示`COFFEES`数据库表的内容，并包含允许您向表中添加行的字段和按钮。以下是此示例的屏幕截图：

![Screenshot of Sample CoffeeFrames.java](img/0aa9481f190e558e0681a592a736f94b.jpg)

该示例包含五个文本字段，这些字段对应于`COFFEES`表中的每个列。它还包含三个按钮：

*   **向表**添加行：根据在文本字段中输入的数据向样本表中添加一行。
*   **更新数据库**：根据样本表中的数据更新表`COFFEES`。
*   **放弃更改**：检索`COFFEES`表的内容，替换样本表中的现有数据。

此示例（需要`CoffeesTableModel`）演示了将 JDBC 与 Swing API 集成的以下一般步骤：

1.  [实现`TableModel`接口](#implementing_tablemodel)
2.  [实现`RowSetListener`接口](#implemeting_rowsetlistener)
3.  [摆出 Swing 组件](#laying_out_swing)
4.  [为示例中的按钮添加侦听器](#adding_listeners)

`TableModel`接口使 Java Swing 应用程序能够管理`JTable`对象中的数据。样本 [`CoffeesTableModel.java`](gettingstarted.html) 实现此接口。它指定`JTable`对象应如何从`RowSet`对象检索数据并将其显示在表中。

**注意**：虽然此示例显示 Swing 应用程序中`COFFEES`表的内容，但类`CoffeesTableModel`应适用于任何 SQL 表，前提是其数据可以用`String`对象表示。 （但是，必须为其他 SQL 表修改允许用户向`COFFEES`添加行的字段，这些字段在`CoffeesFrame`类中指定）。

在实现接口`TableModel`的方法之前，类`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`示例从`TableModel`接口实现以下方法：

*   `Class&lt;?&gt; 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

方法`getColumnCount`和`getRowCount`分别返回成员变量`numcols`和`numrows`的值：

```
public int getColumnCount() {
    return numcols;
}

public int getRowCount() {
    return numrows;
}

```

### 实现 getColumnClass

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

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

```

### 实现 getColumnName

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

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

```

### 实现 getColumnAt

`getColumnAt`方法检索行集`coffeesRowSet`中指定行和列的值。 `JTable`类使用此方法填充其表。请注意，SQL 开始将其行和列编号为 1，但`TableModel`接口从 0 开始;这就是`rowIndex`和`columnIndex`值增加 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();
    }
}

```

### 实现 isCellEditable

由于此示例不允许用户直接编辑表的内容（行由另一个窗口控件添加），因此无论`rowIndex`和`columnIndex`的值如何，此方法都会返回`false`：

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

```

类`CoffeesFrame`仅从接口`RowSetListener`，`rowChanged`实现一种方法。当用户向表中添加行时，将调用此方法。

```
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](../../uiswing/index.html) 创建 GUI 中的 GridBagLayout 。

请参阅 `[CoffeesFrame.java](gettingstarted.html)`的源代码，了解如何将此示例的 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);
                }
            }
        }
    }
);

```

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

以下语句为按钮添加了一个监听器 **Discard changes** ：

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

```

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