news 2026/9/16 14:29:19

Java课设宠物领养系统实战:Swing+JDBC+MySQL从建表到答辩

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java课设宠物领养系统实战:Swing+JDBC+MySQL从建表到答辩

简介:这是一份基于Java Swing与MySQL实现的宠物领养系统完整课设源码包,适合Java课程设计、毕业设计入门及需要快速落地项目的开发者参考。系统包含完整的业务代码、界面素材与数据库脚本,覆盖宠物信息管理、领养记录等核心模块,能够帮助使用者理解GUI事件处理与JDBC操作的典型配合方式。资源包共84个文件,以class编译文件、java源码、png界面截图和xml配置为主,附带sql数据库脚本、docx课程设计报告和mp4演示视频,整体大小约33.13MB,结构清晰,便于对照学习。已有502人浏览学习过该资源。代码风格保持原始交付形态,功能可正常运行,适合作为Java课设的完整参考方案。

1. 宠物领养系统的课设定位:Swing 是老技术,但它的分层逻辑并不过时

做 Java 课设时,很多人一听到 GUI/Swing 就先入为主觉得“技术太老、拿不出手”。但实际上,Swing 项目恰恰是检验 Java 基础功力的最快路径:它强迫你处理事件分发、界面与数据模型的解耦、数据库连接的生命周期管理——这些东西在后续接触 Spring Boot 或 Android 开发时都会原样出现。宠物领养系统就是这样一个典型:用 JFrame 搭壳、JTable 承载数据、JDBC 读写 MySQL,把增删改查从控制台搬到图形界面,覆盖了 Java 课程里最核心的几块知识点。

这套源码是接单交付后留下的,数据库用 MySQL、界面用 Swing、IDE 直接用 IntelliJ IDEA 打开就能跑。它的价值不在于界面有多华丽,而在于完整地演示了一个小型管理系统的工程结构。下面我会把项目的数据库设计、JDBC 连接层、界面事件绑定和课设答辩时的演示技巧逐一拆开,不绕弯子,直接讲代码和踩过坑的地方。

2. 从 pets.sql 看数据建模:四张表如何撑起一套领养业务

2.1 把“系统”拆回数据库:管理员、宠物、领养申请的三层关系

打开压缩包里的pets.sql,你会发现这套系统的表结构非常克制,总共四张核心表,没有多余的冗余字段。建模时遵循的思路是:先理清业务角色,再画实体之间的关系。宠物领养业务的角色很清晰——管理员维护宠物信息,用户浏览宠物并发起领养申请,管理员审核申请后更新宠物状态。这三个动作对应到数据库里,就自然分出管理员表、宠物表、领养申请表,再加上一个支撑登录注册的用户表。

这种建模方式与你平时在课程里学的“学生-课程-选课”三张表如出一辙,核心就是通过外键把业务动作串起来。很多同学做课设时喜欢一张表塞十几个字段,恨不得把用户头像、宠物照片、申请备注全塞进去,结果查询时 JOIN 写得乱七八糟。这套系统的表结构更接近真实项目的最小可行模型:每张表只存自己的核心属性,表与表之间用 ID 关联。

2.2 建表脚本逐段拆解:主键设计、时间戳默认值和外键约束

先看pets.sql中宠物表的关键片段:

