Java Swing 编写数据库增删改查 GUI 程序

此程序是我们数据库课程的一个作业,初衷是帮助大家理解数据库的增删改查操作,不过涉及的数据库操作非常基础,只要之前接触过数据库,对这些语法应该都基本掌握了。

在写这一程序的过程中,图形界面的编写倒是占了大部分时间。写下这篇文章,更多的是记录使用 Swing 来编写这一图形界面程序所遇到的问题和解决的方法,当然,如果你还不熟悉数据库的基本操作的话,也可以通过这个程序来进一步熟悉。

接下来,我们开始分析这个程序的编写过程。

界面布局

编写图形界面程序最先遇到的问题就是窗口布局,现在有很多 GUI 自动构建工具可以让我们直接拖动控件来创建用户界面,这里我通过手动编写代码的方式来创建用户界面,一方面是帮助理解布局管理机制,另一方面通过代码来布局会有更高的可控性。

先展示一下最终的用户界面:

这里使用到了 JFrame 默认的 BorderLayout 布局和 BoxLayout 布局,BorderLayout 将整个容器划分成东南西北中五个方位来放置控件,放置控件时需要指定控件放置的方位;BoxLayout 可以指定在容器中是否对控件进行水平或者垂直放置。

具体布局如下:

最外层(红色方框)和第二层(蓝色方框)使用的是 BorderLayout,红色的 WEST、EAST 和 SOUTH 区域放置的是通过 Box.createRigidArea 创建的空白占位组件,使界面更加美观。

问题:BorderLayout 布局中,CENTER 区域会占据尽可能的扩展开,而其它区域默认是尽可能的小,因此 NORTH 区域默认高度不足以显示多行控件。

解决方法:将这一区域的组件全部放入 JPanel 中,然后调用该 JPanel 的 setPreferredSize(new Dimension(0, height)) 方法设置它的大小,这样就能使 NORTH 区域达到指定的高度。

最里层的黄色方框也是一个 JPanel,包含了 Box.createRigidArea 创建的空白占位组件、JTable.getTableHeader() 得到的表头、JTable 三个组件,采用的是 BoxLayout 的垂直布局。

创建自定义组件

如果你稍作观察,会发现这么多查询的条件,需要创建许多对应的复选框、标签和文本框,零散又不便于管理。

每个查询条件所对应的这三个控件,无论是展示还是数据的获取,其实都可以放在一起,因此我们创建一个自定义的组件,继承自 JPanel,然后将这三个控件添加进来,并对外提供两个方法:复选框是否被选中(isSelected)、获取文本框字符串(getText)。

这样一来,针对每一个查询条件我们只需要创建一个这类组件就可以了,也更加便于管理。

JTable 数据绑定

数据的展示是通过 JTable 来完成的,这也是该程序中处理相对繁琐的地方。

有关 JTable 的详细用法这里不细讲,只对用到的一些功能和设置进行说明。

使用 JTable 时需要一个数据模型,如果没有提供,会使用默认的数据模型。数据模型的作用是,当用户通过用户界面修改表格时,会自动同步到该模型上,同样,当我们修改模型时,也可以更新到表格上,这就实现了数据的一致性,处理起来也更加方便。

通常情况下,我们需要继承 AbstractTableModel 来实现数据模型,再绑定到 JTable 上。但在这个程序中,使用的是最简单的数据绑定方式,即用两层嵌套的 Vector 来存储表格数据,同样,也是用 Vector 来存储表头。当我们修改 Vector 中的数据时,只需调用 JTable 的 validate() 和 updateUI() 方法,就能自动将 Vector 中的最新数据展示出来,而当我们编辑表格中的数据时,Vector 中的数据也会同步更新。

查询:当用户点击查询按钮时,根据查询条件构造出 SQL 语句,显示在中间的文本框中,将查询的结果展示在 JTable 中。

修改:用户可编辑某个单元格,编辑完毕后,保持该单元格的选中状态,然后点击修改按钮,就可以将数据库中对应记录的对应字段更新为当前单元格的值。

添加:添加新记录时,将每个字段输入到对应的查询框中,复选框是否勾选没有关系,点击添加按钮,就会将该记录添加到数据库中,同时更新 JTable。

删除:用户可选中某一行,点击删除按钮,便可从数据库中删除该条记录,同时更新 JTable。

程序代码

这里使用 JDBC 来操作 MySQL 数据库,具体代码如下:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;

import javax.swing.*;

/**
 * @author: Wray Zheng
 * @date: 2018-03-25
 * @description: 可对特定数据库表进行增删改查的图形界面程序
 */
