ARTICLE DETAIL

资讯详情

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

Java Swing+MySQL点餐系统实战:从建库到下单完整教程

Java Swing+MySQL点餐系统实战:从建库到下单完整教程 在 Java 课程设计与期末项目中“点餐系统”几乎是出现频率最高的题目之一。但很多同学在网上找源码时会碰到各种问题要么下载的工程解压后缺少数据库脚本要么代码风格混乱看不懂要么 JDBC 驱动配置好几天都跑不起来。本文基于 Java Swing MySQL JDBC 实现一个最小可用版点餐系统包含数据库建表脚本、工具类、DAO 层、界面代码和运行步骤。哪怕是刚学完 Java 基础的同学也可以照着这份教程一步步把项目跑起来并改造成自己的课程设计作品。本文技术栈定位为传统 Java SE 桌面程序不依赖 Spring、Maven 之外的重型框架。这样做的好处是逻辑足够直观适合讲解 Java 面向对象分层、JDBC 操作、SQL 设计、Swing 事件处理等核心知识点。为了还原课程设计的真实场景我还会给出配套的数据库设计、常见报错排查和扩展方向方便你继续完善。1. 点餐系统到底要做什么1.1 业务场景与功能拆解一个点餐系统的核心业务并不复杂顾客进入餐厅后服务员选择桌号查看菜品列表挑选菜品并填写数量提交订单后在后厨或前台看到订单信息。课程设计版本的“点餐系统”通常只需要围绕这条链路实现以下模块登录验证店长或服务员登录后进入主界面。菜品管理展示菜品 ID、名称、价格、分类。点餐操作选中菜品输入数量加入购物车。订单提交填写桌号将购物车中的菜品写入订单主表和订单明细表。订单基础信息记录总金额、下单时间、订单状态。你不需要一开始就设计复杂的权限系统、库存管理、会员积分、报表统计这些都属于“加分项”。先跑通主流程再逐步扩展是课程设计最稳妥的做法。1.2 为什么选择 Swing MySQL 作为课程设计首选很多同学会纠结到底用纯 Java 控制台还是 Java Swing还是 Spring Boot 做网页从课程设计评分角度看纯控制台程序虽然也能展示业务逻辑但缺少“系统界面”视觉效果一般。Spring Boot 项目则涉及 Tomcat、Maven 依赖、前端页面等大量配置对刚学完 Java SE 的同学来说学习成本偏高。相比之下Java Swing 具有明显优势使用 JDK 自带组件不需要额外引入前端框架。界面是标准窗口程序符合课程设计“有界面、有交互”的要求。配合 MySQL 和 JDBC能清晰展示“界面层 - 业务层 - 数据访问层”的分层思想。因此本文最终选用 Java Swing MySQL JDBC 作为实现方案。2. 环境准备与版本说明2.1 开发环境清单下面的版本是笔者实际使用的参考版本你的环境不一定要完全一致但需要注意对应关系。软件版本建议作用JDK8 或 11Java 编译与运行环境MySQL5.7 或 8.0存储菜单、订单、管理员数据IDEIntelliJ IDEA 或 Eclipse编写和运行代码MySQL 客户端Navicat、DataGrip 或命令行执行 SQL 脚本、查看数据Maven3.6管理项目依赖这里主要用来引入 MySQL 驱动如果只想手工导入 jar 包不使用 Maven 也可以在项目里新建lib目录放入mysql-connector-java-8.0.x.jar然后在 IDE 中添加到项目依赖即可。为了讲解方便下文统一使用 Maven 工程结构。2.2 版本与驱动注意事项MySQL 8.0 的 JDBC 驱动类名是com.mysql.cj.jdbc.Driver连接 URL 需要额外指定时区和编码。MySQL 5.7 通常也可以使用这个驱动但如果你的驱动版本较低类名可能是com.mysql.jdbc.Driver。一旦运行时抛出ClassNotFoundException优先检查驱动类名是否写错。另外数据库连接信息不要硬编码在每一个类中。建议把 URL、用户名、密码放到db.properties配置文件再写一个工具类统一读取这样换数据库环境时只需要修改配置文件。3. 数据库设计建库、建表与初始化数据3.1 表结构设计思路点餐系统的表不能只有一张“订单表”。如果用户一次点了多个菜把菜名和数量都塞进一个字段里后续统计销量会非常痛苦。因此推荐使用“订单主表 订单明细表”的方案admin管理员表保存登录账号。menu菜品表保存菜品信息。orders订单主表保存桌号、总金额、状态、下单时间。order_item订单明细表保存每个菜的菜名、单价、数量、小计。orders与order_item是一对多关系一个订单对应多条明细。这样设计以后你可以自由查询“某一天的所有订单”“某道菜卖了多少份”“某个桌号累计消费金额”。3.2 完整 SQL 脚本在 MySQL 中新建数据库推荐使用utf8mb4字符集避免中文乱码。以下脚本可以直接复制到 Navicat 或命令行中执行CREATE DATABASE IF NOT EXISTS ordering_system DEFAULT CHARACTER SET utf8mb4; USE ordering_system; -- 管理员表 CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, real_name VARCHAR(50) ); INSERT INTO admin(username, password, real_name) VALUES (admin, 123456, 店长); -- 菜品表 CREATE TABLE menu ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, type VARCHAR(50), status TINYINT DEFAULT 1 COMMENT 1上架 0下架 ); INSERT INTO menu(name, price, type, status) VALUES (鱼香肉丝, 28.00, 热菜, 1), (宫保鸡丁, 26.00, 热菜, 1), (番茄炒蛋, 18.00, 素菜, 1), (米饭, 2.00, 主食, 1), (可口可乐, 5.00, 饮品, 1); -- 订单主表 CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, table_no VARCHAR(20) NOT NULL COMMENT 桌号, total_amount DECIMAL(10,2) NOT NULL COMMENT 总金额, status VARCHAR(20) DEFAULT 待支付, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 订单明细表 CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, menu_id INT NOT NULL, dish_name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, quantity INT NOT NULL, subtotal DECIMAL(10,2) NOT NULL, CONSTRAINT fk_order FOREIGN KEY(order_id) REFERENCES orders(id), CONSTRAINT fk_menu FOREIGN KEY(menu_id) REFERENCES menu(id) );这里将订单状态默认值设为“待支付”后续可以扩展支付模块将状态改为“已支付”“已出餐”“已取消”等。菜品表增加status字段是为了实现“下架菜品不展示”避免直接从数据库中删除菜品导致历史订单外键失效。4. 创建项目结构与基础代码4.1 Maven 工程结构在 IDEA 中新建一个 Maven 项目groupId可以写成com.exampleartifactId可以写成ordering-system。最终目录结构如下ordering-system/ ├── pom.xml └── src/main ├── java/com/example/order/ │ ├── Main.java │ ├── dao/ │ │ ├── AdminDao.java │ │ ├── MenuDao.java │ │ └── OrderDao.java │ ├── model/ │ │ ├── Menu.java │ │ └── OrderItem.java │ ├── util/ │ │ └── DBUtil.java │ └── view/ │ └── MainFrame.java └── resources/ └── db.properties4.2 pom.xml 配置在pom.xml中引入 MySQL 驱动依赖。注意这里使用版本 8.0.30 作为示例如果你的 MySQL 是 5.7建议改用 5.1.49 或保持兼容的高版本驱动。?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdordering-system/artifactId version1.0-SNAPSHOT/version packagingjar/packaging properties maven.compiler.source8/maven.compiler.source maven.compiler.target8/maven.compiler.target /properties dependencies dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.30/version /dependency /dependencies /project这里没有配置 Spring Boot 插件因为 Swing 程序用不到内嵌 Tomcat。运行方式就是在 IDE 中直接运行Main类。4.3 数据库配置文件在src/main/resources目录创建db.propertiesjdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/ordering_system?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 jdbc.usernameroot jdbc.password123456注意jdbc.password的值要改成你自己本机 MySQL 的密码。serverTimezoneAsia/Shanghai的作用是解决 MySQL 8.0 连接时的时区报错。4.4 JDBC 工具类DBUtil负责加载驱动、读取配置、提供数据库连接对象。使用Class.forName注册驱动的方式在 JDBC 4.0 之后其实可以省略但保留会更加清晰也能兼容不同版本的驱动包。package com.example.order.util; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; public class DBUtil { private static String url; private static String user; private static String password; static { try (InputStream in DBUtil.class.getClassLoader().getResourceAsStream(db.properties)) { Properties props new Properties(); props.load(in); Class.forName(props.getProperty(jdbc.driver)); url props.getProperty(jdbc.url); user props.getProperty(jdbc.username); password props.getProperty(jdbc.password); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } }getResourceAsStream(db.properties)的作用是从 classpath 根目录加载资源文件。只要db.properties位于resources目录下IDEA 编译后就会自动把它复制到 classes 根目录运行时就能找到。5. 实体类与数据访问层5.1 菜品实体类 Menu实体类的作用是封装表中的一行数据让 Java 代码操作数据库时不再依赖二维数组或ResultSet的散乱字段。Menu类对应menu表包含 id、名称、价格、分类、状态字段。价格使用BigDecimal而不是double是为了避免金额计算出现精度丢失。package com.example.order.model; import java.math.BigDecimal; public class Menu { private int id; private String name; private BigDecimal price; private String type; private int status; public Menu() { } public Menu(int id, String name, BigDecimal price, String type, int status) { this.id id; this.name name; this.price price; this.type type; this.status status; } public int getId() { return id; } public void setId(int id) { this.id id; } public String getName() { return name; } public void setName(String name) { this.name name; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price price; } public String getType() { return type; } public void setType(String type) { this.type type; } public int getStatus() { return status; } public void setStatus(int status) { this.status status; } }5.2 订单明细实体类 OrderItemOrderItem并不直接对应用户输入而是购物车中的一行数据。它包含菜品 ID、菜名、单价、数量和小计金额。构造时可以直接根据单价 * 数量计算小计。package com.example.order.model; import java.math.BigDecimal; public class OrderItem { private int menuId; private String dishName; private BigDecimal price; private int quantity; private BigDecimal subtotal; public OrderItem() { } public OrderItem(int menuId, String dishName, BigDecimal price, int quantity) { this.menuId menuId; this.dishName dishName; this.price price; this.quantity quantity; this.subtotal price.multiply(BigDecimal.valueOf(quantity)); } public int getMenuId() { return menuId; } public void setMenuId(int menuId) { this.menuId menuId; } public String getDishName() { return dishName; } public void setDishName(String dishName) { this.dishName dishName; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price price; } public int getQuantity() { return quantity; } public void setQuantity(int quantity) { this.quantity quantity; } public BigDecimal getSubtotal() { return subtotal; } public void setSubtotal(BigDecimal subtotal) { this.subtotal subtotal; } }5.3 管理员登录验证 AdminDao数据访问层统一负责 SQL 的拼装与执行。这里的登录验证只是课程设计的演示写法密码使用明文存储。正式项目必须对密码进行哈希加密不能直接使用password ?比较明文更不能用Statement拼接字符串。package com.example.order.dao; import com.example.order.util.DBUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class AdminDao { public boolean checkLogin(String username, String password) { String sql SELECT COUNT(*) FROM admin WHERE username ? AND password ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { return rs.getInt(1) 0; } } } catch (SQLException e) { e.printStackTrace(); } return false; } }关于try-with-resourcesConnection、PreparedStatement、ResultSet都实现了AutoCloseable接口当 try 代码块结束时它们会自动关闭。这样能有效避免数据库连接泄漏。5.4 菜品查询 MenuDaoMenuDao只负责处理menu表查询所有status 1的菜品把ResultSet中的数据转换成Menu对象列表。界面层调用findAll()即可得到展示数据。package com.example.order.dao; import com.example.order.model.Menu; import com.example.order.util.DBUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class MenuDao { public ListMenu findAll() { String sql SELECT id, name, price, type, status FROM menu WHERE status 1; ListMenu list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Menu menu new Menu(); menu.setId(rs.getInt(id)); menu.setName(rs.getString(name)); menu.setPrice(rs.getBigDecimal(price)); menu.setType(rs.getString(type)); menu.setStatus(rs.getInt(status)); list.add(menu); } } catch (SQLException e) { e.printStackTrace(); } return list; } }5.5 订单提交 OrderDaocreateOrder是整个项目中最核心的方法。它需要完成两件事插入一条orders主表记录然后插入多条order_item明细记录。为了保证“要么全部成功要么全部失败”必须使用事务。步骤如下设置conn.setAutoCommit(false)开始事务。计算订单总金额。插入订单主表通过RETURN_GENERATED_KEYS获取自增主键。使用批量addBatch()插入所有明细。提交事务返回订单号。如果发生异常回滚事务。package com.example.order.dao; import com.example.order.model.OrderItem; import com.example.order.util.DBUtil; import java.math.BigDecimal; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.List; public class OrderDao { public int createOrder(String tableNo, ListOrderItem items) throws SQLException { String insertOrder INSERT INTO orders(table_no, total_amount, status) VALUES (?, ?, 待支付); String insertItem INSERT INTO order_item(order_id, menu_id, dish_name, price, quantity, subtotal) VALUES (?, ?, ?, ?, ?, ?); Connection conn null; PreparedStatement psOrder null; PreparedStatement psItem null; ResultSet rs null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); BigDecimal total BigDecimal.ZERO; for (OrderItem item : items) { total total.add(item.getSubtotal()); } psOrder conn.prepareStatement(insertOrder, Statement.RETURN_GENERATED_KEYS); psOrder.setString(1, tableNo); psOrder.setBigDecimal(2, total); psOrder.executeUpdate(); rs psOrder.getGeneratedKeys(); int orderId 0; if (rs.next()) { orderId rs.getInt(1); } psItem conn.prepareStatement(insertItem); for (OrderItem item : items) { psItem.setInt(1, orderId); psItem.setInt(2, item.getMenuId()); psItem.setString(3, item.getDishName()); psItem.setBigDecimal(4, item.getPrice()); psItem.setInt(5, item.getQuantity()); psItem.setBigDecimal(6, item.getSubtotal()); psItem.addBatch(); } psItem.executeBatch(); conn.commit(); return orderId; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } throw e; } finally { if (rs ! null) { try { rs.close(); } catch (SQLException ignored) { } } if (psItem ! null) { try { psItem.close(); } catch (SQLException ignored) { } } if (psOrder ! null) { try { psOrder.close(); } catch (SQLException ignored) { } } if (conn ! null) { try { conn.close(); } catch (SQLException ignored) { } } } } }这里使用addBatch()和executeBatch()批量执行多条明细插入比逐条插入性能更高也减少了程序与数据库之间的交互次数。6. 登录与主界面实现6.1 简易登录判断在Main类中使用JOptionPane弹窗接收用户名和密码调用AdminDao校验。这是最简化的登录方式。如果你的课程设计要求更正式可以把这段逻辑改成独立的LoginFrame窗口使用JPasswordField输入密码。package com.example.order; import com.example.order.dao.AdminDao; import com.example.order.view.MainFrame; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; public class Main { public static void main(String[] args) { String username JOptionPane.showInputDialog(用户名:); if (username null) { return; } String password JOptionPane.showInputDialog(密码:); if (password null) { return; } AdminDao adminDao new AdminDao(); if (!adminDao.checkLogin(username, password)) { JOptionPane.showMessageDialog(null, 用户名或密码错误); return; } SwingUtilities.invokeLater(new Runnable() { Override public void run() { new MainFrame().setVisible(true); } }); } }6.2 主窗口界面MainFrame是整个系统的核心界面。它使用JSplitPane将窗口分为左右两部分左侧是菜品表JTable展示从数据库读取到的菜品。右侧是购物车表展示用户已经添加的菜品、数量和小计。顶部是操作栏包含桌号输入框、数量输入框、“加入购物车”“提交订单”“清空购物车”按钮。package com.example.order.view; import com.example.order.dao.MenuDao; import com.example.order.dao.OrderDao; import com.example.order.model.Menu; import com.example.order.model.OrderItem; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.table.DefaultTableModel; import java.awt.BorderLayout; import java.math.BigDecimal; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class MainFrame extends JFrame { private JTable menuTable; private DefaultTableModel menuModel; private JTable cartTable; private DefaultTableModel cartModel; private JTextField tableNoField; private JTextField countField; private ListMenu menuList; private ListOrderItem cartItems; public MainFrame() { setTitle(Java点餐系统); setSize(900, 600); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); init(); } private void init() { menuList new MenuDao().findAll(); cartItems new ArrayList(); menuModel new DefaultTableModel(new Object[]{ID, 名称, 价格, 分类}, 0); menuTable new JTable(menuModel); for (Menu menu : menuList) { menuModel.addRow(new Object[]{ menu.getId(), menu.getName(), menu.getPrice(), menu.getType() }); } cartModel new DefaultTableModel(new Object[]{菜名, 单价, 数量, 小计}, 0); cartTable new JTable(cartModel); tableNoField new JTextField(8); countField new JTextField(5); JButton addButton new JButton(加入购物车); JButton orderButton new JButton(提交订单); JButton clearButton new JButton(清空购物车); addButton.addActionListener(e - addToCart()); orderButton.addActionListener(e - submitOrder()); clearButton.addActionListener(e - clearCart()); JPanel topPanel new JPanel(); topPanel.add(new JLabel(桌号:)); topPanel.add(tableNoField); topPanel.add(new JLabel(数量:)); topPanel.add(countField); topPanel.add(addButton); topPanel.add(orderButton); topPanel.add(clearButton); JSplitPane splitPane new JSplitPane( JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(menuTable), new JScrollPane(cartTable) ); splitPane.setDividerLocation(450); setLayout(new BorderLayout()); add(topPanel, BorderLayout.NORTH); add(splitPane, BorderLayout.CENTER); } private void addToCart() { int row menuTable.getSelectedRow(); if (row 0) { JOptionPane.showMessageDialog(this, 请先选择菜品); return; } int quantity; try { quantity Integer.parseInt(countField.getText().trim()); } catch (NumberFormatException e) { JOptionPane.showMessageDialog(this, 数量必须是整数); return; } if (quantity 0) { JOptionPane.showMessageDialog(this, 数量必须大于0); return; } Menu menu menuList.get(row); OrderItem item new OrderItem(menu.getId(), menu.getName(), menu.getPrice(), quantity); cartItems.add(item); cartModel.addRow(new Object[]{ item.getDishName(), item.getPrice(), item.getQuantity(), item.getSubtotal() }); countField.setText(); } private void submitOrder() { String tableNo tableNoField.getText().trim(); if (tableNo.isEmpty()) { JOptionPane.showMessageDialog(this, 请输入桌号); return; } if (cartItems.isEmpty()) { JOptionPane.showMessageDialog(this, 购物车为空); return; } OrderDao orderDao new OrderDao(); try { int orderId orderDao.createOrder(tableNo, cartItems); JOptionPane.showMessageDialog(this, 下单成功订单号: orderId); clearCart(); } catch (SQLException e) { JOptionPane.showMessageDialog(this, 下单失败: e.getMessage()); } } private void clearCart() { cartItems.clear(); cartModel.setRowCount(0); } }关于JTable的使用DefaultTableModel的一个构造方法是new DefaultTableModel(Object[] columnNames, int rowCount)这里 rowCount 传入 0 是让表格初始没有数据。之后每加入一条数据就调用addRow添加一行。界面层不直接操作数据库所有数据都通过MenuDao和OrderDao获取这样分层更清晰。7. 运行与验证7.1 运行步骤按照以下步骤操作启动本地 MySQL 服务执行第 3 节中的 SQL 脚本。修改db.properties中的账号和密码。在 IDEA 中刷新 Maven让mysql-connector-java依赖下载完成。运行Main类。弹出输入框后输入用户名admin、密码123456。登录成功后在左侧表格中选择一个菜品输入数量点击“加入购物车”。输入桌号例如A01点击“提交订单”。7.2 预期效果程序运行后数据库orders表会出现一条记录idtable_nototal_amountstatuscreate_time1A0130.00待支付当前时间order_item表会出现对应的明细记录。比如点了“鱼香肉丝”一份和“米饭”一份总金额就是 30.00。如果某一次提交失败事务会回滚数据库中不会出现只有主表没有明细表的“脏数据”。你可以在 Navicat 中执行下面的查询验证结果SELECT o.id AS 订单号, o.table_no AS 桌号, o.total_amount AS 总金额, o.status AS 状态, oi.dish_name AS 菜名, oi.quantity AS 数量, oi.subtotal AS 小计 FROM orders o LEFT JOIN order_item oi ON o.id oi.order_id ORDER BY o.id DESC;如果查询结果正常说明点餐流程已经完整打通。8. 常见问题与排查思路8.1 问题现象清单问题现象常见原因解决思路启动后弹窗报ClassNotFoundExceptionJDBC 驱动未引入或驱动类名写错检查 Maven 依赖确认com.mysql.cj.jdbc.Driver是否正确连接数据库超时或Access denied数据库未启动或用户名密码错误检查 MySQL 服务核对db.properties中的密码中文数据显示为?或乱码数据库字符集不是 utf8mb4或连接 URL 缺少编码参数建库时指定 utf8mb4连接 URL 增加characterEncodingutf8下单时提示Column order_id cannot be null获取自增主键失败检查Statement.RETURN_GENERATED_KEYS和getGeneratedKeys()的调用方式运行Main类无法加载资源文件db.properties不在 classpath 下确认文件在src/main/resources目录IDEA 会自动编译输出JTable选中行后数据错乱数据排序后仍用原下标获取实体尽量避免 JTable 拖拽排序或根据选中行的 ID 重新查找8.2 如何快速定位问题遇到报错不要只看弹窗信息要先看控制台完整堆栈。常见排查顺序是确认 MySQL 服务是否启动。用 Navicat 或命令行测试账号密码是否能连上指定数据库。确认 SQL 脚本是否完整执行表是否真的存在。确认 Maven 依赖是否下载成功。在DBUtil中打印连接 URL 和用户名检查配置是否被正确读取。如果是中文乱码优先检查连接 URL 中的characterEncodingutf8与数据库字符集是否一致。很多时候问题并不是代码写错而是环境配置不一致。9. 最佳实践与扩展建议9.1 代码层面的工程建议不要因为这是一个课程设计就忽略基础规范。下面几条建议能显著提高代码质量使用PreparedStatement而不是Statement避免 SQL 注入。数据库操作统一放在 DAO 层界面层不要出现 SQL 语句。涉及多表写入时必须使用事务并捕获异常后回滚。数据库密码不要硬编码在 Java 类中而是放到配置文件。密码不能明文存储。课程设计中演示可以图省事但文档和答辩时可以说明“正式系统建议使用 BCrypt 或 MD5 加盐保存”。关闭资源时使用try-with-resources减少代码噪音避免连接泄漏。金额字段使用BigDecimal不要使用double否则会出现精度误差。项目代码尽量添加注释尤其是事务、自增主键、批量插入等关键逻辑。9.2 论文与答辩材料的组织思路如果这个项目需要配套论文和 PPT可以从以下角度整理需求分析描述餐厅点餐流程。系统设计实体关系图、功能模块图、数据库结构。系统实现分层结构图、关键代码讲解。系统测试登录测试、点餐测试、订单入库测试。在答辩时重点说明你为什么会拆成orders和order_item两张表以及为什么下单要用事务。这两点足以体现你对数据库设计和后端逻辑的理解。9.3 后续扩展方向当你完成这个基础版本之后还可以继续扩展把 Swing 界面改为 Spring Boot Vue 的前后端分离方案实现网页端点餐。引入 MyBatis 或 MyBatis-Plus 管理数据库操作减少手写 JDBC 代码。增加菜品图片上传、销量统计、库存扣减等功能。使用 Redis 缓存菜品列表减少数据库查询压力。增加订单状态流转让后厨端可以看到新订单。从课程设计角度你不需要一开始就把所有功能做完。先把核心链路跑通再选择一个方向深挖会让项目更有亮点。最后回到最初的问题如何在最短时间内完成一个 Java 点餐系统答案不是直接复制一整段大项目而是先准备好数据库再写工具类接着写 DAO最后做界面。这种“数据先行、分层递进”的路径不仅适合点餐系统也适合其他 Java 课程设计项目。希望这份从建库到下单的完整流程能帮你顺利跑通第一个 Java 项目。
返回列表