此程序是我们数据库课程的一个作业,初衷是帮助大家理解数据库的增删改查操作,不过涉及的数据库操作非常基础,只要之前接触过数据库,对这些语法应该都基本掌握了。
在写这一程序的过程中,图形界面的编写倒是占了大部分时间。写下这篇文章,更多的是记录使用 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;
}
}
作者:Wray Zheng
原文:http://www.codebelief.com/article/2018/03/java-swing-code-a-mysql-crud-gui-program/