首页/新闻资讯/正文详情

Java+MySQL+JDBC+Swing超市管理系统课程设计完整实现指南

发布时间:2026/9/25 11:37:35 来源:云帆数科 栏目:资讯中心
Java+MySQL+JDBC+Swing超市管理系统课程设计完整实现指南
简介面向高校数据库课程设计与Java初学者的超市管理系统源码包基于JavaMySQLJDBCJavaSwing分层实现覆盖商品管理、收银结算、库存维护等典型业务场景既可直接用于期末课程设计参考也适合作为学习JDBC与Swing综合开发的练习项目。压缩包共24个文件包含17个Java源文件、1个SQL建库脚本、2个XML配置、2个JAR依赖包及说明文档整体仅1.1MB体量精简易控各类文件按源码、数据库脚本、依赖库等分区存放便于定位。开发环境为JDK12与MySQL8.0代码采用GBK编码并引入FlatLaf主题包美化窗口外观配合附带的UI统一示例代码可减少界面样式调整工作量。该资源已有217人学习源码目录结构完整SQL脚本可直接导入MySQL能直观看到JDBC连接、预编译语句、结果集处理、Swing事件监听等关键代码的完整写法同时附带README说明与依赖清单方便快速启动项目并可在此基础上继续扩展会员管理、进货统计等模块满足课程设计报告和答辩演示需要。1. 超市管理系统课程设计为什么 Java MySQL JDBC Swing 至今还是经典组合期末数据库课程设计选什么题目很多人第一反应是“找个新框架炫技”。但实际上每年 12 月到次年 1 月的课程设计季做超市管理系统、学生管理系统、图书管理系统的同学大部分用的还是 Java 基础 MySQL JDBC JavaSwing 这套组合。原因很简单课程设计的评分标准通常看数据库设计是否合理、SQL 是否熟练、JDBC 是否能跑通增删改查、界面能否把数据操作展示出来——这四件事恰好是这套技术栈最擅长的。框架不是加分项把数据库链路完整跑通才是。这套方案的真实定位是“数据库课设里的全链路最小可运行系统”Java 负责业务逻辑和事件处理MySQL 存数据JDBC 负责 Java 和 MySQL 之间的通信JavaSwing 提供本地桌面界面。它不涉及分布式、不涉及微服务、不涉及 Web 容器但恰恰因为层次清晰评审老师一眼就能看清你的数据库操作有没有问题。本文会按“数据库设计 → JDBC 连接 → Swing 界面 → 完整编码 → 避坑”的顺序把整套系统拆开讲清楚保证新手能照做、熟手能直接拿走常用参数和踩坑经验。适合谁读正在做课程设计、需要交源码和数据库文件的大三/大四学生以及想快速把 Java MySQL 链路重新过一遍的开发者。如果你想要的是一个能演示、能答辩、能改参数的完整成品方案这篇就是为你写的。2. 数据库设计先行超市管理系统的表结构、外键关系与初始化数据2.1 表结构设计从商品、库存到订单最少需要几张表一个能撑起课程设计答辩的超市管理系统最少需要四张核心表商品表、商品类别表、供应商表、销售订单表再加一张销售明细表。这是典型的“主数据 业务数据”结构。商品表存商品本身的信息类别表和供应商表用来消除冗余销售订单表和销售明细表构成一对多的主从关系。商品表的设计建议包含以下字段商品ID、商品名称、类别ID外键、供应商ID外键、进价、售价、库存数量、库存下限、入库时间。这里有几个容易在答辩被问到的设计决策进价和售价分开存是为了后续做毛利统计库存下限是为了触发补货提醒入库时间用 TIMESTAMP 类型方便做时间范围查询。CREATE TABLE category ( category_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 类别ID, category_name VARCHAR(50) NOT NULL UNIQUE COMMENT 类别名称 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里是先建类别表因为商品表要引用它。使用 utf8mb4 而不是 utf8是为了支持中文商品名中的生僻字和特殊符号MySQL 8.0 默认字符集也是 utf8mb4。AUTO_INCREMENT 让主键自增避免写插入语句时手动维护 ID。CREATE TABLE supplier ( supplier_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 供应商ID, supplier_name VARCHAR(100) NOT NULL COMMENT 供应商名称, contact_phone VARCHAR(20) COMMENT 联系电话, address VARCHAR(200) COMMENT 地址 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;CREATE TABLE product ( product_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 商品ID, product_name VARCHAR(100) NOT NULL COMMENT 商品名称, category_id INT NOT NULL COMMENT 类别ID, supplier_id INT NOT NULL COMMENT 供应商ID, purchase_price DECIMAL(10,2) NOT NULL COMMENT 进价, sale_price DECIMAL(10,2) NOT NULL COMMENT 售价, stock INT NOT NULL DEFAULT 0 COMMENT 库存数量, stock_min INT NOT NULL DEFAULT 10 COMMENT 库存下限, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 入库时间, CONSTRAINT fk_prod_category FOREIGN KEY (category_id) REFERENCES category(category_id), CONSTRAINT fk_prod_supplier FOREIGN KEY (supplier_id) REFERENCES supplier(supplier_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;商品表里 DECIMAL(10,2) 是金额字段的标准选择不要用 DOUBLE因为 DOUBLE 在计算金额时会产生精度误差这点在答辩时主动提出来反而是亮点。FOREIGN KEY 约束保证引用完整性插入商品时如果类别ID或供应商ID不存在数据库会直接报错而不是等到查询时才发现脏数据。销售订单表和明细表是典型的“主表 子表”设计。订单表存一次收银动作的整体信息明细表存这次收银里的每一条商品记录。两张表用订单ID关联形成一对多关系。CREATE TABLE sale_order ( order_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 订单ID, order_code VARCHAR(32) NOT NULL UNIQUE COMMENT 订单编号, total_amount DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT 总金额, pay_method VARCHAR(20) NOT NULL DEFAULT cash COMMENT 支付方式, sale_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 销售时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE sale_order_detail ( detail_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 明细ID, order_id INT NOT NULL COMMENT 订单ID, product_id INT NOT NULL COMMENT 商品ID, product_name VARCHAR(100) NOT NULL COMMENT 商品名称快照, quantity INT NOT NULL COMMENT 数量, unit_price DECIMAL(10,2) NOT NULL COMMENT 成交单价, subtotal DECIMAL(10,2) NOT NULL COMMENT 小计, CONSTRAINT fk_detail_order FOREIGN KEY (order_id) REFERENCES sale_order(order_id), CONSTRAINT fk_detail_product FOREIGN KEY (product_id) REFERENCES product(product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;2.2 初始化数据为什么商品名称要“快照”以及演示数据怎么灌sale_order_detail 表里存了 product_name这看起来冗余但它是刻意的设计商品名称可能会被修改而历史订单里的商品名称应当保持当时成交时的样子——这叫“快照”。如果没有快照商品改名后历史订单显示的名称也跟着变了这在真实的超市系统中是不可接受的。答辩时能把这个理由说出来数据库设计分不会低。演示数据建议写成一个 init.sql里面有 4~5 个类别、5~6 个供应商、10~15 个商品、3~5 张订单每张订单至少 2 条明细。数据量不用大但要覆盖多对多关系和多表 JOIN 的查询场景。录入商品数据时注意单价要设置小数不要全是整数库存要让部分商品低于库存下限这样“补货提醒”功能才有演示效果。INSERT INTO category (category_name) VALUES (饮料), (零食), (日用品), (生鲜); INSERT INTO supplier (supplier_name, contact_phone) VALUES (本地食品批发, 13800000001), (日用百货销售, 13800000002); INSERT INTO product (product_name, category_id, supplier_id, purchase_price, sale_price, stock, stock_min) VALUES (农夫山泉550ml, 1, 1, 1.20, 2.00, 100, 20), (可口可乐330ml, 1, 1, 1.80, 3.00, 60, 20), (乐事薯片原味, 2, 1, 4.50, 6.50, 15, 20), (海天酱油500ml, 3, 2, 5.00, 8.50, 8, 10);这里把“乐事薯片”的库存设为 15低于库存下限 20“海天酱油”库存设为 8低于下限 10目的就是让补货提醒有数据可查。插入完成后执行一条查询验证外键关系是否正常SELECT p.product_name, c.category_name, s.supplier_name, p.stock FROM product p JOIN category c ON p.category_id c.category_id JOIN supplier s ON p.supplier_id s.supplier_id WHERE p.stock p.stock_min;这条 SQL 是补货提醒功能的核心后面 Swing 界面的“库存预警”按钮复用的就是这段查询。3. JDBC 连接与工具类封装MySQL 连接参数、连接池选择与增删改查模板3.1 mysql-connector-java 驱动版本怎么选URL 参数含义逐个拆JDBC 是 Java 操作数据库的桥梁它本身是一组接口真正的实现是 MySQL 提供的驱动包。课程设计里最常用的是 mysql-connector-javaMaven 坐标如下dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency如果你是手工导入 jar 包不经过 Maven就下载 mysql-connector-j-8.0.33.jar放到项目根目录新建的 lib 文件夹里然后在 IDE 里把 lib 目录加入 Build Path。这里注意一个细节8.0.33 版本中驱动类名是com.mysql.cj.jdbc.Driver而不是旧的com.mysql.jdbc.Driver。用旧类名在 MySQL 8.x 驱动上会直接抛出 ClassNotFoundException。下载官网是 MySQL 官方站点注意选择 Platform Independent 的 zip 包不要下载 Windows 安装版那会装一堆不需要的东西。JDBC URL 是新手翻车率最高的地方。最常见的写法是String url jdbc:mysql://localhost:3306/supermarket?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8allowPublicKeyRetrievaltrue; String user root; String password 123456;逐个参数说清楚jdbc:mysql://是协议头localhost:3306是数据库地址和端口如果 MySQL 装在远程服务器换成对应 IPsupermarket是数据库名必须提前建好useSSLfalse表示不启用 SSL 加密连接本地课程设计场景用不到加密加上它可以避免一堆 SSL 握手警告serverTimezoneAsia/Shanghai是时区参数不加的话 MySQL 8.x 会报时区相关的异常characterEncodingutf8保证中文不乱码allowPublicKeyRetrievaltrue是 MySQL 8.x 在使用 caching_sha2_password 认证插件时需要的参数不加可能报 Public Key Retrieval not allowed。注意使用 SSL 时 MySQL 8.x 的驱动默认可能要求加密如果你在连接里看到sslMode相关的报错就把 URL 改成useSSLfalse试试。useSSL和sslmode本质是同一种东西在不同驱动/协议里的叫法Java 驱动认useSSL而某些第三方工具认sslmode别混用。3.2 DBUtil 工具类封装静态连接、资源释放与 PreparedStatement 防注入课程设计不需要连接池直接用 JDBC 的DriverManager.getConnection就够了。但每次连接都写一遍获取和释放的代码会很乱所以封装一个 DBUtil 工具类。它负责三件事加载驱动、提供连接、安全释放资源。import java.sql.*; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/supermarket?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8allowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL驱动加载失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (stmt ! null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }类的静态代码块在 JVM 加载 DBUtil 时只执行一次完成驱动注册。close方法按 ResultSet → Statement → Connection 的顺序关闭资源这个顺序不能乱。实际开发中还要注意Connection 关闭后它下面的 Statement 和 ResultSet 也会被连带关闭所以先关 ResultSet 再关 Statement 再关 Connection 是标准顺序。查询操作使用 PreparedStatement 而不是 Statement这是必须养成的习惯。PreparedStatement 有两个不可替代的好处一是参数预编译SQL 结构固定执行效率高于拼串二是参数以占位符?传入能防止 SQL 注入。课程设计答辩时老师常问“为什么用 PreparedStatement”回答“防止 SQL 注入、支持预编译”就够了。public ListProduct findByKeyword(String keyword) { ListProduct list new ArrayList(); String sql SELECT * FROM product WHERE product_name LIKE ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Product p new Product(); p.setProductId(rs.getInt(product_id)); p.setProductName(rs.getString(product_name)); p.setSalePrice(rs.getBigDecimal(sale_price)); p.setStock(rs.getInt(stock)); list.add(p); } } } catch (SQLException e) { e.printStackTrace(); } return list; }这里使用了 JDK 7 的 try-with-resources 语法Connection、PreparedStatement、ResultSet 都实现了 AutoCloseable 接口try 块结束后会自动关闭不需要手动在 finally 里调 close。模糊查询的%通配符要拼在参数里而不是拼在 SQL 字符串里这也是 PreparedStatement 的细节。rs.getBigDecimal(sale_price)对应数据库的 DECIMAL 类型避免 DOUBLE 的精度问题。3.3 增删改查四个基础操作事务控制在“订单 明细”中的必要性单表增删改查没什么难度但超市系统的收银功能必须涉及事务。一次收银是“往 sale_order 插一条主记录 往 sale_order_detail 插 N 条明细 更新 product 表的库存”这三件事要么全部成功要么全部失败。如果只插了订单但没有扣库存系统就产生脏数据。这就是事务要解决的问题。public boolean settleOrder(SaleOrder order, ListSaleOrderDetail details) { Connection conn null; PreparedStatement psOrder null; PreparedStatement psDetail null; PreparedStatement psStock null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 String sqlOrder INSERT INTO sale_order (order_code, total_amount, pay_method) VALUES (?, ?, ?); psOrder conn.prepareStatement(sqlOrder, Statement.RETURN_GENERATED_KEYS); psOrder.setString(1, order.getOrderCode()); psOrder.setBigDecimal(2, order.getTotalAmount()); psOrder.setString(3, order.getPayMethod()); psOrder.executeUpdate(); ResultSet keys psOrder.getGeneratedKeys(); int orderId 0; if (keys.next()) orderId keys.getInt(1); String sqlDetail INSERT INTO sale_order_detail (order_id, product_id, product_name, quantity, unit_price, subtotal) VALUES (?, ?, ?, ?, ?, ?); psDetail conn.prepareStatement(sqlDetail); for (SaleOrderDetail d : details) { psDetail.setInt(1, orderId); psDetail.setInt(2, d.getProductId()); psDetail.setString(3, d.getProductName()); psDetail.setInt(4, d.getQuantity()); psDetail.setBigDecimal(5, d.getUnitPrice()); psDetail.setBigDecimal(6, d.getSubtotal()); psDetail.addBatch(); } psDetail.executeBatch(); String sqlStock UPDATE product SET stock stock - ? WHERE product_id ? AND stock ?; psStock conn.prepareStatement(sqlStock); for (SaleOrderDetail d : details) { psStock.setInt(1, d.getQuantity()); psStock.setInt(2, d.getProductId()); psStock.setInt(3, d.getQuantity()); psStock.addBatch(); } int[] stockResult psStock.executeBatch(); for (int r : stockResult) { if (r 0) { conn.rollback(); return false; } } 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 (psOrder ! null) psOrder.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (psDetail ! null) psDetail.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (psStock ! null) psStock.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.setAutoCommit(true); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }这段代码有三个关键点。第一conn.setAutoCommit(false)之后所有 SQL 都在同一个事务里直到commit()或rollback()才结束。第二Statement.RETURN_GENERATED_KEYS配合getGeneratedKeys()拿到自增主键这里拿到的 orderId 要作为明细表的外键。第三库存更新的 WHERE 条件里带AND stock ?这是在数据库层面防止超卖——如果库存不足executeBatch 返回的影响行数是 0就回滚整个事务。注意一个细节conn.setAutoCommit(true)放在 finally 里是为了把连接恢复成默认状态这样连接归还到连接池或下次复用时不会残留事务状态。虽然课程设计不用连接池但养成这个习惯是值得的。4. JavaSwing 界面搭建主窗口布局、表格绑定的两种姿势与事件驱动写法4.1 JFrame 主窗口与功能模块的划分方式JavaSwing 界面不需要做得多炫酷课程设计的评分标准是“功能完整、操作直观、代码结构清晰”。主窗口建议用 JFrame BorderLayout 布局北区放功能按钮栏中央放 JTable 数据展示区南区放状态栏。每个业务模块商品管理、订单管理、库存预警、供应商管理做成独立的 JPanel 页面用 JTabbedPane 承载。这样做的原因是把界面切换与业务逻辑解耦后续改动模块不影响其他部分。import javax.swing.*; public class MainFrame extends JFrame { private JTabbedPane tabPane; private ProductPanel productPanel; private OrderPanel orderPanel; private StockPanel stockPanel; public MainFrame() { setTitle(超市管理系统); setSize(1000, 650); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); tabPane new JTabbedPane(); productPanel new ProductPanel(); orderPanel new OrderPanel(); stockPanel new StockPanel(); tabPane.addTab(商品管理, productPanel); tabPane.addTab(收银台, orderPanel); tabPane.addTab(库存预警, stockPanel); add(tabPane, BorderLayout.CENTER); JLabel statusLabel new JLabel(就绪); add(statusLabel, BorderLayout.SOUTH); setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(MainFrame::new); } }setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)表示点击窗口关闭按钮时直接结束进程setLocationRelativeTo(null)让窗口在屏幕居中。SwingUtilities.invokeLater保证界面线程在事件分发线程中创建这是 Swing 的线程模型要求——不要在 main 线程直接 new JFrame。线程模型这里值得展开说Swing 是单线程模型所有 UI 操作必须在事件分发线程执行。如果你在耗时操作比如数据库查询中直接操作组件界面会卡死。正确的做法是在点击事件里另起线程做数据库查询查询结束后再用SwingUtilities.invokeLater回到 UI 线程更新表格。课程设计数据量很小不卡但答辩时能讲出这个点会让老师觉得你是真懂。4.2 JTable 数据展示TableModel 绑定与刷新机制JTable 本身不存数据它依赖 TableModel。最简单的方案是使用 DefaultTableModel但这种做法的代价是每次刷新都需要重建模型。另一种做法是自定义 TableModel 封装 List实现 M-V-C 分离。课程设计用 DefaultTableModel 足够但要注意刷新逻辑。import javax.swing.*; import javax.swing.table.DefaultTableModel; import java.util.List; public class ProductTableBinder { public static void refreshTable(JTable table, ListProduct products) { String[] columns {商品ID, 商品名称, 类别, 供应商, 进价, 售价, 库存, 库存下限}; Object[][] data new Object[products.size()][columns.length]; for (int i 0; i products.size(); i) { Product p products.get(i); data[i][0] p.getProductId(); data[i][1] p.getProductName(); data[i][2] p.getCategoryName(); data[i][3] p.getSupplierName(); data[i][4] p.getPurchasePrice(); data[i][5] p.getSalePrice(); data[i][6] p.getStock(); data[i][7] p.getStockMin(); } DefaultTableModel model new DefaultTableModel(data, columns) { Override public boolean isCellEditable(int row, int column) { return false; } }; table.setModel(model); } }这里覆写了isCellEditable返回 false这是必须的。如果不覆写JTable 默认双击单元格就能编辑用户直接改表格里的数据显示层数据但数据库不会同步更新这会造成界面与数据库不一致——这是课程设计中典型的“演示翻车”现场。要编辑数据应该通过表单弹窗用 Update 语句提交到数据库后再刷新表格。4.3 商品管理模块整体流程增删改查的界面事件如何写商品管理模块是整个系统的门面它演示了增删改查的完整闭环。界面包含上方一个查询输入框 查询按钮 “添加”按钮中部一个 JTable下方一个表单区商品名称、类别、供应商、进价、售价、库存 保存/删除按钮。整个模块的事件流是点击“添加”→ 弹出输入框 → 填信息 → 点保存 → 调 DAO 的 insert 方法 → 刷新表格 → 提示“添加成功”。public class ProductPanel extends JPanel { private JTable table; private JTextField searchField; private ProductDao productDao; public ProductPanel() { productDao new ProductDao(); setLayout(new BorderLayout(10, 10)); JPanel topBar new JPanel(); searchField new JTextField(15); JButton searchBtn new JButton(查询); JButton addBtn new JButton(添加商品); topBar.add(new JLabel(关键字:)); topBar.add(searchField); topBar.add(searchBtn); topBar.add(addBtn); add(topBar, BorderLayout.NORTH); table new JTable(); add(new JScrollPane(table), BorderLayout.CENTER); refreshData(null); searchBtn.addActionListener(e - refreshData(searchField.getText().trim())); addBtn.addActionListener(e - showAddDialog()); } private void refreshData(String keyword) { ListProduct list (keyword null || keyword.isEmpty()) ? productDao.findAll() : productDao.findByKeyword(keyword); ProductTableBinder.refreshTable(table, list); } private void showAddDialog() { // 弹窗收集商品信息输入校验通过后调 productDao.insert() } }代码里有价值的是设计模式Spring 的 MVC 在 Swing 项目里缩水成“DAO Panel Binder”但职责边界是一样的。JButton 的addActionListener接收一个 lambda 表达式这比匿名内部类简洁。注意searchField.getText().trim()不要直接拼到 SQL 里DAO 层的 PreparedStatement 会处理它。5. 避坑指南JDBC 驱动、中文乱码、窗口卡死与答辩高频追问5.1 现象ClassNotFoundException: com.mysql.jdbc.Driver原因mysql-connector-java 8.x 的驱动类名改成了com.mysql.cj.jdbc.Driver旧代码里写的是com.mysql.jdbc.Driver或者 jar 包没有正确加入 Build Path。解决把代码里的驱动类名改成新类名在 IDE 中确认 lib 目录已加入类路径。排查办法是在项目里搜索 “Driver”确认 import 和 Class.forName 里都出现的是cj。如果用了 Maven先mvn clean再mvn install避免 IDE 缓存导致依赖不生效。5.2 现象MySQL 8.x 连接报错 Public Key Retrieval is not allowed原因MySQL 8.x 默认认证插件是 caching_sha2_password第一次连接时客户端需要向服务器请求公钥但 URL 里没有允许这个行为。解决在 JDBC URL 里追加allowPublicKeyRetrievaltrue。如果你用的是 Navicat 或 DBX 这类数据库管理工具连接达梦或 MySQL 报同样错误去连接配置里找到“允许获取公钥”或类似选项勾上。根因是同一个只是不同工具的配置项叫法不同。5.3 现象JTable 显示中文全是 ?原因JDBC URL 没有设置 characterEncodingutf8或者数据库本身的字符集不是 utf8mb4。解决URL 加上characterEncodingutf8如果还乱码检查建库语句和建表语句的 DDL 是否指定了DEFAULT CHARSETutf8mb4。注意连接参数里的 characterEncoding 只影响当前连接的客户端编码数据库和数据表的字符集要独立设置。三者保持一致才不会出问题。5.4 现象点击按钮后界面卡死鼠标变成转圈原因在事件分发线程里直接执行了耗时操作数据库查询、大文件读写阻塞了 UI 线程导致 Swing 无法重绘控件。这在数据量增大会越来越明显但课程设计数据量小需要刻意制造慢查询才能复现。解决耗时操作用new Thread(() - { ... }).start()放到后台线程如果需要更新 UI在退出线程时用SwingUtilities.invokeLater(() - { ... })回到 UI 线程。注意后台线程里别访问 Swing 组件那属于跨线程访问在极端情况下会有竞态问题。5.5 现象数据库连接关不掉程序退出后 MySQL 进程还存在原因本项目里每次操作都 new 了一个 Connection正常用完要 close。如果代码里有异常抛出时忘了 close连接就不释放。MySQL 的默认 wait_timeout 是 8 小时连接堆积以后数据库会拒绝新连接报 too many connections。解决把 try-with-resources 的写法贯彻到 DAO 层Connection、Statement、ResultSet 都放在 try 括号里。另外在开发阶段MySQL 工作台里执行SHOW PROCESSLIST;可以看到残留的连接KILL id;手动清理。5.6 答辩高频问题准备外键、事务与索引评委大概率会问外键为什么用逻辑外键而不是物理外键事务怎么保证订单一致性商品表数据量大了怎么办。回答口径物理外键保证引用完整性同时也带来写操作开销其实课程设计数据量小物理外键不影响性能但索引在大数据量下很有价值。商品名称模糊查询是高频操作建议给 product_name 加普通索引ALTER TABLE product ADD INDEX idx_product_name (product_name);逆规范化快照字段是怎么取舍的——为了查询性能和保留历史上下文在明细表冗余了商品名称业务上这是刻意的、合理的。6. 进阶技巧把项目从“交了”升级到“能赢”——库存流水表、参数校验与服务化改造如果你只打算交一份能运行的课程设计前五章的内容已经够用了。但如果你希望在答辩时拿到优秀或者想把这份代码作为期末项目背景去投实习还有三个低成本改良方向库存流水账、输入校验集中化、把 DAO 改造成接口。第一个方向库存流水表。现在 inventory 更新是直接SET stock stock - ?这留下了一个问题——只能看到当前库存看不到库存的变动历史。真实超市系统的盘点与对账都需要流水。加一张stock_log表日志ID、商品ID、变动数量、变动后库存、变动类型、变动时间然后在库存更新的事务里插入一条流水。这样一来“什么时间、什么商品、为什么少了库存”都能追溯这个设计在答辩时非常有说服力。// 在 settleOrder 事务内部每次库存更新后追加流水日志 String sqlLog INSERT INTO stock_log (product_id, change_qty, after_stock, change_type) VALUES (?, ?, ?, sale); psLog conn.prepareStatement(sqlLog); // 每次更新库存后查询最新的 stock 并插入流水具体实现可参考 Dao 层第二个方向输入校验集中化。当前示例里商品价格、库存数量的校验散落在事件监听器中这很容易把 Swing 代码写得很臃肿。整理一个 Validator 类静态方法检查“必填、格式、范围”返回一个统一的错误码或提示字符串在 DAO 调用前全部校验完。这样代码的阅读性大幅提升而且你可以在答辩时解释“我从 Controller 层把输入校验与业务逻辑分离了”。第三个方向DAO 接口化。把 ProductDao、OrderDao 改成接口 实现类的结构比如ProductDao定义findAll / findByKeyword / insert / update / deleteProductDaoImpl用 JDBC 实现它。这个改动的意义不仅是“长得像企业项目”而且让你理解了数据库操作的边界调用者依赖接口而非具体实现将来从 MySQL 换到 PostgreSQL 或 SQLite只需新增实现类业务代码不动。这也是职责分离思想的入门门槛。最后说一个我个人的习惯课程设计系统做完整后不要急着打包交掉先去跑一遍“摧毁测试”——把所有输入框都填非法数据、连续点击保存按钮、在库存不足的红线边缘反复下单看它会不会崩溃、会不会产生负数库存、会不会出现重复提示。多修掉几个“看起来能跑”的问题答辩时就少几个尴尬的翻车瞬间。如果你还没动手我建议按这个顺序推进先建数据库和执行 init.sql → 用命令行客户端或数据库工具验证 SQL 与业务查询 → 写 Java 实体类和 DAO 层 → 写 Swing 界面并接线 → 全流程联调。数据库先行能省掉后面大半的无头绪调试。这套方案的另一个现实意义是基础扎实。JDBC 的连接、事务、资源释放逻辑在 MyBatis 和 Spring 里都是被框架封装掉的东西你用原始 API 做过一遍之后再去学数据库连接池、MyBatis 的 SqlSession会有一种“原来是这样”的打通感。希望这篇能把你的课程设计链路顺下来做出一份真正经得起演示和提问的源码——祝顺利。本文还有配套的精品资源点击获取

相关推荐

桌面通信型CRM实战解析:从客户生命周期到通信集成
桌面通信型CRM实战解析:从客户生命周期到通信集成

1. 项目概述:为什么我需要一个“桌面通信”型的CRM做客户管理这行久了,接触过的CRM工具少说也有十来个。从SaaS平台的自带模块,到各种需要二次开发的开源系统,再到企业微信、钉钉工作台里的小应用,我都陪着团队挨个试过… · 2026/9/25 11:37:29

AWS HealthImaging(医学影像)JavaScript SDK v3 代码示例全解析:从 DICOM 导入到图像帧下载
AWS HealthImaging(医学影像)JavaScript SDK v3 代码示例全解析:从 DICOM 导入到图像帧下载

示例工程教程后端 【免费下载链接】aws-doc-sdk-examples Welcome to the AWS Code Examples Repository. This repo contains code examples used in the AWS documentation, AWS SDK Developer Guides, and more. For more information, see the Readme.md file below. 项目地… · 2026/9/25 11:37:29

用 Hypothesis 生成正确的数据:从字段策略到领域对象的完整实战
用 Hypothesis 生成正确的数据:从字段策略到领域对象的完整实战

测试开发工具 【免费下载链接】hypothesis The property-based testing library for Python 项目地址: https://gitcode.com/gh_mirrors/hy/hypothesis 点击查看 免费下载 Hypothesis 是 Python 生态中广受欢迎的属性测试(property-based testing&#… · 2026/9/25 11:37:29

MiniMAX H3三段式SKILL提示词:构建电影级视频生成工作流
MiniMAX H3三段式SKILL提示词:构建电影级视频生成工作流

1. 项目概述:这不是“写提示词”,而是搭建一套可复用的电影级视频生成工作流你搜“MiniMAX H3”“SKILL提示词”“电影级视频生成”,刷到的大多是零散截图、参数堆砌、或者一句“效果炸裂但没说怎么复现”。我去年下半年开始系统性测试MiniMA… · 2026/9/25 12:18:16

CLI驱动的LLM代码审查:基于Git Diff的自动化协作代理
CLI驱动的LLM代码审查:基于Git Diff的自动化协作代理

1. 项目概述:这不是又一个代码审查工具,而是一次开发工作流的底层重定义“open-code-review”这个名称乍看平平无奇,甚至容易被误读为某个开源项目的代号或某个 GitHub 仓库的简单命名。但结合当前高频出现的热搜词——open-code-review、CLI… · 2026/9/25 12:18:16

Atlas 300V 24G是运算加速卡吗?昇腾AI推理卡部署YOLO完整实战
Atlas 300V 24G是运算加速卡吗?昇腾AI推理卡部署YOLO完整实战

前两天刷到一条热搜:「atlas 300v 24g 是运算加速卡吗」。底下吵得挺热闹,有人说这是显卡,有人说这是矿卡,还有人直接说华为的卡跑不了深度学习。作为一个真金白银买过 Atlas 300V 24G、并在上面折腾了大半年 YOLO 部署的工程师&a… · 2026/9/25 12:18:10

Atlas 300V 24G实战:从环境搭建到YOLO推理部署全流程
Atlas 300V 24G实战:从环境搭建到YOLO推理部署全流程

买过Atlas 300V 24G这张卡的朋友,最近在群里问得最多的就是一句:“atlas 300v 24g是运算加速卡吗?”我的回答始终是同一句:是,再准确一点说,它是一张专门做AI推理的加速卡。这半年我用它跑YOLO系列模型&… · 2026/9/25 12:18:10

Modbus TCP温湿度变送器选型避坑指南
Modbus TCP温湿度变送器选型避坑指南

1. 为什么动环监控里温湿度变送器选型总踩坑?先搞清Modbus TCP和以太网不是一回事机房动环监控设备怎么选?这个问题我干了八年,从最早用RS485手拉线、接终端电阻、调波特率调到凌晨三点,到现在坐在办公室看Web界面刷数据&#xff… · 2026/9/25 12:18:04

用ESP32绕过射频直连DMR网络:纯网络终端设计与实现
用ESP32绕过射频直连DMR网络:纯网络终端设计与实现

前两天我把手头的ESP32 DevKitC接上家里的路由器,没装天线、没有PA、没有射频调制器,却在串口日志里看到DMR主站发来了语音帧,耳机里传出清晰的通话声。这个标题里说的“纯网络DMR终端”不是玄学,而是一种非常实际的玩法&#xff… · 2026/9/25 12:17:58

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码