☰
基于Java+Swing+MySQL的餐厅点餐管理系统完整开发指南
2026/10/7 14:32:57 网站建设 项目流程

简介:这是一套基于Java Swing与MySQL的餐厅点餐管理系统,适合Java课程设计、毕业设计或入门桌面应用开发的读者参考。系统覆盖管理员与顾客两类角色,包括注册登录、套餐新增与管理、订单处理、顾客点餐、订单查看、个人信息修改和密码修改等完整业务流程,功能设计贴近真实餐饮管理场景,运行环境为Eclipse/IDEA搭配MySQL 8.0以上。资源包共170个文件,含18个java源码文件、66个class编译文件、3个jar依赖库和1个sql数据库脚本,可通过sql脚本快速初始化数据库,另附62张png与16张jpg界面截图,便于对照界面效果;整包体积仅2.19MB,结构简洁,便于快速部署。目前已有1532人学习/下载。通过这套资源,读者能获取可直接运行的完整项目、数据库建表脚本和可视化界面素材,既可快速启动演示,也能基于源码进行二次开发,加深对Swing与MySQL联动开发的理解。

1. 基于Java+Swing+MySQL的餐厅点餐管理系统:为什么桌面端项目仍然值得亲手做一遍

提起Java桌面应用,很多人的第一反应是“都什么年代了还在用Swing”。但恰恰是“基于Java+Swing+MySQL的点餐管理系统”这类项目,构成了绝大多数计算机专业学生第一次完整接触Java图形界面、JDBC数据库编程、分层架构的真实战场。它没有Spring Boot那种框架托底的舒适区,所有界面跳转、事件监听、数据库连接都得自己一点点写出来。这个过程虽然显得原始,但你会在里面学会最本质的Java程序运行逻辑:主类怎么启动、界面线程在哪里跑、数据库连接什么时候打开什么时候关闭、异常最终会抛到哪里。本文不讲解系统演示,也不贴花哨的运行截图,而是把整个系统从MySQL建表到Swing界面再到JDBC业务代码完整地走一遍,同时把开发期间最常见的字符集、驱动版本、表名大小写这些“卡住新手三天”的问题全部摊开来说。

2. 把数据结构先钉死:五张核心表与订单外键链

2.1 为什么开发顺序必须先MySQL后Swing

大部分首次做这类项目的同学会先启动NetBeans或Eclipse拖一个登录窗口出来,鼠标拖了半天觉得很爽,结果写业务逻辑的时候发现:登录要查员工表,点餐要写订单表,结算要更新菜品销量,报表要聚合订单明细——这些操作全都要依赖数据库结构。界面拖得越复杂,后面改表越痛苦。桌面端项目不同于Web项目,接口协议是自己定的,所以数据模型的优先级远高于界面。

我一般会建议先花半天时间把MySQL里的表建好,再回头画界面。你先确定有哪些数据需要存,以及数据之间的关系,界面上放几个按钮、几个表格自然就清楚了。一个典型的餐厅点餐管理系统,数据量并不大,但业务的完整性要求它至少覆盖员工、菜品、会员、订单、订单明细这五个实体。

2.2 建表SQL:一次把类型、编码、默认值都写对

下面这份SQL是一套比较通用的结构,适用于MySQL 5.7和8.0两个大版本。注意我刻意少用了一些时髦特性,比如JSON类型、CHECK约束,目的是让你在低版本MySQL上也能直接跑通。

