ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java+Swing+MySQL点餐系统开发:数据库设计、事务处理与JAR打包

Java+Swing+MySQL点餐系统开发:数据库设计、事务处理与JAR打包 简介这套基于Java Swing与MySQL的餐厅点餐管理系统面向Java初、中级学习者及需要课程设计/毕业设计参考的高校学生。项目完整实现管理员与顾客双角色流程包含注册登录、套餐新增与管理、订单处理、点餐及个人信息修改等核心模块覆盖Swing界面设计、JDBC数据库操作、分层开发等常见知识点。压缩包共170个文件以18个java源文件、66个class编译文件为主附带数据库sql脚本、界面截图png/jpg及项目依赖jar包整体大小2.19MB解压后可直接导入IDE运行。目前已有1532人学习适合用来对照练习Swing窗体布局、事件监听与MySQL数据交互。通过阅读源码和界面素材可快速理解点餐系统从数据表设计到功能实现的全过程也可在此基础上扩展菜品分类、销量统计等功能。1. 为什么 2025 年还有人拿 JavaSwingMySQL 做点餐系统如果你去翻各大高校的 Java 课程设计题目点餐管理系统几乎是出场率最高的那个。不是因为它时髦而是因为它把桌面应用最核心的三件事全占了界面交互、数据库持久化、业务逻辑落盘。Java 负责业务建模Swing 把操作界面拖出来MySQL 把订单和菜品的数据存住——三个技术点都是 Java 桌面开发绕不开的基本功。你要是能把这套系统从零写到能跑Java 面试题里常问的 JDBC 连接、事务控制、PreparedStatement 防注入、事件监听这些概念就不再是背出来的而是真正用过的。这篇文章不会给你贴一份完整源码让你复制粘贴了事而是把做这个系统最常见的方案拆开讲清楚数据库表怎么设计、DAO 层怎么写、Swing 界面怎么跟数据绑定、订单提交时怎么处理事务以及最后怎么打包成能发给别人的可执行 JAR。每段代码后面我都会说明为什么这样写、哪些参数能调、失败时应该看哪里。新手可以照着敲熟手可以直接跳到第五章看优化思路。2. 技术选型Swing 和 MySQL 在这个项目里谁说了算2.1 为什么是 Swing 而不是 JavaFXJava 桌面开发现在有两个官方 UI 框架可选Swing 和 JavaFX。很多新项目选了 JavaFX因为界面更现代、支持 CSS 样式但点餐管理系统这类课设和内部小工具Swing 反而更合适。原因有三条第一Swing 是 JDK 自带的不需要额外装运行时。JavaFX 从 JDK 11 开始被剥离出 JDK要单独引依赖光这一步就能劝退一批环境还没配好的新手。第二Swing 的资料存量极大。你搜JavaSwingMySQL 点餐系统能找到十年前的论坛帖子、GitHub 上的完整项目、CSDN 上的设计文档。遇到问题一搜就有答案这对课设赶工来说是最实际的效率优势。第三Swing 的事件监听模型足够经典。ActionListener、MouseListener、TableModel 这些接口理解透了以后看 Android 的点击事件、看前端的事件绑定都是同一个套路。用 Swing 做项目练的是底层的事件驱动思维而不是某个框架的 API。当然你如果做一个要上线商用、对界面颜值有要求的系统Swing 确实不够看。但如果是课程设计、毕业设计、企业内部工具Swing 的成熟稳定反而成了优点。2.2 MySQL 选型和 JDBC 驱动版本怎么定MySQL 这边最常见的教学组合是 MySQL 5.7 或 8.0 mysql-connector-java 8.x 驱动。如果你机器上已经装了 8.0直接用 8.0 的驱动就行如果用的是 5.7驱动也建议用 8.x 版本因为它向下兼容 5.x 的协议。连接字符串写这一行jdbc:mysql://localhost:3306/restaurant?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8参数说明useSSLfalse本地开发不需要加密连接不加这个参数 MySQL 8.0 会警告虽然不影响运行但日志会很吵serverTimezoneAsia/ShanghaiMySQL 8.0 默认时区是 UTC不加这个参数你往数据库里存时间取出来会差 8 小时characterEncodingutf8保证中文菜品名和订单备注不乱码驱动 JAR 包下载后放到项目里的lib目录用 IDE 导入时选择Add as Library。注意不要用System.setProperty(jdbc.drivers, ...)这种写法直接在DriverManager.getConnection()之前写Class.forName(com.mysql.cj.jdbc.Driver)虽然 JDBC 4.0 以后这一步可以省略但写上能让阅卷老师一眼看懂你在加载驱动。2.3 数据库连接用 JDBC 还是连接池课设项目里直接用 JDBC 是常规操作但你要是想在答辩时多拿点分用一个简单的连接池会让代码质量上一个档次。自己做课设的话别引入 MyBatis 或者 HikariCP 这种重量级框架——没必要而且答辩时老师会追着问原理。我一般建议自己写一个极简连接池用LinkedList存连接对象核心逻辑也就三十行public class SimpleConnectionPool { private static LinkedListConnection pool new LinkedList(); private static final int INITIAL_SIZE 5; private static final int MAX_SIZE 10; private static String url jdbc:mysql://localhost:3306/restaurant?...; static { try { Class.forName(com.mysql.cj.jdbc.Driver); for (int i 0; i INITIAL_SIZE; i) { pool.add(DriverManager.getConnection(url, root, password)); } } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { if (pool.isEmpty()) { synchronized (pool) { if (pool.isEmpty() pool.size() MAX_SIZE) { pool.add(DriverManager.getConnection(url, root, password)); } } } return pool.removeFirst(); } public static void returnConnection(Connection conn) { if (conn ! null) { pool.add(conn); } } }逻辑说明连接池的本质就是预先创建几个连接放在池子里用的时候从池头取用完还回池尾。synchronized保证多线程同时取连接时不会重复创建超出上限的连接。这里要注意MAX_SIZE不能设太大MySQL 默认的最大连接数是 151你池子里放 100 个等于把数据库逼到极限。3. 数据库表设计与 DAO 落地把菜单和订单变成可操作的对象3.1 建表 SQL五张表怎么设计才不冗余点餐系统的核心数据是菜品、订单、订单明细再加上员工和管理员。设计表的时候记住一个原则一张表只存一类事物的信息订单和菜品之间用中间表关联。完整的建表脚本是这样的CREATE DATABASE IF NOT EXISTS restaurant DEFAULT CHARSET utf8mb4; USE restaurant; CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(20) ); CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20) NOT NULL UNIQUE, sort_order INT DEFAULT 0 ); CREATE TABLE dish ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL, name VARCHAR(50) NOT NULL, price DECIMAL(10, 2) NOT NULL, image_path VARCHAR(100), description TEXT, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, FOREIGN KEY (category_id) REFERENCES category(id) ); CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, table_no VARCHAR(10) NOT NULL, total_amount DECIMAL(10, 2) NOT NULL, status TINYINT DEFAULT 0 COMMENT 0未支付 1已支付 2已出餐, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME ); CREATE TABLE order_detail ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, dish_id INT NOT NULL, dish_name VARCHAR(50) NOT NULL, price DECIMAL(10, 2) NOT NULL, quantity INT NOT NULL, subtotal DECIMAL(10, 2) NOT NULL, FOREIGN KEY (order_id) REFERENCES orders(id), FOREIGN KEY (dish_id) REFERENCES dish(id) );表设计说明order_detail里同时存了dish_id和dish_name有人会觉得冗余但这恰恰是为了防止菜品改名或删除后订单记录对不上。实际做餐饮的系统都是这么干的这叫快照冗余用空间换数据的稳定性。DECIMAL(10,2)而不是DOUBLE是因为浮点数算钱会丢精度这也是 Java 后端开发里讲小数计算时的标准答案。菜品价格如果只用DOUBLE一次0.1 0.2的结果就能让你在结账时少收几分钱。3.2 DAO 层的三个必写方法查询、插入、更新DAOData Access Object这一层做的是把 SQL 语句封装成 Java 方法。以菜品为例最基础的是按分类查询菜品、新增菜品、修改菜品状态。查询菜品的代码用 PreparedStatement 防止 SQL 注入public class DishDAO { public ListDish findByCategoryId(int categoryId) { String sql SELECT * FROM dish WHERE category_id ? AND status 1 ORDER BY id ASC; ListDish list new ArrayList(); try (Connection conn SimpleConnectionPool.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, categoryId); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Dish dish new Dish(); dish.setId(rs.getInt(id)); dish.setName(rs.getString(name)); dish.setPrice(rs.getBigDecimal(price)); dish.setImagePath(rs.getString(image_path)); list.add(dish); } } } catch (SQLException e) { e.printStackTrace(); } finally { // 连接归还到连接池 } return list; } }代码里的关键点try-with-resources写法能保证ResultSet、PreparedStatement自动关闭这个写法从 JDK 7 开始就有Java 面试题里常考?占位符配合ps.setInt()传入参数SQL 注入攻击在这里失效因为PreparedStatement会在数据库端预编译用户输入不会被当成 SQL 代码拼接执行。新增菜品的方法类似但要注意设置返回自增主键。MySQL 的AUTO_INCREMENT字段插入成功后不会自动回填到 Java 对象的 id 属性里需要额外处理public boolean insertDish(Dish dish) { String sql INSERT INTO dish (category_id, name, price, description, status) VALUES (?, ?, ?, ?, 1); try (Connection conn SimpleConnectionPool.getConnection(); PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setInt(1, dish.getCategoryId()); ps.setString(2, dish.getName()); ps.setBigDecimal(3, dish.getPrice()); ps.setString(4, dish.getDescription()); int rows ps.executeUpdate(); if (rows 0) { try (ResultSet keys ps.getGeneratedKeys()) { if (keys.next()) { dish.setId(keys.getInt(1)); } } return true; } } catch (SQLException e) { e.printStackTrace(); } return false; }这里的Statement.RETURN_GENERATED_KEYS是个容易忽略的细节。不加这个参数getGeneratedKeys()返回的结果集就是空的你拿到的新增记录 id 永远是 0。这个坑我在给项目加购物车功能时实实在在踩过菜品加入购物车后想按 id 修改数量结果 id 全是 0一改就把第一条记录改了。3.3 订单提交的原子性问题事务边界怎么画订单提交是整个系统里唯一涉及多表写入的操作先往orders表插入订单主表再往order_detail表插入明细。这两步必须是一个事务——如果订单主表插入成功但明细失败就会出现一张没有内容的空订单。Java SE 里没有 Spring 的事务注解事务得手动控制public boolean createOrder(Order order, ListOrderDetail details) { Connection conn null; try { conn SimpleConnectionPool.getConnection(); conn.setAutoCommit(false); // 关闭自动提交手动开启事务 String insertOrder INSERT INTO orders (order_no, table_no, total_amount, status) VALUES (?, ?, ?, 0); try (PreparedStatement ps conn.prepareStatement(insertOrder, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, generateOrderNo()); ps.setString(2, order.getTableNo()); ps.setBigDecimal(3, calculateTotal(details)); ps.executeUpdate(); try (ResultSet keys ps.getGeneratedKeys()) { if (keys.next()) { order.setId(keys.getInt(1)); } } } String insertDetail INSERT INTO order_detail (order_id, dish_id, dish_name, price, quantity, subtotal) VALUES (?, ?, ?, ?, ?, ?); try (PreparedStatement ps conn.prepareStatement(insertDetail)) { for (OrderDetail detail : details) { ps.setInt(1, order.getId()); ps.setInt(2, detail.getDishId()); ps.setString(3, detail.getDishName()); ps.setBigDecimal(4, detail.getPrice()); ps.setInt(5, detail.getQuantity()); ps.setBigDecimal(6, detail.getPrice().multiply(BigDecimal.valueOf(detail.getQuantity()))); ps.addBatch(); } ps.executeBatch(); } conn.commit(); // 全部成功提交事务 return true; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); // 任一环节出错回滚所有操作 } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { if (conn ! null) { try { conn.setAutoCommit(true); // 恢复自动提交把连接还回池子 SimpleConnectionPool.returnConnection(conn); } catch (SQLException e) { e.printStackTrace(); } } } }这段代码有三处值得复盘的地方。第一executeBatch()批量执行明细插入比一条条executeUpdate()快得多数据量大时差距明显。第二conn.setAutoCommit(false)之后如果忘记commit()就关连接MySQL 会默认回滚数据静默丢失。第三连接归还前一定要把autoCommit恢复成true否则连接池里的其他代码拿到的还是事务态连接一个新的查询可能读到旧事务里的脏数据。4. Swing 界面与数据绑定别把界面代码写成三千行的上帝类4.1 主窗口布局JFrame CardLayout 还是 JTabbedPane点餐系统的界面通常分两个角色服务员端要点单、结算管理员端要维护菜品、看订单统计。最常见的布局是左侧一个导航栏右侧内容区根据点击切换面板。实现这个效果有两种方式——JTabbedPane或者CardLayout。JTabbedPane 适合角色功能固定、标签不会动态变化的管理端代码少外观自带边框。而服务员点餐界面我更推荐CardLayout因为你要配合全屏菜单展示点餐时隐藏导航栏、结账时再切回来CardLayout 可以直接控制显示哪一块JPanel mainPanel new JPanel(); CardLayout cardLayout new CardLayout(); mainPanel.setLayout(cardLayout); mainPanel.add(dishPanel, DISH_PANEL); mainPanel.add(orderPanel, ORDER_PANEL); mainPanel.add(settlePanel, SETTLE_PANEL); // 点击去结算按钮 cardLayout.show(mainPanel, SETTLE_PANEL);CardLayout的show(Container, String)方法的第二个参数是你在add()时指定的约束字符串。这里有个容易犯的错页面切换后界面上之前输入的数据还留着因为组件没有重建。解决办法是在切页监听里刷新数据或者用removeAll()后重新add()二选一但别两个都做否则会出现闪烁。4.2 JTable 绑定数据的正确姿势TableModel 与 setValueAt菜品列表和订单明细都要用JTable展示。新手最容易踩的坑是直接往DefaultTableModel里塞数据但不实现单元格更新回调结果界面上改了数量后台的订单对象完全不知情。正确做法是自定义TableModel把数据源和界面控件实时同步public class DishTableModel extends AbstractTableModel { private String[] columns {菜品编号, 菜品名称, 价格, 数量}; private ListOrderDetail details; public DishTableModel(ListOrderDetail details) { this.details details; } Override public int getRowCount() { return details.size(); } Override public int getColumnCount() { return columns.length; } Override public Object getValueAt(int rowIndex, int columnIndex) { OrderDetail d details.get(rowIndex); switch (columnIndex) { case 0: return d.getDishId(); case 1: return d.getDishName(); case 2: return d.getPrice(); case 3: return d.getQuantity(); default: return null; } } Override public boolean isCellEditable(int rowIndex, int columnIndex) { return columnIndex 3; // 只允许修改数量列 } Override public void setValueAt(Object value, int rowIndex, int columnIndex) { if (columnIndex 3) { int quantity Integer.parseInt(value.toString()); details.get(rowIndex).setQuantity(quantity); fireTableCellUpdated(rowIndex, columnIndex); } } }这个 Model 类的价值在于界面上的修改会直接写回OrderDetail对象而不只是改了表格显示。fireTableCellUpdated()会通知JTable重绘该单元格省去手动刷新整个表格。注意quantity的输入校验——解析成int之前要先try-catch不然用户在单元格里输入abc会抛NumberFormatException直接把整个界面搞崩。我一般会先写一个正则校验只允许 1 到 99 之间的数字。4.3 事件监听与耗时操作的坑EDT 线程上别做数据库查询Swing 的所有界面操作都在 Event Dispatch ThreadEDT上执行。你在按钮的ActionListener里直接写数据库查询点一下按钮界面就卡死——鼠标能动但窗口不刷新这就是 EDT 被阻塞了。解决方式是用SwingWorker把耗时操作丢到后台线程btnSave.addActionListener(e - { btnSave.setEnabled(false); // 防止重复提交 new SwingWorkerVoid, Void() { Override protected Void doInBackground() { boolean ok orderService.createOrder(order, details); if (ok) { SwingUtilities.invokeLater(() - { JOptionPane.showMessageDialog(frame, 下单成功); cardLayout.show(mainPanel, DISH_PANEL); btnSave.setEnabled(true); }); } return null; } }.execute(); });doInBackground()里执行的是后台线程可以放心查数据库界面上的回调必须用SwingUtilities.invokeLater()切回 EDT 执行。这个写法的好处是用户界面始终流畅点单时不会出现未响应的假死状态。如果不开新线程MySQL 查询响应慢的瞬间操作系统就会弹窗提示Java 程序未响应这在答辩演示时是相当尴尬的场面。5. 三层架构拆分与订单编号生成让代码不只是能跑5.1 一层代码和三层代码的分界线在哪里很多课设项目的代码是这样组织的一个MainFrame.java里既有窗口布局、事件监听又有 JDBC 查询代码。整个文件两千行跑起来没问题但你的系统也就停留在跑起来这个层面。要让它达到系统的水准得把职责拆开——这就是常说的三层架构表现层UIJFrame、JPanel、JTable 这些界面组件只负责展示数据和收集用户操作业务逻辑层Service订单金额计算、库存校验、订单状态流转这些规则数据访问层DAO只在 Service 层被调用持有 SQL 语句和 Connection 管理我用一个实际的登录验证来说明这个分层的价值。如果你把SELECT * FROM admin WHERE username? AND password?写在按钮的 ActionListener 里那这个按钮只能干登录这一件事以后要加记住密码自动登录、验证码校验就得再复制一遍查询代码。如果拆出AdminService.login(String username, String password)方法public class AdminService { private AdminDAO adminDAO new AdminDAO(); public boolean login(String username, String password) { // 参数校验 if (username null || username.trim().isEmpty()) { throw new IllegalArgumentException(用户名不能为空); } // 业务规则密码加密后对比 String encryptedPwd encrypt(password); Admin admin adminDAO.findByUsername(username); return admin ! null admin.getPassword().equals(encryptedPwd); } private String encrypt(String rawPassword) { // MD5 或 SHA-256 哈希 return DigestUtils.md5Hex(rawPassword); } }UI 层的登录按钮只需要调adminService.login()根据返回的 boolean 决定跳转。这样将来换数据库、换加密算法、增加验证码改动的范围都被隔离在 Service 层内部UI 层一行代码都不用动。这也是 Java 面试题里高内聚低耦合的活教材。5.2 订单编号生成时间戳加随机数为什么不够订单编号有两个要求唯一性和可读性。有人用System.currentTimeMillis()加Random.nextInt(1000)但同一毫秒内两个线程同时下单会撞号虽然概率低但撞上一次就够你查半天的。我习惯用日期加序列号的方式public synchronized String generateOrderNo() { SimpleDateFormat sdf new SimpleDateFormat(yyyyMMddHHmmss); String timePart sdf.format(new Date()); int sequence sequenceCounter; return timePart String.format(%03d, sequence); }synchronized保证同一时刻只有一个线程能执行编号生成String.format(%03d, sequence)把序号补足三位这样即使同一秒内有多个订单编号也是20250618143025001这种形式既不会重复又能在日志里直接看出下单时间。sequenceCounter每次系统重启要重置回 1但因为有时间戳参与拼接重启后同一秒发生撞号的概率极低。如果确实需要更严格的唯一性可以在数据库orders.order_no字段上加唯一索引插入重复时捕获DuplicateKeyException重新生成。5.3 多线程环境下的订单并发一个隐藏的坑服务员端可能同时开着多个窗口对同一个桌号下单。理论上两个窗口同时向orders表插入数据不会有问题问题出在数据校验环节——比如你要实现每桌同时最多 4 人下单或者菜品库存不能为负这类约束。这些校验如果在事务外执行就会出现经典的并发问题两个线程同时读到库存为 1同时通过校验同时扣减最后库存变成 -1。解法是给涉及库存的表增加乐观锁或悲观锁。Java 面试题里讲 MySQL 锁的分类时经常提到的SELECT ... FOR UPDATE就是悲观锁在事务内锁定读取的行// 事务内执行 String checkStock SELECT stock FROM dish WHERE id ? FOR UPDATE; try (PreparedStatement ps conn.prepareStatement(checkStock)) { ps.setInt(1, dishId); ResultSet rs ps.executeQuery(); if (rs.next() rs.getInt(stock) quantity) { throw new BusinessException(库存不足); } } // 然后执行 UPDATE dish SET stock stock - ? WHERE id ?FOR UPDATE会让其他事务的SELECT和UPDATE等待当前事务提交或回滚。如果你的点餐系统不涉及库存扣减这个技巧用不上但你要是在菜单里卖限量烤鸭这就是必须掌握的写法。6. 打包分发与常见问题排查从你电脑搬到别人电脑不翻车6.1 可执行 JAR 的导出与内嵌 MySQL 驱动项目写完后交付形态是一个可执行的 JAR 包。用 IDEA 的 Artifacts 功能导出时默认打出来的 JAR 用java -jar运行会报ClassNotFoundException: com.mysql.cj.jdbc.Driver——因为依赖的 MySQL 驱动 JAR 根本没打进去。解决方法是打 fat JAR也叫 uber JAR把所有依赖解压后合并进一个 JAR 包。如果你用 Maven 管理项目直接在pom.xml里加maven-shade-plugin如果是手工导 JARIDEA 里选择Build Artifacts - Add - JAR - From modules with dependencies然后在Main-Class里填你带main方法的类全名比如com.restaurant.MainApp。导出成功后命令行验证一下java -jar restaurant-system.jar如果提示Error: Could not find or load main class八成是 MANIFEST.MF 里的Main-Class写错了。注意类名前面不要带.class后缀包名分隔符用点号。6.2 五个高频报错与对应的排查方式报错一java.sql.SQLException: Access denied for user rootlocalhost。现象是程序启动后连不上数据库。原因一般是密码错误或者 MySQL 8.0 用的加密插件是caching_sha2_password而本地驱动版本太旧不支持。解决确认 MySQL 用户密码或者在 MySQL 命令行里执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码;换回老插件。报错二Unknown database restaurant。原因是你忘了建库。MySQL 的CREATE DATABASE不会因为你在 Java 代码里写了一句jdbc:mysql://localhost:3306/restaurant就自动创建。解决先手动执行第三章里的建表脚本确保数据库和表都真实存在。报错三中文乱码。界面里显示的中文正常但存进 MySQL 后变成???。这通常是三处编码不一致MySQL 连接字符串的characterEncoding、表字段的字符集、IDE 的默认编码。确认三处都是utf8或utf8mb4注意表字段单独设utf8而连接用utf8mb4才会出问题。我一般建表时统一用DEFAULT CHARSETutf8mb4连接字符串也用utf8mb4从根上消除编码错位。报错四Public Key Retrieval is not allowed。MySQL 8.0 使用caching_sha2_password认证时客户端第一次连接需要向服务器请求公钥。解决连接字符串加allowPublicKeyRetrievaltrueuseSSLfalse。这属于 MySQL 8.0.34 版本之后的常见问题我用 8.0 跑课设时遇到的第一道坎就是它。报错五Communications link failure。现象是程序跑着跑着突然数据库访问失败。原因可能是 MySQL 服务停了或者连接空闲太久被服务端断开。开发阶段最简单的排查方法打开命令行执行net start mysql确认服务在运行如果是空闲断开代码里查一下wait_timeout变量的值默认 8 小时但连接池里的连接可能以为它还活着实际早已被数据库端杀掉。6.3 一个加分小技巧全局异常捕获双击运行不再是黑屏JAR 包在别人电脑上双击运行时如果抛异常用户只看到一个一闪而过的黑窗或者毫无反应根本不知道发生了什么。加一个全局异常处理器把错误信息写入日志文件是让交付物更专业的细节。Java 的Thread.setDefaultUncaughtExceptionHandler可以拦截所有未捕获的异常Thread.setDefaultUncaughtExceptionHandler((thread, throwable) - { try { FileWriter writer new FileWriter(error.log, true); writer.write(时间: LocalDateTime.now() \n); writer.write(异常: throwable.toString() \n); StackTraceElement[] stack throwable.getStackTrace(); for (StackTraceElement element : stack) { writer.write(\t element.toString() \n); } writer.close(); } catch (IOException e) { e.printStackTrace(); } JOptionPane.showMessageDialog(null, 程序遇到错误详情请查看 error.log 文件); });这段代码放在main()方法里、启动界面的第一时间执行。加上之后用户双击运行即使出错也至少能弹出一个提示框日志文件会记录完整的堆栈信息。我自己做项目交付时单位领导双击 JAR 包看到无反应时心情都不太好加了异常捕获之后至少能定位问题不用靠猜。6.4 数据备份别让 MySQL 数据跟着重装系统一起消失开发过程中有件事现在不做答辩前一周必然会后悔——备份数据库。每次改完表结构、加完测试数据顺手执行一次导出成本只有几秒钟mysqldump -u root -p restaurant restaurant_backup.sql需要还原时执行mysql -u root -p -e source restaurant_backup.sql这个命令可以手动跑也可以写成一个.bat脚本放在项目根目录双击就能生成带日期后缀的备份文件。如果你用 Windows 自带的任务计划程序甚至可以做到每天自动备份。这个习惯我是在一次手滑执行了DROP DATABASE之后形成的从那以后我所有带数据库的项目都会顺手做备份——数据没了代码写得再好也只是个空壳。这套系统的完整链路到这里就闭环了从数据库建模到 DAO 层封装从 Swing 界面绑定到事务处理最后打包成可执行文件 —— 每一步踩过的坑基本都是我在实际带项目时遇到过的希望帮到你。照这个思路做下来不管答辩还是实际部署心里都更有底。本文还有配套的精品资源点击获取
返回列表