多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

期末数据库课设:JDBC+JavaSwing超市管理系统实战指南

期末数据库课设:JDBC+JavaSwing超市管理系统实战指南 简介这份资源是面向高校计算机相关专业学生的期末数据库课程设计参考方案基于Java、MySQL、JDBC与JavaSwing实现一套完整的超市管理系统适合正在准备课程设计或想练习桌面端数据库应用开发的学习者。压缩包共24个文件约1.1MB其中17个java源文件承载商品、库存、销售等核心业务逻辑1个sql脚本用于建库建表2个jar包提供FlatLaf界面美化支持另有pom.xml与mvn-settings.xml等Maven配置及README说明整体结构清晰、便于导入IDE运行调试。项目采用JDK12与MySQL8.0环境编码为GBK并在各组件构造函数中通过UIManager设置系统外观使界面风格更统一美观。目前已有217人学习下载读者可借此掌握JDBC连接数据库、Swing事件处理与增删改查的完整实现思路并参考其分层结构与SQL设计完成自己的课程设计。1. 期末数据库课设选超市管理系统为什么JDBCJavaSwing这套组合最不容易翻车每年一到期末数据库课程设计就成了不少人的心病。选题太简单怕被导师打回选题太复杂又怕自己写不完。在众多题目里超市管理系统算是出现频率最高的一个而基于JavaMySQLJDBCJavaSwing的实现路线几乎是零基础也能跑通、导师也挑不出大毛病的稳妥选择。原因很直接这套技术栈把数据库操作、界面展示、业务逻辑三层分得清清楚楚JDBC负责和MySQL对话JavaSwing负责把结果画到窗口上中间的业务代码只做数据搬运和校验每一层都能单独调试。这篇文章面向的是正在做数据库课设、需要交出一份能演示、能答辩、代码能讲清楚的同学。我会把超市管理系统的表结构设计、JDBC连接封装、Swing界面搭建、事务处理、以及答辩时最容易被追问的几个点按实际动手顺序拆开讲。你不需要有很深的Java基础但至少要能看懂类、方法、循环和条件判断。读完之后你应该能独立搭出一个带登录、商品管理、库存出入库、销售收银和简单报表的系统并且知道每一行关键代码为什么这么写。2. 先把数据库表结构定下来五张表撑起整个超市业务2.1 从业务动作反推表结构而不是先画ER图很多人一上来就打开画图工具画ER图画了半天发现字段对不上业务。我的习惯是先把系统里会发生哪些动作列出来管理员登录、商品上架、商品分类、库存入库、库存出库、收银结算、查看销售记录。每个动作对应一到两张表列完动作表自然就出来了。超市管理系统的核心表不需要多五张足够用户表、商品分类表、商品表、库存记录表、销售订单表。用户表存登录信息商品分类表给商品归类商品表存商品基本信息和当前库存库存记录表记录每一次入库和出库的流水销售订单表记录每一笔收银。这样设计的好处是商品表里的库存字段是实时值库存记录表是历史流水两者对账时能互相验证。2.2 建表SQL与字段类型选择下面这段SQL可以直接在MySQL里执行。注意字符集用utf8mb4不然商品名称里出现特殊符号会乱码。主键统一用int自增外键约束先不加课设阶段用代码保证一致性比用数据库约束更灵活也更容易在答辩时解释清楚。-- 创建数据库字符集必须指定否则中文商品名会乱码 CREATE DATABASE supermarket_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE supermarket_db; -- 用户表区分管理员和普通收银员 CREATE TABLE t_user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role VARCHAR(20) DEFAULT cashier, -- admin 或 cashier create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 商品分类表 CREATE TABLE t_category ( category_id INT PRIMARY KEY AUTO_INCREMENT, category_name VARCHAR(50) NOT NULL ); -- 商品表stock 字段是当前库存实时值 CREATE TABLE t_product ( product_id INT PRIMARY KEY AUTO_INCREMENT, product_name VARCHAR(100) NOT NULL, category_id INT, price DECIMAL(10,2) NOT NULL, stock INT DEFAULT 0, unit VARCHAR(10) DEFAULT 件 ); -- 库存流水表每次入库/出库都插一条 CREATE TABLE t_stock_record ( record_id INT PRIMARY KEY AUTO_INCREMENT, product_id INT NOT NULL, change_type VARCHAR(10) NOT NULL, -- in 或 out change_qty INT NOT NULL, record_time DATETIME DEFAULT CURRENT_TIMESTAMP, operator VARCHAR(50) ); -- 销售订单表 CREATE TABLE t_order ( order_id INT PRIMARY KEY AUTO_INCREMENT, product_id INT NOT NULL, quantity INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, order_time DATETIME DEFAULT CURRENT_TIMESTAMP, cashier VARCHAR(50) );字段类型上有几个容易踩坑的地方。价格用DECIMAL而不是FLOAT因为浮点数算钱会出现0.30000000000000004这种结果答辩时被问到很难解释。库存用INT不要用VARCHAR否则后面做库存扣减时还要转类型。时间字段统一用DATETIME默认值CURRENT_TIMESTAMP插入时不用手动传时间。2.3 初始化数据与自增主键的注意事项建完表之后先插几条测试数据方便后面调界面。注意自增主键在删除数据后不会重置如果你反复删插测试ID会越来越大这是正常现象不用去TRUNCATE课设演示不影响。INSERT INTO t_user (username, password, role) VALUES (admin, 123456, admin); INSERT INTO t_user (username, password, role) VALUES (cashier01, 123456, cashier); INSERT INTO t_category (category_name) VALUES (饮料), (零食), (日用品); INSERT INTO t_product (product_name, category_id, price, stock, unit) VALUES (矿泉水, 1, 2.00, 100, 瓶), (薯片, 2, 6.50, 50, 袋), (洗发水, 3, 35.00, 30, 瓶);提示课设数据库名和表名尽量用英文字段名也用英文答辩时导师看到中文表名会追问编码问题徒增麻烦。3. JDBC连接封装一个工具类解决所有数据库操作3.1 为什么必须封装JDBC工具类如果每个Swing按钮的监听器里都写一遍Class.forName、DriverManager.getConnection、PreparedStatement、ResultSet代码会重复到无法维护而且连接忘记关闭会导致MySQL连接数爆掉演示到一半程序卡死。我一般会写一个DBUtil类把获取连接、关闭资源、执行增删改、执行查询这四个动作封装成静态方法。这样业务代码里只需要调DBUtil.executeUpdate(sql, params)和DBUtil.executeQuery(sql, params)既干净又不容易漏关连接。3.2 DBUtil完整代码与参数说明import java.sql.*; import java.util.ArrayList; import java.util.List; public class DBUtil { // 数据库连接参数课设阶段直接写死方便演示 private static final String URL jdbc:mysql://localhost:3306/supermarket_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 你的密码; // 加载驱动静态代码块只执行一次 static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } // 获取连接 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } // 通用增删改返回受影响行数 public static int executeUpdate(String sql, Object... params) { try (Connection conn getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { for (int i 0; i params.length; i) { ps.setObject(i 1, params[i]); } return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return 0; } } // 通用查询返回ListObject[]每行一个数组 public static ListObject[] executeQuery(String sql, Object... params) { ListObject[] list new ArrayList(); try (Connection conn getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { for (int i 0; i params.length; i) { ps.setObject(i 1, params[i]); } try (ResultSet rs ps.executeQuery()) { ResultSetMetaData meta rs.getMetaData(); int colCount meta.getColumnCount(); while (rs.next()) { Object[] row new Object[colCount]; for (int i 0; i colCount; i) { row[i] rs.getObject(i 1); } list.add(row); } } } catch (SQLException e) { e.printStackTrace(); } return list; } }URL里的serverTimezoneAsia/Shanghai必须加否则MySQL 8.x会报时区错误。useSSLfalse在本地演示时加上避免SSL握手警告。characterEncodingutf8保证中文不乱码。executeUpdate和executeQuery都用了try-with-resources连接和语句会自动关闭这是Java 7之后的标准写法比手动finally里close更安全。3.3 事务处理收银扣库存必须一起成功收银这个动作涉及两张表往t_order插一条销售记录同时更新t_product的stock字段。如果插订单成功但扣库存失败库存就对不上。这时候必须用事务。DBUtil里再加一个方法专门处理需要事务的操作。public static boolean executeTransaction(String sql1, Object[] params1, String sql2, Object[] params2) { Connection conn null; try { conn getConnection(); conn.setAutoCommit(false); // 关闭自动提交开启事务 try (PreparedStatement ps1 conn.prepareStatement(sql1); PreparedStatement ps2 conn.prepareStatement(sql2)) { for (int i 0; i params1.length; i) ps1.setObject(i 1, params1[i]); for (int i 0; i params2.length; i) ps2.setObject(i 1, params2[i]); ps1.executeUpdate(); ps2.executeUpdate(); conn.commit(); // 两条都成功才提交 return true; } } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }调用时传入两条SQL和对应参数比如第一条是INSERT INTO t_order...第二条是UPDATE t_product SET stock stock - ? WHERE product_id ?。任何一条抛异常rollback会把两条都撤销保证数据一致。答辩时如果导师问“你怎么保证收银不会扣错库存”把这段代码指给他看就行。4. JavaSwing界面搭建从登录窗口到收银主界面4.1 登录窗口最小可运行Swing程序Swing的布局管理器是新手最大的拦路虎。我建议登录窗口用绝对布局setLayout(null)因为登录界面元素少绝对定位最直观不用去理解BorderLayout和GridBagLayout的约束规则。主界面再用BorderLayout加JPanel组合这样难度是递进的。import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.util.List; public class LoginFrame extends JFrame { private JTextField userField; private JPasswordField passField; public LoginFrame() { setTitle(超市管理系统 - 登录); setSize(350, 220); setLocationRelativeTo(null); // 居中显示 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(null); JLabel userLabel new JLabel(用户名); userLabel.setBounds(50, 40, 80, 25); add(userLabel); userField new JTextField(); userField.setBounds(120, 40, 150, 25); add(userField); JLabel passLabel new JLabel(密 码); passLabel.setBounds(50, 80, 80, 25); add(passLabel); passField new JPasswordField(); passField.setBounds(120, 80, 150, 25); add(passField); JButton loginBtn new JButton(登录); loginBtn.setBounds(80, 130, 80, 30); add(loginBtn); JButton cancelBtn new JButton(取消); cancelBtn.setBounds(190, 130, 80, 30); add(cancelBtn); loginBtn.addActionListener(this::doLogin); cancelBtn.addActionListener(e - System.exit(0)); } private void doLogin(ActionEvent e) { String username userField.getText().trim(); String password new String(passField.getPassword()).trim(); if (username.isEmpty() || password.isEmpty()) { JOptionPane.showMessageDialog(this, 用户名和密码不能为空); return; } String sql SELECT user_id, role FROM t_user WHERE username ? AND password ?; ListObject[] result DBUtil.executeQuery(sql, username, password); if (result.isEmpty()) { JOptionPane.showMessageDialog(this, 用户名或密码错误); } else { String role result.get(0)[1].toString(); new MainFrame(username, role).setVisible(true); dispose(); // 关闭登录窗口 } } public static void main(String[] args) { SwingUtilities.invokeLater(() - new LoginFrame().setVisible(true)); } }setLocationRelativeTo(null)让窗口在屏幕居中不加的话窗口会跑到左上角。SwingUtilities.invokeLater是Swing线程规范所有界面创建都放在事件调度线程里避免偶发的界面卡死。登录查询用PreparedStatement参数化不要用字符串拼接否则输入单引号会SQL报错这也是答辩常问的安全点。4.2 主界面布局菜单栏加选项卡主界面用JFrame加JMenuBar做菜单中间用JTabbedPane分页每个选项卡是一个JPanel。商品管理、库存管理、收银、销售记录各占一个选项卡。这样代码结构清晰每个面板单独写一个类互不干扰。import javax.swing.*; import java.awt.*; public class MainFrame extends JFrame { private String username; private String role; public MainFrame(String username, String role) { this.username username; this.role role; setTitle(超市管理系统 - 当前用户 username); setSize(900, 600); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JMenuBar menuBar new JMenuBar(); JMenu sysMenu new JMenu(系统); JMenuItem logoutItem new JMenuItem(注销); logoutItem.addActionListener(e - { new LoginFrame().setVisible(true); dispose(); }); sysMenu.add(logoutItem); menuBar.add(sysMenu); setJMenuBar(menuBar); JTabbedPane tabbedPane new JTabbedPane(); tabbedPane.addTab(商品管理, new ProductPanel()); tabbedPane.addTab(库存管理, new StockPanel(username)); tabbedPane.addTab(收银, new CashierPanel(username)); tabbedPane.addTab(销售记录, new OrderPanel()); add(tabbedPane, BorderLayout.CENTER); } }每个Panel类里用JTable展示数据JTable必须包在JScrollPane里否则表头不显示。表格数据用DefaultTableModel查询到ListObject[]之后循环addRow。按钮监听器里调DBUtil执行SQL然后重新查询刷新表格。这套模式在四个面板里重复用写熟一个后面就是复制改字段。4.3 收银面板事务与界面刷新的配合收银面板是最能体现业务逻辑的地方。用户选择商品、输入数量、点结算程序先查库存够不够够的话执行事务插订单扣库存然后弹窗提示成功并刷新商品表格。库存不够就提示“库存不足”不执行任何数据库操作。private void doCheckout() { int selectedRow productTable.getSelectedRow(); if (selectedRow -1) { JOptionPane.showMessageDialog(this, 请先选择商品); return; } int productId Integer.parseInt(productTable.getValueAt(selectedRow, 0).toString()); int stock Integer.parseInt(productTable.getValueAt(selectedRow, 4).toString()); int qty; try { qty Integer.parseInt(qtyField.getText().trim()); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(this, 数量必须是数字); return; } if (qty 0 || qty stock) { JOptionPane.showMessageDialog(this, 数量不合法或库存不足); return; } double price Double.parseDouble(productTable.getValueAt(selectedRow, 3).toString()); double total price * qty; String sql1 INSERT INTO t_order (product_id, quantity, total_price, cashier) VALUES (?, ?, ?, ?); String sql2 UPDATE t_product SET stock stock - ? WHERE product_id ?; boolean ok DBUtil.executeTransaction(sql1, new Object[]{productId, qty, total, username}, sql2, new Object[]{qty, productId}); if (ok) { JOptionPane.showMessageDialog(this, 收银成功金额 total); refreshTable(); // 重新查询商品表库存显示会更新 } else { JOptionPane.showMessageDialog(this, 收银失败请检查数据库); } }refreshTable方法里重新执行SELECT * FROM t_product然后tableModel.setRowCount(0)清空再addRow。注意setRowCount(0)必须在addRow之前否则数据会重复叠加。这个细节很多人第一次写会漏导致表格越刷新行越多。5. 避坑与排查课设演示前必须过的五道关5.1 中文乱码从数据库到界面全链路排查现象是商品名称在JTable里显示成问号或方块。原因可能在三处数据库字符集不是utf8mb4、JDBC URL没加characterEncodingutf8、Swing组件字体不支持中文。解决顺序是先确认数据库和表都是utf8mb4再检查URL参数最后在main方法开头设置UIManager.setLookAndFeel和默认字体。我一般会在程序启动时加一句UIManager.put(Label.font, new Font(微软雅黑, Font.PLAIN, 14))把全局字体定下来。5.2 连接失败MySQL 8.x的驱动和时区问题现象是启动就报Communications link failure或者Unknown database。先确认MySQL服务在运行再确认数据库名拼写。MySQL 8.x必须用com.mysql.cj.jdbc.Driver用旧的com.mysql.jdbc.Driver会警告。时区问题在URL里加serverTimezoneAsia/Shanghai就能解决。如果报Access denied检查DBUtil里的用户名密码和MySQL实际账号是否一致课设阶段用root最省事。5.3 表格不刷新查询结果和界面不同步现象是数据库里数据变了但JTable还显示旧数据。原因是Swing的表格模型没有重新加载。解决方法是每次增删改之后重新执行查询然后tableModel.setRowCount(0)再循环addRow。不要试图直接修改表格里的单元格来同步数据库那样数据库和界面会脱节答辩时说不清楚。5.4 事务不生效自动提交没关现象是收银时订单插入了但库存没扣或者反过来。原因是Connection默认autoCommit为true每条SQL独立提交。必须在事务方法里先conn.setAutoCommit(false)执行完两条SQL再commit异常时rollback。另外注意事务方法里不能用DBUtil.executeUpdate因为那个方法每次都会新建连接事务必须用同一个Connection。5.5 打包运行报错缺少MySQL驱动jar现象是在IDE里跑得好好的导出成jar双击就报ClassNotFoundException。原因是MySQL驱动jar没有一起打包。解决方法是导出时选择Runnable JAR把mysql-connector-java.jar一起打进去或者用IDEA的Artifacts配置把驱动加到lib目录。课设答辩如果要求现场跑提前在答辩机器上装好MySQL并导入SQL文件比现场打包更稳。6. 答辩前把这三个进阶点讲清楚导师基本不会为难你第一个进阶点是连接池。课设阶段用DriverManager每次新建连接没问题但你可以主动提一句实际项目里会用连接池比如Druid或HikariCP复用连接避免频繁创建销毁的开销。不需要真去集成但要知道这个概念导师问“你的连接管理有什么优化空间”时能答上来。第二个进阶点是分页查询。销售记录多了之后SELECT * FROM t_order全查出来会卡。MySQL用LIMIT offset, size做分页Swing里配合上一页下一页按钮。你可以只实现一个简单的LIMIT 0, 20然后说明完整分页需要传页码参数。这个点能体现你有考虑数据量增长的情况。第三个进阶点是密码存储。课设里密码明文存t_user表演示没问题但你可以提一句实际应该存MD5或BCrypt哈希值登录时把输入的密码哈希后再比对。不需要真改代码但答辩时主动说出来导师会觉得你考虑过安全。最后一个我自己的习惯答辩前把整个系统从头到尾操作三遍每遍记录哪里卡顿、哪里报错、哪里数据不对。第一遍查功能第二遍查数据一致性第三遍查异常输入比如数量输负数、商品名输空。三遍下来导师现场让你演示任何功能你都能顺手点出来。数据库课设不难难的是细节都想到。希望帮到你。本文还有配套的精品资源点击获取
返回列表