public class Main extends JFrame {
    private final int COLUMN = 7;
    private final List<String> TITLES = Arrays.asList(
            "Sid", "Sname", "Ssex", "Sage", "Sclass", "Sdept", "Saddr");
    private Vector<Vector<String>> dataModel = new Vector<Vector<String>>();
    private QueryItem id = new QueryItem("学号:", 10);
    private QueryItem name = new QueryItem("姓名:", 10);
    private QueryItem sex = new QueryItem("性别:", 5);
    private QueryItem2 age = new QueryItem2("年龄自:", "到", 5);
    private QueryItem class_ = new QueryItem("班级:", 5);
    private QueryItem dept = new QueryItem("系别:", 5);
    private QueryItem addr = new QueryItem("地址:", 10);
    private JButton queryBtn = new JButton("查询");
    private JButton saveBtn = new JButton("修改");
    private JButton insertBtn = new JButton("添加");
    private JButton deleteBtn = new JButton("删除");
    private JTextArea textarea = new JTextArea(5, 5);
    private MyTable table;
    private Connection conn;

    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Main frame = new Main("Database Query");
        frame.connectToDB();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setMinimumSize(new Dimension(750, 500));
        frame.setVisible(true);
        frame.setResizable(false);
    }

    //构造函数,负责创建用户界面
    public Main(String title) {
        super(title);

        Vector<String> titles = new Vector<String>(TITLES);
        table = new MyTable(dataModel, titles);
        table.getColumnModel().getColumn(2).setPreferredWidth(30);
        table.getColumnModel().getColumn(3).setPreferredWidth(30);
        table.getColumnModel().getColumn(5).setPreferredWidth(30);
        table.getColumnModel().getColumn(6).setPreferredWidth(150);

        JPanel controlPanel = new JPanel();
        controlPanel.setLayout(new FlowLayout());
        controlPanel.add(id);
        controlPanel.add(name);
        controlPanel.add(sex);
        controlPanel.add(age);
        controlPanel.add(class_);
        controlPanel.add(dept);
        controlPanel.add(addr);
        controlPanel.add(queryBtn);
        controlPanel.add(saveBtn);
        controlPanel.add(insertBtn);
        controlPanel.add(deleteBtn);
        controlPanel.setPreferredSize(new Dimension(0, 130));

        JPanel tablePanel = new JPanel();
        tablePanel.setLayout(new BoxLayout(tablePanel, BoxLayout.Y_AXIS));
        tablePanel.add(Box.createRigidArea(new Dimension(0, 20)));
        tablePanel.add(table.getTableHeader());
        tablePanel.add(new JScrollPane(table));

        JPanel container = new JPanel();
        container.setLayout(new BorderLayout());
        container.add(textarea, BorderLayout.NORTH);
        container.add(tablePanel, BorderLayout.CENTER);

        this.add(controlPanel, BorderLayout.NORTH);
        this.add(container, BorderLayout.CENTER);
        this.add(Box.createRigidArea(new Dimension(20, 0)), BorderLayout.WEST);
        this.add(Box.createRigidArea(new Dimension(20, 0)), BorderLayout.EAST);
        this.add(Box.createRigidArea(new Dimension(0, 20)), BorderLayout.SOUTH);

        setActionListener();
    }

    //程序启动时,需调用该方法连接到数据库
    //之所以不放在构造函数中,是因为这些操作可能抛出异常,需要单独处理
    public void connectToDB() throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.jdbc.Driver");
        final String URL = "jdbc:mysql://localhost:3306/mydb?characterEncoding=UTF-8";
        conn = DriverManager.getConnection(URL, "root", "helloworld");
    }

    private void setActionListener() {
        //根据指定条件,列出数据库中满足条件的记录
        queryBtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                ArrayList<String> conditions = new ArrayList<String>();
                if (id.isSelected()) conditions.add("(Sid = '" + id.getText() + "')");
                if (name.isSelected()) conditions.add("(Sname like '" + name.getText() + "')");
                if (sex.isSelected()) conditions.add("(Ssex = '" + sex.getText() + "')");
                if (age.isSelected()) conditions.add("(Sage >= " + age.getText() + " AND " + "Sage <= " + age.getText2() + ")");
                if (class_.isSelected()) conditions.add("(Sclass = '" + class_.getText() + "')");
                if (dept.isSelected()) conditions.add("(Sdept = '" + dept.getText() + "')");
                if (addr.isSelected()) conditions.add("(Saddr like '" + addr.getText() + "')");

                StringBuilder sb = new StringBuilder();
                sb.append("select * from student");
                int length = conditions.size();
                if (length != 0) sb.append(" where ");
                for (int i = 0; i < length; i++) {
                    sb.append(conditions.get(i));
                    if (i != length - 1) sb.append(" AND ");
                }
                sb.append(";");
                String queryString = sb.toString();
                textarea.setText(queryString);

                dataModel.clear();
                Statement stmt;
                try {
                    stmt = conn.createStatement();
                    ResultSet rs = stmt.executeQuery(queryString);
                    Vector<String> record;
                    while (rs.next()) {
                        record = new Vector<String>();
                        for (int i = 0; i < COLUMN; i++) {
                            record.add(rs.getString(i + 1));
                        }
                        dataModel.add(record);
                    }
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }

                //更新表格
                table.validate();
                table.updateUI();
            }

        });

        //根据用户当前选中的单元格,修改数据库中对应记录的对应字段
        saveBtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                int row = table.getSelectedRow();
                int column = table.getSelectedColumn();
                if (row == -1 || column == 0) return;

                String val = dataModel.get(row).get(column);
                String sid = dataModel.get(row).get(0);
                String sql = "update student set " + TITLES.get(column) + " = ? where Sid = ?;";

                //在文本框显示 SQL 命令
                String cmd = "update student set " + TITLES.get(column) + " = ";
                cmd += (TITLES.get(column) == "Sage") ? val : "'" + val + "'";
                cmd += " where Sid = '" + sid + "';";
                textarea.setText(cmd);

                PreparedStatement ps;
                try {
                    ps = conn.prepareStatement(sql);
                    if (TITLES.get(column) == "Sage") ps.setInt(1, Integer.valueOf(val));
                    else ps.setString(1, val);
                    ps.setString(2, sid);
                    ps.executeUpdate();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
        });

        //往数据库中插入一条新的记录
        insertBtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                String sql = "insert into student values (?,?,?,?,?,?,?);";
                String sid = id.getText();
                String sname = name.getText();
                String ssex = sex.getText();
                String sage = age.getText();
                String sclass = class_.getText();
                String sdept = dept.getText();
                String saddr = addr.getText();

                //在文本框显示 SQL 命令
                String cmd = "insert into student values ('" + sid + "', '" + sname + "', '" +
                ssex + "', " + sage + ", '" + sclass + "', '" + sdept + "', '" + saddr + "');"; 
                textarea.setText(cmd);

                PreparedStatement ps;
                try {
                    ps = conn.prepareStatement(sql);
                    ps.setString(1, sid);
                    ps.setString(2, sname);
                    ps.setString(3, ssex);
                    ps.setInt(4, Integer.valueOf(sage));
                    ps.setString(5, sclass);
                    ps.setString(6, sdept);
                    ps.setString(7, saddr);
                    ps.executeUpdate();
                    dataModel.add(new Vector<String>(Arrays.asList(
                            sid, sname, ssex, sage, sclass, sdept, saddr)));

                    //更新表格
                    table.validate();
                    table.updateUI();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }

        });

        //将用户当前选中的记录从数据库中删除
        deleteBtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                int row = table.getSelectedRow();
                String sid = dataModel.get(row).get(0);
                String sql = "delete from student where Sid = '" + sid + "';";

                //在文本框显示 SQL 命令
                textarea.setText(sql);

                Statement stmt;
                try {
                    stmt = conn.createStatement();
                    if (stmt.executeUpdate(sql) == 0) return;
                    dataModel.remove(row);

                    //更新表格
                    table.validate();
                    table.updateUI();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }

        });
    }

}