-- 员工表:登录账号、角色区分管理员和收银员 CREATE TABLE staff ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '员工ID', username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录账号', password VARCHAR(255) NOT NULL COMMENT '密码(建议存MD5)', real_name VARCHAR(50) NOT NULL COMMENT '真实姓名', role TINYINT NOT NULL DEFAULT 2 COMMENT '角色:1管理员,2收银员', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工表'; -- 菜品表:状态字段用于下架 CREATE TABLE dish ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '菜品ID', name VARCHAR(100) NOT NULL COMMENT '菜品名称', category VARCHAR(50) NOT NULL COMMENT '分类:热菜/凉菜/主食/饮品', price DECIMAL(10,2) NOT NULL COMMENT '单价', status TINYINT NOT NULL DEFAULT 1 COMMENT '1在售,0下架', sales_count INT NOT NULL DEFAULT 0 COMMENT '销量统计' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜品表'; -- 会员表:手机号作为业务主键 CREATE TABLE member ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '会员ID', phone VARCHAR(20) NOT NULL UNIQUE COMMENT '手机号', name VARCHAR(50) NOT NULL COMMENT '会员姓名', points INT NOT NULL DEFAULT 0 COMMENT '积分' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员表'; -- 订单表 CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID', order_no VARCHAR(30) NOT NULL UNIQUE COMMENT '订单号', staff_id INT NOT NULL COMMENT '操作收银员', member_id INT DEFAULT NULL COMMENT '会员ID,可有可无', total_amount DECIMAL(10,2) NOT NULL COMMENT '订单总金额', pay_time DATETIME NOT NULL COMMENT '支付时间', FOREIGN KEY (staff_id) REFERENCES staff(id), FOREIGN KEY (member_id) REFERENCES member(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表'; -- 订单明细表 CREATE TABLE t_order_item ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '明细ID', order_id INT NOT NULL COMMENT '所属订单', dish_id INT NOT NULL COMMENT '菜品ID', quantity INT NOT NULL COMMENT '数量', subtotal DECIMAL(10,2) NOT NULL COMMENT '小计', FOREIGN KEY (order_id) REFERENCES t_order(id), FOREIGN KEY (dish_id) REFERENCES dish(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表';

这份SQL里面有四个细节值得你注意。

第一,订单表我命名为t_order而不是order。ORDER是MySQL的保留字,你写CREATE TABLE order会直接语法报错,加了前缀t_就安全了,而且这个习惯在联表查询时语义也更清楚。

第二,所有表都使用InnoDB引擎。原因很简单:外键约束在InnoDB下才真正生效,你用MyISAM虽然能建出表,但外键只是文档上的说明,实际删除父表记录时子表不会收到限制。

第三,金额统一用DECIMAL(10,2),绝对不要用FLOAT或DOUBLE存钱。二进制浮点数在累加时会产生令人困惑的精度误差,比如0.1+0.2不等于0.3。点餐系统的结算金额每分钱都要对得上,用定点数才是正经做法。

第四,staff表里我在role字段上加了默认值2。意思是说,如果程序漏传了这个字段,默认创建的是收银员账号而不是管理员,避免误操作把最高权限给出去。

2.3 外键与索引:查询快一个量级的代价

建好表之后,有人会纠结要不要真的在t_order_item上建外键。我的建议是建——这是桌面端系统,并发量远没有Web后端那么夸张,外键带来的额外检查成本完全可忽略,而它带来的数据完整性保障却非常实惠。比如你删掉一个菜品,如果订单明细里还在引用它,MySQL会拒绝删除并报错,这比你写了半天的软删除逻辑要省事得多。

另外别忘了给查询字段加索引。t_order表上的pay_time一定会被用于“查询某日营业额”的报表统计,不加索引时全表扫描对几十万条订单数据会卡出明显延迟。可以在建表语句之外补一句:

ALTER TABLE t_order ADD INDEX idx_pay_time (pay_time);

关于销量统计,很多人会想到用ORDER BY sales_count DESC排个序,然后在程序里统计。这里我可以提醒你一句:点餐系统开发到后期,你大概率会需要一张“菜品销量排行”的报表。与其在Java代码里聚合数据,不如直接用一条SQL:

SELECT d.name, SUM(ti.quantity) AS sold FROM t_order_item ti JOIN dish d ON ti.dish_id = d.id GROUP BY d.id ORDER BY sold DESC;

手续越少越好——这就是数据库先行带来的直接收益。你的Swing界面后期只要绑定这个查询结果,就有现成的报表数据源。

3. 界面层:Swing组件写点餐主界面的取舍

3.1 Swing开发的“劝退点”与应对方式

Swing被吐槽得最多的就是界面丑、布局难调。丑的问题我承认,但布局难调多半是因为没用对布局管理器。很多初学者拿到JFrame就直接setLayout(null),然后靠setBounds(x, y, w, h)去坐标手工定位,这种写法在窗口拉伸或者字体缩放时必然乱套。桌面端窗口一般不允许用户自由缩放,但布局管理器仍然会让你的代码简洁很多。

应对方式就一条:外层用BorderLayout,内部面板用GridBagLayout。BorderLayout最符合点餐系统的界面结构——顶部放标题和当前登录人,左侧放菜品分类列表,中央放菜品表格,右侧放购物车列表,底部放结算按钮和统计信息。GridBagLayout虽然写起来麻烦,但它能精确控制控件在面板里的位置、宽度权重、拉伸策略,特别是购物车这种需要“数量列宽一点、金额列宽一点”的场景,不用它很难做得舒服。

3.2 主界面骨架代码:JFrame+JPanel+JTable的最小运行结构

先看下面的代码,这是一个能运行的最简主窗体结构。这里我只写了框架部分,业务细节在第四章展开。

import javax.swing.*; import java.awt.*; public class MainFrame extends JFrame { public MainFrame() { setTitle("餐厅点餐管理系统"); setSize(1024, 768); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); // 窗口居中 // 顶部:登录信息 JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); topPanel.add(new JLabel("当前操作员:" + UserContext.getCurrentUserName())); topPanel.add(new JButton("退出登录")); // 左侧:菜品分类 JPanel leftPanel = new JPanel(new BorderLayout()); JList<String> categoryList = new JList<>(new String[]{"热菜", "凉菜", "主食", "饮品"}); leftPanel.add(new JLabel("菜品分类", SwingConstants.CENTER), BorderLayout.NORTH); leftPanel.add(new JScrollPane(categoryList), BorderLayout.CENTER); // 中央:菜品列表(仅展示在售) JTable dishTable = new JTable(); JScrollPane dishScroll = new JScrollPane(dishTable); dishTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // 右侧:购物车 JTable cartTable = new JTable(); JScrollPane cartScroll = new JScrollPane(cartTable); // 底部:结算区 JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); JLabel totalLabel = new JLabel("合计:¥0.00"); JButton checkoutBtn = new JButton("结算"); bottomPanel.add(totalLabel); bottomPanel.add(checkoutBtn); // 组装:中央用JSplitPane分隔菜品区和购物车 JSplitPane splitPane = new JSplitPane( JSplitPane.HORIZONTAL_SPLIT, dishScroll, cartScroll); splitPane.setResizeWeight(0.6); // 左侧占60%宽 add(topPanel, BorderLayout.NORTH); add(leftPanel, BorderLayout.WEST); add(splitPane, BorderLayout.CENTER); add(bottomPanel, BorderLayout.SOUTH); } public static void main(String[] args) { // Swing界面必须在事件调度线程创建,否则偶发界面不刷新 SwingUtilities.invokeLater(() -> { new MainFrame().setVisible(true); }); } }

SwingUtilities.invokeLater这行代码建议你形成肌肉记忆,后面写任何Swing程序都带上它。它是Swing单线程模型的入口,把界面创建任务丢给EDT(事件派发线程)执行,如果你直接在main线程里创建JFrame,某些Linux和macOS环境下窗口可能无法正确绘制。

JSplitPane比用一个固定布局更实用。点餐时中央菜品的行数往往少于购物车行数,分割条允许操作员手动调整两个区域的高度平衡,这对桌面上鼠标操作来说体验好很多。

3.3 购物车与订单窗口的组件选型

购物车和菜品列表我用的是JTable而不是JList,这里解释一下理由。JTable天然支持多列——菜品名称、单价、数量、小计放到一张表里,每一列可以单独设置宽度。而JList只能显示单列文本,你还要自己拼HTML才能做出两列的效果,对TableModel的修改也会被直接映射到界面,刷新效率更好。

订单结算完毕后,建议弹出一个新的JDialog窗口用来展示本次订单的明细。JDialog比JFrame更合适做这种临时窗口,因为它可以不创建任务栏图标,而且设置了setModal(true)之后,用户必须关掉它才能回到主界面继续操作,符合“结完账再看一眼”的使用习惯。

4. 业务层:JDBC连接与登录、点餐、统计三条主流程

4.1 JDBC连接参数:driver、url四件套

Swing界面写完之后,你要做的第一件事就是把数据库连接打通。这一步的重要性不用多说,但连接失败之后很多人就卡住了。先看一个正确的连接工具类写法:

import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { // 这四个配置项建议放到配置文件,这里写死仅为演示 private static final String DRIVER = "com.mysql.cj.jdbc.Driver"; private static final String URL = "jdbc:mysql://localhost:3306/restaurant" + "?useUnicode=true&characterEncoding=utf8" + "&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"; private static final String USER = "root"; private static final String PASSWORD = "你的数据库密码"; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new RuntimeException("MySQL驱动加载失败,检查jar包是否在classpath", e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, java.sql.Statement stmt, java.sql.ResultSet rs) { try { if (rs != null) rs.close(); } catch (SQLException ignored) {} try { if (stmt != null) stmt.close(); } catch (SQLException ignored) {} try { if (conn != null) conn.close(); } catch (SQLException ignored) {} } }

这套连接参数里,最容易出问题的三个参数分别是useSSL、serverTimezone和allowPublicKeyRetrieval。

如果去掉serverTimezone,本地MySQL 8.0会报“The server time zone value”的错误。原因是MySQL服务器和JDBC驱动双方的时区默认值不一致。

如果allowPublicKeyRetrieval不打开,MySQL 8.0下caching_sha2_password用户连接时经常会抛出“Public Key Retrieval is not allowed”异常,这是MySQL 8.0默认加密插件的策略导致的。

如果不加characterEncoding=utf8,后面你往数据库里写入中文的时候就会变成问号,这个问题我在第五章细讲。

另外注意我在静态代码块里做了Class.forName(DRIVER)。在JDBC 4.0以后,驱动jar包里的META-INF/services/java.sql.Driver文件会被自动扫描,这一步其实可以省掉。但保留下来有个好处:如果驱动jar没打包进classpath,程序启动时立刻就会抛异常提示你,比连接到一半才找不到类错误更明确。

4.2 登录校验:用PreparedStatement代替Statement

登录逻辑是每个点餐系统都要经历的第一道安全关。这里有一个容易犯的经典错误——用Statement拼接SQL字符串:

// 错误示范:千万不要这么写 String sql = "SELECT * FROM staff WHERE username = '" + username + "' AND password = '" + password + "'"; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql);

这段代码在SQL注入面前毫无还手之力。比如用户在输入框敲入admin' --,整个SQL就变成查询admin用户了。正确做法是使用PreparedStatement参数化查询:

public boolean login(String username, String password) { String sql = "SELECT id, real_name, role FROM staff WHERE username = ? AND password = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, md5(password)); // 至少做一次MD5,不要存明文 try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { UserContext.setCurrentUser( rs.getInt("id"), rs.getString("real_name"), rs.getInt("role") ); return true; } } } catch (SQLException e) { e.printStackTrace(); } return false; }

PreparedStatement把参数通过setString传入,数据库驱动会对值做转义处理,这样拼接注入只存在于初中级教程里的反面案例中。

注意这里的加密逻辑:演示代码里我写了md5(password),但真实项目中MD5已经被证实不够安全,建议用BCrypt或SHA-256 + 随机盐。对于课设项目,MD5加盐属于及格水平,但你要在答辩时能说出理由——这才是重点。

另外,连接管理这里使用了try-with-resources语法,一个try块结束后Connection、PreparedStatement、ResultSet都会被自动关闭,不会出现连接泄漏。桌面端应用连接池往往不需要,因为使用频率低,且长时间保持连接在MySQL默认wait_timeout八小时后会自动断掉,反而造成困惑。每次操作临时打开连接再关闭是更稳的方案。

4.3 点餐结算与当日营业额统计

点餐结算的关键在于事务。用户点击“结算”那一刻,程序要做三件事:插入订单主表、插入订单明细表、累加菜品的销量。这三件事任何一件失败,数据库就会处于不一致状态——订单有了但明细缺失。代码如下:

public boolean checkout(CartItem[] items, Integer memberId) { // 使用事务:要么全部成功,要么全部回滚 String insertOrder = "INSERT INTO t_order(order_no, staff_id, member_id, total_amount, pay_time) VALUES(?,?,?,?,NOW())"; String insertItem = "INSERT INTO t_order_item(order_id, dish_id, quantity, subtotal) VALUES(?,?,?,?)"; String updateSales = "UPDATE dish SET sales_count = sales_count + ? WHERE id = ?"; Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交,开启事务 // 生成订单号:时间戳+随机数,避免并发冲突 String orderNo = "D" + System.currentTimeMillis() + (int)(Math.random()*1000); try (PreparedStatement ps = conn.prepareStatement(insertOrder, PreparedStatement.RETURN_GENERATED_KEYS)) { ps.setString(1, orderNo); ps.setInt(2, UserContext.getCurrentUserId()); if (memberId != null) { ps.setInt(3, memberId); } else { ps.setNull(3, java.sql.Types.INTEGER); } double total = 0; for (CartItem item : items) { total += item.getSubtotal(); } ps.setDouble(4, total); ps.executeUpdate(); // 获取数据库自动生成的订单ID ResultSet keys = ps.getGeneratedKeys(); int orderId = -1; if (keys.next()) { orderId = keys.getInt(1); } try (PreparedStatement psItem = conn.prepareStatement(insertItem); PreparedStatement psSales = conn.prepareStatement(updateSales)) { for (CartItem item : items) { psItem.setInt(1, orderId); psItem.setInt(2, item.getDishId()); psItem.setInt(3, item.getQuantity()); psItem.setDouble(4, item.getSubtotal()); psItem.addBatch(); psSales.setInt(1, item.getQuantity()); psSales.setInt(2, item.getDishId()); psSales.addBatch(); } psItem.executeBatch(); psSales.executeBatch(); } } conn.commit(); return true; } catch (SQLException e) { try { if (conn != null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { try { if (conn != null) { conn.setAutoCommit(true); conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } }

这段代码里有两个容易被忽略的点。第一是PreparedStatement.RETURN_GENERATED_KEYS参数,没有它你拿不到数据库自增的订单ID,你没法往下插入明细。第二是addBatch()+executeBatch()批量执行,这是把多条INSERT合并成一次网络往返的技巧,在桌面端虽然体感差异不大,但代码结构更清晰。

当日销售额统计比想象中简单得多:

SELECT COALESCE(SUM(total_amount), 0) AS today_amount FROM t_order WHERE DATE(pay_time) = CURDATE();

COALESCE在这里很关键,因为如果今天还没有任何订单,SUM会返回NULL而不是0,Java侧解析rs.getDouble时会把NULL转成0,但万一哪一天没有订单就显示不出金额,那界面就有问题了。加上COALESCE之后,SQL就永远返回数字。

5. 开发期间最容易翻车的5个地方:现象、原因、解决办法

5.1 Class.forName之后仍然连接超时

现象:程序启动后卡在DriverManager.getConnection这一行,等几十秒后抛CommunicationsException: Communications link failure。

原因:MySQL服务没有启动。Windows下最容易出现这种局面——装了MySQL但忘了注册成服务,每次要手动打开命令行启动,或者是在服务管理器里启动了但端口被占用。

解决办法:先确认服务状态。Windows上运行net start mysql,Linux上运行systemctl status mysqld。然后确认端口通不通:telnet 127.0.0.1 3306,如果telnet连不上,八成是MySQL服务没起来。再检查一下URL里的localhost是解析到了IPv6还是IPv4,某些机器localhost会指向::1而MySQL只监听IPv4,把localhost换成127.0.0.1能绕开这个玄学问题。

5.2 MySQL驱动类名不同导致ClassNotFound

现象:Class.forName("com.mysql.jdbc.Driver")抛出ClassNotFoundException。

原因:mysql-connector-java 5.x的驱动类名是com.mysql.jdbc.Driver,但8.x已经把它改为com.mysql.cj.jdbc.Driver。旧类名在新驱动里只是遗留的桥接类,在某些版本被移除了。

解决办法:在pom.xml或lib目录里查看你的jar版本。如果是8.0+,写com.mysql.cj.jdbc.Driver,URL里记得带上serverTimezone=Asia/Shanghai。如果你是在做一个新项目,直接使用8.0以上的驱动,旧驱动连不上MySQL 5.7.44和8.0。常见做法是把驱动jar放进项目的lib目录,然后用IDE的“Add as Library”导入,避免手动设置classpath时漏掉。

5.3 数据库中文全部变成问号

现象:通过点餐系统插入的菜名、用户名在MySQL里显示为???,但在Navicat里手动插入中文又是正常的。

原因:十有八九是JDBC连接URL没有指定字符集。MySQL服务端默认字符集可能是latin1,你直接写入UTF-8编码的中文数据,存储层面上编码不匹配就变成了问号。

解决办法:URL里加useUnicode=true&characterEncoding=utf8,并且确认建表语句里加了DEFAULT CHARSET=utf8mb4。这两个地方缺一不可。另外,如果你的Windows控制台运行Java程序时打出中文乱码,那是系统控制台编码本身是GBK,与数据库存储无关,你可以在程序里用-Dfile.encoding=UTF-8运行参数修正,避免和数据库字符集问题混在一起排查。

5.4 JTable刷新不生效,改了数据界面还是老样子

现象:结算完,购物车表格清空后调用table.repaint(),但界面纹丝不动。

原因:repaint()只是让组件重新绘制像素,不会重新读取数据模型。JTable的数据来源于TableModel,你直接清空了List,但没有让TableModel知道数据已经变了。

解决办法:不要直接操作List再repaint。应该重新构造一个DefaultTableModel对象或调用model.setRowCount(0)来清空数据行。由于TableModel会主动发事件给视图,表格会自动重绘。如果你用的是自己继承AbstractTableModel的模型,记得在数据变化后主动调用fireTableDataChanged()。

5.5 表名大小写导致部署到Linux上查询失败

现象:Windows上开发的程序运行得好好的,部署到Linux服务器(多见于把数据库搬到生产或考试环境)后,执行SELECT * FROM t_order报Table 'restaurant.t_order' doesn't exist。

原因:MySQL在Windows上lower_case_table_names默认值为1,表名不区分大小写;在Linux上默认值为0,区分大小写。你在Windows建的表叫T_Order,Linux上找t_order就找不到。

解决办法:从开发第一天就统一使用小写表名,这是成本最低的方案。如果已经发生,在Linux端修改MySQL配置文件/etc/my.cnf,在[mysqld]段下加lower_case_table_names=1,然后重启MySQL——注意改这个参数之前最好备份数据,因为需要重建表才能完全生效。

6. 最后一公里:把系统变成能交付的jar并做一轮完整验证

课设和毕设答辩的逻辑是:程序能跑起来只是起点,能交付脱离IDE的独立可运行jar包才是加分项。下面这一步我建议你一定会做——用命令行启动验证整个项目打包正确性。

先用mvn package或IDE的Artifacts功能打包出带依赖的jar。打包时注意要带上MySQL驱动jar,即使用maven-shade-plugin或IDE里“Include in project build”。如果最终只导出了一个几十KB的空壳jar,那必然报ClassNotFoundException。

打包之后,打开命令行执行一次:

java -Dfile.encoding=UTF-8 -jar restaurant-system.jar

为什么一定要手动跑一次?因为IDEA里运行程序时会自动帮你加上一堆classpath参数,而命令行是最真实的环境。如果你在命令行能跑起来,说明驱动jar、配置文件、日志库都齐了,演示时不会翻车。

启动成功后,建议按这个清单做一次回归验证:

  1. 用普通收银员账号登录,确认没有员工管理菜单
  2. 新增一个菜品,刷新菜品列表,确认在售状态为1
  3. 点一个菜品到购物车,结算后去MySQL执行SELECT * FROM t_order_item,确认明细行数正确
  4. 查当日报表,确认金额等于购物车合计
  5. 关掉程序重新打开,确认销量数据持久化

这五步走完,系统基本达到了可交付状态。

如果想把项目做成局域网多机部署,可以在另一台电脑上装MySQL服务端或直接把连接URL的localhost改成数据库所在机器的IP地址。这里有个隐患你需要提前知道:MySQL默认只监听localhost,需要把bind-address改成0.0.0.0并新建一个允许%远程访问的账号,否则局域网内其他机器连不上。

最后说一个我自己栽过跟头的习惯:数据库密码和连接信息不要直接写死在业务代码里,至少抽到config.properties外面。虽然Swing项目没有Web项目那么强的安全要求,但答辩时你拿出一个配置文件,解释“连接信息与业务代码解耦,便于部署切换环境”,评委的印象分会明显不同。数据库这块宁可多花半小时做扎实,也别等报表统计时被一句“数据对不上”逼得从头找。

做桌面端管理系统这么多年,我最大的体会就是Swing项目不是用来展示前沿技术的,而是用来检验你对Java最基础能力的掌握程度——事件、线程、IO、集合、JDBC,每一个环节都是你自己亲手拼出来的。遇到它给的这些坑也不用怕,踩过一次,下次你就知道先看编码再看表名大小写。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询