CREATE TABLE `pet` ( `id` int(11) NOT NULL AUTO_INCREMENT, `pet_name` varchar(50) NOT NULL COMMENT '宠物名称', `pet_type` varchar(20) DEFAULT NULL COMMENT '类别:dog/cat/rabbit', `age` int(11) DEFAULT NULL COMMENT '年龄(月)', `health_status` varchar(100) DEFAULT NULL COMMENT '健康状况描述', `adopt_status` tinyint(4) DEFAULT '0' COMMENT '0-待领养 1-已领养', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这段建表脚本有几个细节值得展开。idAUTO_INCREMENT做主键,保证插入数据时不需要显式传 ID,由 InnoDB 自增维护,这是小型管理系统的惯用做法。adopt_statustinyint而不是varchar存储状态,0 和 1 两个取值在 Java 端映射为Integer类型,判断时用==比较就够了,比字符串比较更高效也更安全。created_at设置了DEFAULT CURRENT_TIMESTAMP,插入时无需手动赋值,JDBC 的PreparedStatement里少绑一个参数,这个技巧在做新增功能时很实用。

再来看领养申请表的外键设计:

CREATE TABLE `apply` ( `id` int(11) NOT NULL AUTO_INCREMENT, `pet_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, `apply_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `status` tinyint(4) DEFAULT '0' COMMENT '0-待审批 1-通过 2-拒绝', PRIMARY KEY (`id`), KEY `idx_pet_id` (`pet_id`), KEY `idx_user_id` (`user_id`), CONSTRAINT `fk_apply_pet` FOREIGN KEY (`pet_id`) REFERENCES `pet` (`id`), CONSTRAINT `fk_apply_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

apply表本身带着pet_iduser_id两个外键,分别指向宠物表和用户表,领养行为被抽象成一条申请记录。这里做了两个索引idx_pet_ididx_user_id,它们是给外键查询准备的。实际写 SQL 时,WHERE pet_id = ?这类条件能直接走索引,不会全表扫描。外键约束fk_apply_pet的作用是保证引用完整性——你无法插入一条指向不存在宠物的申请记录,这在课设答辩演示时是一个可以主动展示的亮点。

2.3 为什么需要四张表而不是三张:权限分离的红线

有同学会问:既然管理员和用户都是人,为什么不能合成一张表,用role字段区分?答案是权限边界。管理员表里存的是后台登录凭据,用户表里存的是前台领养人信息,两者字段不同、操作权限不同,混在一起会让代码里的权限判断遍布if (role == 1)这种散弹式逻辑。这套系统把两张表分开,Java 端的AdminDaoUserDao也独立编写,后续要加管理员操作日志时只需要改管理员相关的类,不会牵连用户模块。

提示:导入pets.sql时如果报错,优先检查 MySQL 版本是否支持utf8mb4字符集。MySQL 5.5.3 以下版本不认识这个字符集,建议直接使用 5.7 或 8.0 版本。

3. JDBC 连接层:从 DriverManager 到连接池的演化逻辑

3.1 MYSQL 驱动注册与连接的封装方式

压缩包里的src目录下,数据库连接部分通常是一个独立的工具类,常见命名是MysqlConnectorDBUtil。核心功能就是两件事:加载驱动、获取连接。看一下典型的实现:

public class MysqlConnector { private static final String URL = "jdbc:mysql://localhost:3306/pets?useSSL=false&serverTimezone=Asia/Shanghai"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

Class.forName("com.mysql.cj.jdbc.Driver")放在静态代码块里,类加载时执行一次,驱动只注册一次。URL 末尾的useSSL=false是 MySQL 8.0 的常见配置,本地开发不需要 SSL 加密链路,不写这个参数会看到大量 SSL 告警日志。serverTimezone=Asia/Shanghai解决的是时区问题,MySQL 8.0 默认使用 UTC,不指定时区会导致java.sql.SQLException的日期时间字段错乱。close()方法里的三层判断是为了防止空指针——ResultSetPreparedStatement可能为null

3.2 DAO 模式下的 SQL 注入防线:PreparedStatement 参数绑定

连接层解决的是“怎么连”的问题,DAO 层解决的是“怎么查”的问题。以宠物搜索功能为例,PetDao里查找待领养宠物的方法如下:

public List<Pet> findPetsByType(String type) { List<Pet> list = new ArrayList<>(); String sql = "SELECT id, pet_name, pet_type, age, health_status, adopt_status " + "FROM pet WHERE adopt_status = 0 "; if (type != null && !type.trim().isEmpty()) { sql += " AND pet_type = ?"; } try (Connection conn = MysqlConnector.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { if (type != null && !type.trim().isEmpty()) { ps.setString(1, type); } try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { Pet pet = new Pet(); pet.setId(rs.getInt("id")); pet.setPetName(rs.getString("pet_name")); pet.setPetType(rs.getString("pet_type")); pet.setAge(rs.getInt("age")); pet.setHealthStatus(rs.getString("health_status")); pet.setAdoptStatus(rs.getInt("adopt_status")); list.add(pet); } } } catch (SQLException e) { e.printStackTrace(); } return list; }

这里的查询条件是动态拼接的,当type参数为空时,SQL 退化为查全部待领养宠物;有值时追加AND pet_type = ?。用PreparedStatement而不是Statement,核心原因是参数占位符?会被 JDBC 预编译,传进来的字符串即使包含单引号或OR 1=1也会被转义,从根上堵住了 SQL 注入。try-with-resources语法自动关闭ConnectionPreparedStatement,不需要手动调用close(),代码比传统写法干净不少。

3.3 连接释放的两个隐藏坑:事务边界与血淋淋的 TooManyConnections

DriverManager.getConnection()每次调用都会新建一条物理连接,使用频率高时性能会快速劣化。这套系统是单机课设,并发量极小,所以直接使用DriverManager完全够用。但如果你在答辩演示时连续快速点击查询按钮,仍然要把释放资源的逻辑写对。观察下面的反例:

// 错误写法 Connection conn = MysqlConnector.getConnection(); PreparedStatement ps = conn.prepareStatement("SELECT * FROM pet"); ResultSet rs = ps.executeQuery(); // 忘记关闭 conn, ps, rs

连接不关闭的直接后果是 MySQL 的max_connections(默认 151)被快速耗尽,后续请求全部报Too many connections。更隐蔽的坑是事务边界问题:如果一次领养操作包含“插入申请记录”和“更新宠物状态”两步,必须把两个 DAO 调用包在同一个事务里,否则会出现申请已提交但宠物状态未变更的数据不一致。正确做法是在业务层手动关闭conn.setAutoCommit(false),操作完成后统一commit(),异常时rollback()

提示:全部代码跑通后,可以在MysqlConnector里加一个static int activeConnectionCount计数器,在getConnection()close()里增减并打印,用来验收连接是否全部释放——这是一个能写进课设报告里的“自验步骤”。

4. Swing 界面与事件绑定:从 JFrame 到 JTable 的 MVC 落地

4.1 主窗口结构拆分:面板嵌套与组件布局策略

Swing 的界面搭建逻辑是“面板套面板”。主窗口MainFrame继承JFrame,内容面板设置为BorderLayout,北区放查询条件和操作按钮,中间区域放JScrollPane包裹的JTable,南区放状态栏。核心布局代码:

public class MainFrame extends JFrame { private JTable petTable; private DefaultTableModel tableModel; public MainFrame() { setTitle("宠物领养系统"); setSize(900, 600); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10)); JComboBox<String> typeBox = new JComboBox<>(new String[]{"全部", "dog", "cat", "rabbit"}); JButton searchBtn = new JButton("查询"); JButton adoptBtn = new JButton("领养申请"); topPanel.add(new JLabel("宠物类别:")); topPanel.add(typeBox); topPanel.add(searchBtn); topPanel.add(adoptBtn); tableModel = new DefaultTableModel( new String[]{"ID", "名称", "类别", "年龄", "健康状况", "状态"}, 0); petTable = new JTable(tableModel); JScrollPane scrollPane = new JScrollPane(petTable); getContentPane().add(topPanel, BorderLayout.NORTH); getContentPane().add(scrollPane, BorderLayout.CENTER); } }

DefaultTableModel的构造参数是“列名数组 + 初始行数 0”,后续通过addRow()动态追加数据。JTable只负责展示模型中的数据,不直接操作数据库,这正好对应 Swing 的 MVC 模式——JTable是视图,DefaultTableModel是模型,DAO 层是数据源。setLocationRelativeTo(null)让窗口居中显示,这是容易被忽略但体验差异明显的细节。

4.2 事件监听线程模型:为什么不能在 ActionListener 里睡线程

按钮的事件绑定通过addActionListener完成。以下是查询按钮的监听器实现:

searchBtn.addActionListener(e -> { String selectedType = (String) typeBox.getSelectedItem(); List<Pet> petList = new PetDao().findPetsByType( "全部".equals(selectedType) ? null : selectedType); tableModel.setRowCount(0); for (Pet pet : petList) { tableModel.addRow(new Object[]{ pet.getId(), pet.getPetName(), pet.getPetType(), pet.getAge(), pet.getHealthStatus(), pet.getAdoptStatus() == 0 ? "待领养" : "已领养" }); } });

查询按钮触发时,事件分发线程(EDT,Event Dispatch Thread)会执行PetDao().findPetsByType()的数据库查询。注意setRowCount(0)先把表格清空,再逐行addRow,这个顺序保证刷新时不会出现旧数据残留。如果你的查询逻辑比较复杂,不要在监听器里直接执行,因为 EDT 是单线程模型——耗时操作会阻塞整个界面的重绘和按钮点击,窗口看起来就像“卡死”了。这个现象在课设答辩演示时最容易翻车:评委点击查询后如果转圈超过两秒,会直接怀疑代码有问题。

setRowCount(0)清空模型后,表格列头不会重置,因为列名是在DefaultTableModel构造时写死的。adopt_status == 0 ? "待领养" : "已领养"这行把数据库里的 0/1 整数翻译成界面上的用户友好文案,属于典型的“数据展示层转换”,不要在 SQL 里用CASE WHEN去做这件事——Java 端处理更直观,改起来也更方便。

4.3 领养申请状态流转:从待审批到通过的界面反馈闭环

领养申请不是点一下就结束的,它有完整的状态机。用户在宠物列表选中一行,点击“领养申请”按钮,系统插入一条apply记录,状态为 0(待审批)。管理员在另一个窗口看到申请列表,点击“通过”后,apply.status变为 1,同时pet.adopt_status变为 1。这个联动操作跨两张表,必须放在事务里:

public boolean approveApply(int applyId, int petId) { String updateApply = "UPDATE apply SET status = 1 WHERE id = ?"; String updatePet = "UPDATE pet SET adopt_status = 1 WHERE id = ?"; try (Connection conn = MysqlConnector.getConnection()) { conn.setAutoCommit(false); try (PreparedStatement ps1 = conn.prepareStatement(updateApply); PreparedStatement ps2 = conn.prepareStatement(updatePet)) { ps1.setInt(1, applyId); ps1.executeUpdate(); ps2.setInt(1, petId); ps2.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { conn.rollback(); return false; } } catch (SQLException e) { e.printStackTrace(); return false; } }

setAutoCommit(false)是事务开启的标志。ps1.executeUpdate()执行申请状态更新,ps2.executeUpdate()执行宠物状态更新,任何一步抛出SQLException都会进入 catch 块执行rollback(),让已执行的更新全部回滚。这段代码是课设报告里最有技术含金量的部分——它演示了跨表事务处理,是“及格”和“优秀”的分水岭。

领养申请按钮监听器还涉及表格行选中校验:

int selectedRow = petTable.getSelectedRow(); if (selectedRow < 0) { JOptionPane.showMessageDialog(this, "请先选中一条宠物记录", "提示", JOptionPane.WARNING_MESSAGE); return; }

没有选中行就点击按钮时,getSelectedRow()返回 -1,这时弹出警告框拦截。这个校验动作能在答辩时展示你的工程意识。

5. 从启动到验收:演示脚本设计、MYSQL 环境变量与二次封装思路

演示项目时,最有效的准备方式是提前踩一遍自己的运行环境。先把 MySQL 服务启动,用命令行或 Navicat 执行pets.sql,确认库和表已建好,然后用 IDEA 打开项目,等 Maven 或 Gradle 依赖解析完成(如果项目本身不带构建工具,直接把src作为源码根目录导入),运行包含main方法的启动类。这里有一个高频踩坑点:MySQL 安装在命令行直接输入mysql报“不是内部或外部命令”,这是环境变量没有配置C:\Program Files\MySQL\MySQL Server 8.0\bin导致的,需要在系统 PATH 里追加该路径后重开终端验证mysql --version

演示时的操作路径建议固定为:启动程序登录管理员账号 → 添加一只新宠物 → 切换用户视角浏览宠物列表 → 发起领养申请 → 切回管理员审批 → 确认宠物状态变为已领养。这条路径覆盖了增删改查的所有分支,而且每一步都有界面反馈。如果演示过程网络波动或数据库卡顿,优先检查MysqlConnector的 URL 是否写成了localhost而 MySQL 实际监听在127.0.0.1,或者防火墙拦截了 3306 端口。一个通用排错指令是:

netstat -ano | findstr :3306

这条命令用来验证 MySQL 是否在监听 3306 端口。如果输出有LISTENING状态但程序连不上,接下来看ping 127.0.0.1是否通则确认端口未对外暴露——本地课设无需改 bind-address,保持默认值即可。如果netstat无输出,说明 MySQL 服务没起来,在 Windows 服务管理器里启动MySQL80服务后再跑程序。

提示:IDEA 里运行项目时如果控制台输出Access denied for user 'root'@'localhost',说明密码与MysqlConnector里的PASSWORD常量不一致,优先排查这个值而不是重装 MySQL。

答辩前的界面调优遵循“小而准”的改动策略。打开JFrame的构造函数,把setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)保留,这是窗口关闭即退出程序的标准行为。如果感觉表格显示太拥挤,把petTable.setRowHeight(28)加上,让每行高度更宽松。想进一步展示细节,可以在Pet实体类里重写toString()方法,将宠物 ID、名称、健康状态拼成一个可读字符串,方便调试时直接打印。这些小的体验优化不需要引入额外的美化库,纯 Swing 代码就能完成,对“加分项”的贡献比追求花哨外观更实在。

如果你拿到源码后想二次开发,我建议改的方向是追加一个“宠物图片路径”字段——在pet表加image_path varchar(255),界面用ImageIcon加载本地图片显示在JLabel上。这个改动只涉及一张表、一个实体字段和一个界面组件,却能显著提升完整度。注意图片路径不要存绝对路径,用相对路径或项目根目录拼接,避免换机器后路径失效。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 14:28:01

2025年AI写作工具市场分析与十大推荐

1. 2025届AI写作工具市场现状2025年的内容创作领域已经全面进入人机协同时代。根据最新行业调研数据显示&#xff0c;超过87%的专业内容创作者在日常工作中至少使用一种AI写作辅助工具&#xff0c;而这一比例在三年前还不足35%。市场需求的激增催生了大量新兴工具&#xff0c;同…

作者头像 李华
网站建设 2026/9/16 14:27:03

CodeBuddy 跑 WireMCP 分析 PCAP:Key 用 TaoToken

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

作者头像 李华
网站建设 2026/9/16 14:26:39

JDK25安装指南:从下载到环境配置全解析

1. JDK25概述与环境准备Java Development Kit 25&#xff08;JDK25&#xff09;是Oracle公司推出的最新Java开发工具包&#xff0c;包含运行、调试和监控Java应用程序所需的完整工具链。与之前版本相比&#xff0c;JDK25在性能优化、安全增强和语言特性方面都有显著改进。对于开…

作者头像 李华
网站建设 2026/9/16 14:25:50

玉米病虫害知识图谱问答系统:从图谱构建到Cypher查询落地实践

简介&#xff1a;这是一套面向玉米病虫害领域的知识图谱问答系统新版设计源码&#xff0c;适合农业信息化研究者、自然语言处理初学者及毕业设计开发人员。系统涵盖病虫害数据标注、问题解析、答案检索等模块&#xff0c;用户可用自然语言询问病害特征、发生规律与防治方法&…

作者头像 李华