/* 查询项目
 * 将复选框、标签、文本框组合成一个组件
 * 对外提供获取文本和选中状态的两个方法
 */
class QueryItem extends JPanel {
    private JCheckBox checkbox;
    private JLabel label;
    private JTextField textfield;

    public QueryItem(String labelText, int textWidth) {
        checkbox = new JCheckBox();
        label = new JLabel(labelText);
        textfield = new JTextField(textWidth);
        this.add(checkbox);
        this.add(label);
        this.add(textfield);
    }

    public boolean isSelected() {
        return checkbox.isSelected();
    }

    public String getText() {
        return textfield.getText();
    }
}

/* 同样是查询项目
 * 这是用于查询年龄范围的组件,包含了两个文本框
 * 因此特殊处理,并增加了获取第二个文本框内容的方法
 */
class QueryItem2 extends QueryItem {
    private JLabel label2;
    private JTextField textfield2;

    public QueryItem2(String labelText, String labelText2, int textWidth) {
        super(labelText, textWidth);
        label2 = new JLabel(labelText2);
        textfield2 = new JTextField(textWidth);
        this.add(label2);
        this.add(textfield2);
    }

    public String getText2() {
        return textfield2.getText();
    }
}

/* 表格组件
 * 重载了 JTable 的 isCellEditable 方法
 * 目的是防止编辑 Sid 字段,禁止修改主键
 */
class MyTable extends JTable {
    public MyTable(Vector data, Vector title) {
        super(data, title);
    }

    @Override
    public boolean isCellEditable(int row, int column) {
        if (column == 0) return false;
        else return true;
    }
}

发表评论

邮箱地址不会被公开。 必填项已用*标注