资讯动态

基于Java Swing+JDBC的柑橘水果管理系统课设源码解析

发布时间:2026/10/4 7:20:04 来源:尧图企业网站定制
简介Java课程设计往往需要兼顾功能完整性与代码可读性而Swing与JDBC的组合正是桌面端管理系统开发的经典技术栈。JDBC通过PreparedStatement预编译机制有效防止SQL注入事务控制则保证了进货与销售场景下库存数据的一致性。在水果零售行业中柑橘类水果因品种、产地差异导致进价与售价波动明显信息管理系统能够帮助经营者高效维护库存与流水。本文基于一个完整的柑橘类水果管理系统源码拆解其数据库四表设计、DAO分层实现、界面绑定以及部署避坑要点为初学者提供一套可直接落地的课设参考。1. 基于Java的柑橘类水果管理系统课设级源码的完整落地拆解拿到这份「基于Java语言的柑橘类水果管理系统」源码时我第一反应是去核对它的技术栈和模块边界。柑橘类水果的管理和普通商品管理最大的差异在于品类粒度——砂糖橘、沃柑、脐橙、柚子在库存、售价、进货周期上完全是不同的逻辑如果表结构里只有一个笼统的「水果名称」字段那这个系统做出来就只能是个演示品。这套源码用纯Java JDBC Swing实现数据库用MySQL整个项目从建表到界面交互都是课设标准打法适合正在做Java课程设计、或者想用最短时间跑通一个完整CRUD项目的人。它解决的核心问题很务实水果进货、销售、库存、品类管理这几件事用桌面程序串起来不用搭Web环境不用配Tomcat拿到源码改一下数据库连接就能跑。适合的人群是Java基础刚学完、需要交课设或者想练手的人。2. 环境与地基JDK版本、MySQL建库和四张核心表的设计2.1 技术栈选型为什么是 Swing JDBC 而不是 SSM这套源码没有选择Spring Boot MyBatis的Web方案而是回到Swing JDBC的桌面端路线。很多刚学完Java的人会困惑「现在不都学Spring吗」但从课设复现的角度讲Swing方案有它不可替代的优势没有Maven依赖冲突、没有配置文件爆炸、单文件就能跑通主逻辑。JDBC直连MySQL的写法虽然老派但对理解Connection、PreparedStatement、ResultSet这三个核心接口反而更直接。我拆过不少课设源码凡是SSM的项目八成时间花在解决依赖和配置上真正写业务的时间反而不多。而这套源码的控制层就是事件监听器数据层就是DAO接口加实现类结构一眼能看穿。2.2 数据库设计四张表怎么把柑橘品类拆明白打开源码里的sql目录核心是四张表水果信息表、水果分类表、进货记录表、销售记录表。水果信息表是最关键的它没有用单一的「名称」字段而是拆出了variety品种和origin产地两个字段。这一点做得聪明——同样是橙子赣南脐橙和湖南冰糖橙的进价差了近一倍如果不拆字段后续做利润统计的时候根本没法区分。CREATE TABLE fruit_info ( id INT PRIMARY KEY AUTO_INCREMENT, fruit_name VARCHAR(50) NOT NULL COMMENT 水果名称, variety VARCHAR(50) DEFAULT NULL COMMENT 品种细分, origin VARCHAR(50) DEFAULT NULL COMMENT 产地, stock INT DEFAULT 0 COMMENT 当前库存斤, purchase_price DECIMAL(10,2) DEFAULT 0.00 COMMENT 最近进价, sale_price DECIMAL(10,2) DEFAULT 0.00 COMMENT 当前售价, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里的DECIMAL(10,2)是刻意避开FLOAT的金额字段用浮点类型在累加时会出现精度漂移比如 0.1 0.2 算出 0.30000000000000004这在利润统计里会直接导致账目对不上。utf8mb4字符集则是为了防止生僻品种名或产地名写入时报错。进货表和销售表的结构是对称的都带着fruit_id外键和quantity数量字段。设计上没有加级联删除这是个值得注意的取舍——如果用户删除一个水果关联的进货记录不会跟着删除这样虽然可能产生孤数据但在真实的经营场景里删除操作本来就该被限制历史流水必须保留。2.3 JDBC工具类连接参数与驱动加载项目根目录下的db.properties文件集中管理数据库连接配置这是JDBC项目的标准做法。源码里配套的是MySQL 5.7和8.x通用的驱动类名。jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/citrus_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 jdbc.usernameroot jdbc.password123456工具类DBUtil.java负责加载驱动、获取连接和释放资源。注意它用的是Class.forName加驱动在MySQL 8.x版本下驱动类名是com.mysql.cj.jdbc.Driver如果是MySQL 5.x就要改成com.mysql.jdbc.Driver这是最常见的启动报错来源之一。serverTimezoneAsia/Shanghai这个参数在MySQL 8.x下几乎是必填的否则连接时会直接抛Server returns invalid timezone异常。characterEncodingutf8是为了让PreparedStatement写入的中文不乱码但如果数据库表本身已经是 utf8mb4而连接串只写 utf8在个别MySQL版本上仍可能出现字符集转换告警。2.4 初始化数据预置柑橘品类的插入策略源码里带了一个init_data.sql脚本里面预置了十几条柑橘类水果数据。预置数据的策略值得留意每条记录都明确区分了fruit_name橙子、橘子、柚子、柠檬和variety赣南脐橙、砂糖橘、沃柑、金桔而不是直接把「赣南脐橙」整个塞进名称字段。这种拆分在后续做分类统计、按产地筛选时非常有用。INSERT INTO fruit_info (fruit_name, variety, origin, stock, purchase_price, sale_price) VALUES (橙子, 赣南脐橙, 江西赣州, 120, 3.80, 6.50), (橙子, 冰糖橙, 湖南怀化, 80, 2.90, 5.50), (橘子, 砂糖橘, 广西桂林, 200, 4.20, 7.80);初始化数据插入后如果发现某个字段重复多半是之前手工执行过脚本主键冲突只是在所难免用TRUNCATE TABLE fruit_info清空后重跑即可。不建议用DELETE FROM因为TRUNCATE会重置自增ID让所有数据从1开始界面展示更整洁。3. 分层实现从实体类到DAO再到业务逻辑3.1 实体层Fruit类与ORM字段映射实体类Fruit.java的字段和数据库表是一一对应的这是JDBC项目最简单的ORM映射方式。类的属性类型选型上有一个细节BigDecimal对应数据库的DECIMALjava.util.Date对应数据库的DATETIME。如果用double或Float来承接金额字段在界面显示时就会遇到精度灾难。public class Fruit { private Integer id; private String fruitName; private String variety; private String origin; private Integer stock; private BigDecimal purchasePrice; private BigDecimal salePrice; private Date createTime; // 无参构造器 全参构造器 // getter/setter 省略 }实体类设计贯彻的是「贫血模型」——只有数据没有行为所有操作逻辑都放在Service层。这在课设项目里是合理的选择因为评审老师看重的不是充血模型的架构艺术而是你能不能讲清楚每一层是干什么的。3.2 数据访问层FruitDAO与PreparedStatement的标准写法FruitDAO.java提供了按ID查询、查询全部、条件搜索、新增、修改、删除六个方法。这里没有使用Statement全部走PreparedStatement这两个类的区别在面试里也经常被问到——前者存在SQL注入风险后者通过预编译把参数和SQL语句分离。public ListFruit searchFruits(String keyword) { String sql SELECT id, fruit_name, variety, origin, stock, purchase_price, sale_price, create_time FROM fruit_info WHERE fruit_name LIKE ? OR variety LIKE ? OR origin LIKE ?; ListFruit list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { String likePattern % keyword %; ps.setString(1, likePattern); ps.setString(2, likePattern); ps.setString(3, likePattern); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Fruit fruit new Fruit(); fruit.setId(rs.getInt(id)); fruit.setFruitName(rs.getString(fruit_name)); // 其余字段赋值省略 list.add(fruit); } } } catch (SQLException e) { e.printStackTrace(); } return list; }代码里有几个细节值得注意LIKE参数的拼接用%keyword%是写在传入参数里的而不是SQL模板里这保证了PreparedStatement的参数占位符数量恒定。try-with-resources语法自动关闭Connection、PreparedStatement、ResultSet不用手动写finally块去释放资源。搜索接口的keyword如果传空字符串LIKE %%在MySQL里等价于全表扫描会返回所有数据。这个行为在界面搜索框为空时恰好实现了「显示全部」的效果算是意外收获。3.3 业务层进货和销售时的库存同步逻辑业务层的核心在PurchaseService.java和SaleService.java。进货的逻辑是插入进货记录表 → 更新水果库存 → 刷新界面。销售的逻辑刚好反过来校验库存足够 → 扣减库存 → 插入销售记录。这里最关键的是「先校验后操作」的顺序不能乱。public boolean saleFruit(Integer fruitId, Integer quantity, BigDecimal salePrice) { Connection conn null; PreparedStatement checkStock null; PreparedStatement updateStock null; PreparedStatement insertSale null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 checkStock conn.prepareStatement(SELECT stock, sale_price FROM fruit_info WHERE id ?); checkStock.setInt(1, fruitId); ResultSet rs checkStock.executeQuery(); if (rs.next()) { int currentStock rs.getInt(stock); if (currentStock quantity) { conn.rollback(); return false; // 库存不足回滚 } } updateStock conn.prepareStatement(UPDATE fruit_info SET stock stock - ? WHERE id ?); updateStock.setInt(1, quantity); updateStock.setInt(2, fruitId); updateStock.executeUpdate(); insertSale conn.prepareStatement(INSERT INTO sale_record (fruit_id, quantity, sale_price, sale_time) VALUES (?, ?, ?, NOW())); insertSale.setInt(1, fruitId); insertSale.setInt(2, quantity); insertSale.setBigDecimal(3, salePrice); insertSale.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } return false; } finally { DBUtil.close(conn, checkStock, insertSale, null); } }这段代码展示了事务的最小完整写法setAutoCommit(false)开启、操作成功后commit()、异常时rollback()、最后在finally里统一关闭资源。缺任何一环库存扣了但销售记录没插入或者反过来账目就永远对不上了。从我这边的经验看课设源码里能主动用到事务的其实不多大部分代码就是单条SQL走天下。这套源码在买卖两个核心操作上都做了事务处理答辩时这段代码能拿出来当亮点讲属于「超出及格线」的水平。3.4 控制层监听器与界面刷新的耦合拆解控制层在Swing里就是事件监听器。主窗体的MainFrame.java中「新增水果」按钮的监听器逻辑非常典型它把弹窗输入、数据校验、DAO调用、表格刷新四件事串在一起但互相之间通过方法调用隔离。btnAdd.addActionListener(e - { FruitAddDialog dialog new FruitAddDialog(MainFrame.this); dialog.setVisible(true); if (dialog.isConfirmed()) { Fruit fruit dialog.getFruitData(); if (fruitDAO.addFruit(fruit)) { refreshTable(); // 重新加载表格数据 JOptionPane.showMessageDialog(this, 添加成功); } else { JOptionPane.showMessageDialog(this, 添加失败, 错误, JOptionPane.ERROR_MESSAGE); } } });这个设计的巧妙之处在于对话框只负责收集数据是否符合预期由调用方二次判断而不是对话框自己执行插入操作。这样如果以后要把DAO换掉只需要改MainFrame里的调用不用动对话框的代码。界面刷新的refreshTable()方法是重新执行一次查询然后重建TableModel虽然性能不优但数据一致性绝对可靠不存在局部刷新遗漏某行的问题。4. 界面布局与数据绑定主窗体到搜索功能4.1 主窗体布局JSplitPane JTable的结构主界面的布局采用的是BorderLayout嵌套JSplitPane的做法。顶部是操作工具栏放「新增」「删除」「进货入库」「销售出库」四个按钮左侧是分类筛选的JList右侧是水果信息的JTable。JSplitPane的分隔条可以让用户自由调节左右面板宽度在屏幕分辨率差异比较大的实验室机器上不会出现界面挤压变形的尴尬。4.2 自定义TableModelDefaultTableModel的类型问题表格的数据绑定没有直接用DefaultTableModel而是写了一个继承自AbstractTableModel的FruitTableModel。原因是DefaultTableModel里所有单元格都是Object类型数字列默认右对齐做不到金额列排序也会变成字符串排序比如 6.50 会排在 45.00 的后面。class FruitTableModel extends AbstractTableModel { private String[] columns {编号, 名称, 品种, 产地, 库存(斤), 进价, 售价, 录入时间}; private ListFruit data; Override public int getRowCount() { return data null ? 0 : data.size(); } Override public int getColumnCount() { return columns.length; } Override public Object getValueAt(int rowIndex, int columnIndex) { Fruit fruit data.get(rowIndex); switch (columnIndex) { case 0: return fruit.getId(); case 1: return fruit.getFruitName(); case 4: return fruit.getStock(); case 5: return fruit.getPurchasePrice(); case 6: return fruit.getSalePrice(); default: return ; } } Override public Class? getColumnClass(int columnIndex) { if (columnIndex 4 || columnIndex 5 || columnIndex 6) { return Number.class; // 数字列右对齐 } return String.class; } }重写getColumnClass返回Number.class是关键设计决策。JTable的单元格渲染器会检查这一列的类型如果是Number就默认右对齐这样库存、金额列在视觉上立刻变得整齐规范是桌面端表格的标准观感。4.3 搜索功能SQL拼接与防注入搜索框监听的是KeyAdapter的keyReleased事件也就是每敲一个字符就触发一次搜索不需要按回车。这个体验在数据量几百条时没有性能问题因为SQL只做了LIKE匹配单次查询耗时基本在毫秒级。防注入的措施前面已经提到了用PreparedStatement的参数绑定替代手动拼接字符串。这里补充一个容易被忽略的点LIKE搜索的关键字里如果含有通配符%或_会被MySQL当作通配符处理用户搜「100%纯棉」会把所有数据都带出来。正规做法是用ESCAPE关键字转义但课设项目里通常不处理这个边界知道有这个坑即可。5. 避坑日志课设源码里最常见的五个翻车点5.1 MySQL 8.x驱动类名不匹配现象运行DBUtil时报ClassNotFoundException: com.mysql.jdbc.Driver。原因MySQL 8.x把驱动类改名成com.mysql.cj.jdbc.Driver旧的包名在8.x里默认不兼容。如果pom或lib目录里放的是mysql-connector-java 8.x版本代码里还写老类名必然报错。解决改db.properties里的驱动类名为com.mysql.cj.jdbc.Driver。如果数据库是5.7驱动包放5.x版则必须改回com.mysql.jdbc.Driver。先确认自己的MySQL版本再动这个配置。5.2 中文乱码全表生效现象插入的水果名称在表格里显示为???或直接报Incorrect string value错误。原因字符集问题从三层传递——连接串、数据库表、Java文件编码三层中任意一层不是utf8结果就是乱码。源码的Java文件本身是utf8编码但如果你用Windows记事本打开另存过可能变成GBK。解决连接串保留characterEncodingutf8建表SQL统一用DEFAULT CHARSETutf8mb4IDEA设置File Encoding为UTF-8。三步都做到乱码才能彻底根除。只改任何一处都可能在某个环节复发。5.3 PreparedStatement 参数索引越界现象启动后执行任何一条带条件的查询报Parameter index out of range (1 number of parameters)。原因SQL模板里的问号数量和ps.setXxx()的调用次数对不上。最常见的情况是修改了SQL但忘了同步代码里的参数设置比如原本查三个字段后来加了一个搜索条件变四个问号但代码里只set了三个。解决数清楚SQL模板里的问号数然后在IDE里逐个对照set方法的角标。我的习惯是每条set语句后面写注释标明它对应哪个字段比如// 1 - fruit_name排错效率提升明显。5.4 库存变负数现象销售出库时不校验库存直接扣减导致库存字段变负数界面数据看起来很不合理。原因没写库存充足性校验或者校验和扣减不在同一个事务里。两个操作分开执行时如果扣减前有其他线程也做了销售库存会被超卖。解决严格按照3.3节的saleFruit方法结构——先查库存、判断是否充足、再执行扣减且整个过程在一个事务内。另外数据库层面可以给stock字段加CHECK (stock 0)约束双保险兜底。5.5 JTable 点刷新按钮后列宽度还原现象每次refreshTable()之后用户手动拖宽的表格列都恢复默认宽度。原因源码的refreshTable()直接调用table.setModel(new FruitTableModel())重建了整个模型列的宽度偏好preferredWidth是跟随旧的TableColumnModel丢失的新模型使用的是默认宽度。解决在refreshTable()里先记录每列当前的宽度到int[] widthssetModel之后再用table.getColumnModel().getColumn(i).setPreferredWidth(widths[i])逐列恢复。改动只有几行代码但交互体验提升明显这种细节在答辩演示时也很能说明问题。6. 源码部署与三个加分改动从能跑到能讲6.1 打包成可执行Jar连数据库跑通全流程项目通过IDE直接跑没有难度但如果想打包成jar文件带去答辩现场演示用IDEA的操作路径是File → Project Structure → Artifacts → 添加JAR → 选择「From modules with dependencies」Main Class选MainFrame然后Build → Build Artifacts。生成的jar包默认不带配置文件需要把db.properties复制到jar的同级目录否则启动还是找不到连接配置。6.2 加分改动一库存预警标记在原表格基础上加一个「库存预警」列实现逻辑是对stock字段做阈值判断。在FruitTableModel.getValueAt里加一个分支库存小于等于50斤时单元格渲染成红色背景。在JTable里对单一单元格做红色标记需要重写DefaultTableCellRenderer的getTableCellRendererComponent方法这是Swing里最常见的自定义渲染练习讲清楚了就是一次漂亮的加分答辩。6.3 加分改动二按产地统计的库存报表原系统只提供按名称搜索不提供聚合统计。我建议补一个SELECT origin, SUM(stock) AS total_stock, AVG(purchase_price) AS avg_price FROM fruit_info GROUP BY origin跑完结果直接显示在左侧分类面板的下半部分。这个改动能把「产地拆字段」设计的价值展现出来——不同产地的库存占用情况一目了然进下一批货时知道哪个产地该补、哪个产地该停。6.4 加分改动三进货记录的时间范围筛选进货表只有简单的插入和展示没有按日期筛选的功能。但这个功能对经营场景是刚需——老板想看「上周进了多少货」你总不能让他一天一天翻。改法是在进货记录面板加两个JDateChooser日期选择器查询SQL加WHERE sale_time BETWEEN ? AND ?参数直接set日期对象。做完这三个改动你这套源码已经不只是「能跑的课设」而是「能讲出设计思路的课设」。我拆过很多套Java课设源码大部分功能单薄到答辩老师一问数据流就卡壳。这套源码至少把事务、PreparedStatement、自定义TableModel这三个技术点都立住了你拿到手之后重点去读saleFruit和FruitTableModel这两块读懂之后把数据库连接串改成你自己的口令跑通一遍主流程。我拿到的第一件事就是看建表语句和事务代码从那以后我每次拆这类课设源码都强制自己先走一遍「建库 → 跑通 → 找坑」的流程这几个坑如果你提前跳过能省下至少一下午的排错时间。希望这份拆解帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