ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

养老院管理系统Java源码解析:Swing+MySQL实现与改造指南

2026/10/8 10:13:40 拓冰建站 浏览量
养老院管理系统Java源码解析:Swing+MySQL实现与改造指南 简介面向Java初学者与养老院信息化项目开发者一套完整的养老院管理系统设计与实现源码包覆盖老人档案、护理记录、健康监测、服务预约、员工与财务管理等核心模块可帮助读者理解业务系统从分层架构到接口设计的落地过程。压缩包共155个文件其中133个Java源文件承载登录控制、通用工具、分页响应、房间类型服务、来访登记等核心业务逻辑17个XML配置负责框架与运行参数另有JSON、YAML、Git忽略文件与说明文档整体大小仅420KB代码结构清晰便于按模块阅读。该源码包接口层包含SwaggerConfig持久层引入AutoFillInterceptor配合BaseEnum、PageResponse等基础组件展示了如何统一响应格式、自动填充公共字段及配置接口文档适合系统学习JavaWeb工程化开发。已有109人学习下载对期望通过完整源码快速上手管理系统开发、借鉴企业级项目组织方式的读者而言是一份轻量且实用的参考资料。1. 养老院管理系统 Java 源码与其拼功能不如先看它能不能改如果你正在找 Java 课程设计案例源码大概率已经翻过不少管理系统项目。这个养老院管理系统没有堆砌花哨功能而是把老人档案、床位分配、护理记录和收费管理四条主线串在一起角色也控制得很克制管理员管全局护理员管日常记录。整个项目基于 Java 语言实现界面走 Swing数据库用 MySQL源码结构直白适合答辩时讲清楚也适合拿来做毕业设计的二开底子。下面我从功能边界、运行步骤、核心代码和常见坑几个角度拆给你看最后给出一套改造成局域网可用的验证方法。2. 先看系统边界功能模块、数据表与技术选型2.1 核心模块拆解四条业务主线拿到一份课程设计源码第一步不是急着运行而是先读它的菜单。养老院管理系统这种题目通常表面看是“老人信息管理”实际上背后是床位、护理、收费三条隐藏流程。我按这个源码包的界面结构把功能拆成四条主线。老人档案主线包括老人的基本信息姓名、身份证、家属联系方式、入院日期、既往病史、过敏史和状态管理在院、出院、转院。这里最容易出现的问题是身份证号长度校验课程设计里很多代码只做了非空判断录入 15 位或 18 位混用时会报错。床位与房间主线房间有房号、朝向、楼层、床位数量床位有状态空闲、占用、维修。老人与床位通常是一对一绑定退房或换床要同时更新两个表这是后面收费核算的基础。护理记录主线护理员每天记录老人的体征、饮食、用药、护理事项。这个模块的时间字段用得最频繁查询范围、排序、分页都在这里。收费管理主线根据床位费、护理等级、伙食费按月生成账单。这条线和床位状态强关联如果床位被释放但收费单还没结清报表就会对不上。这四个模块的关系比一般学生管理系统更复杂一点但恰恰因为复杂答辩时反而好讲。评委通常关注两个问题老人换床后费用怎么变护理记录和档案是什么关系这两个问题能在代码里说清楚这门课基本就稳了。角色权限上源码一般只区分admin和nurse两种。admin可以操作老人档案、床位分配、收费结算和用户管理nurse只能访问护理记录和老人信息查询看不到收费和用户管理菜单。这种限制分布在各窗口的checkPermission()方法里本质是判断当前登录用户的role字符串不涉及复杂权限框架。如果你想让某个角色只能看不能改在保存按钮的setVisible(false)里补一句角色判断就行。2.2 数据表设计与关联关系表结构草图源码里最核心的几张表我整理成下面的清单。实际表名前缀可能带tb_或t_不同版本会有差异但字段语义基本一致表名关键字段说明userid, username, password, role管理员/护理员账号role 区分权限elderid, name, id_card, phone, family_phone, entry_date, status老人档案主表roomid, room_no, floor, direction, bed_count房间基础信息bedid, room_id, bed_no, status, elder_id床位表elder_id 为空表示空闲nurse_recordid, elder_id, nurse_time, content, operator_id护理日常记录paymentid, elder_id, amount, pay_date, pay_type收费/账单记录dict_itemid, type, name, value下拉字典护理等级、收费项目等这里要重点看bed表和payment表的外键关系。bed.elder_id关联elder.idpayment.elder_id也关联elder.id但没有直接关联床位。这就是一个隐藏问题如果换床时只改了新的bed.elder_id没有同步生成一条换床记录那么收费时按时间区间查询床位价格就会算错。我在源码里看到这一块的处理方式是在床位变更时先关闭原床位、分配新床位同时写一条变更流水到nurse_record里用备注字段记录“换床”。这种做法不算优雅但对课程设计来说足够应付而且答辩时能讲出理由。从面向对象编程 Java 的角度看这些表映射成了Elder、Room、Bed、NurseRecord、Payment几个实体类字段与表列一一对应。DAO 层负责把ResultSet转成实体对象界面层只跟实体打交道不直接操作ResultSet。封装性到这一层就够了再往上加 Service 层也不是不行只是会让老师觉得你把简单问题复杂化。2.3 技术选型为什么用 Swing JDBC 而不是 Spring Boot MyBatis现在很多 Java 课程设计已经用 Spring Boot MyBatis 了但养老院管理系统这类题目市面上存量最大的源码其实还是 Java SE Swing JDBC MySQL。原因很简单课程设计给的时间通常只有两周到一个月Swing 拖界面比写前端加后端快得多JDBC 手写 SQL 也能让老师确认你真的会查数据库而不会误以为你只会调 MyBatis 接口。并不是说 MyBatis 不好。如果你准备二开我的建议是保持 JDBC 不动把注意力放在业务逻辑上。原因有三个第一这份源码本身是面向“课程设计答辩”写的SQL 集中在 DAO 层替换成 MyBatis 反而把简单问题复杂化第二Swing 界面直接调用 DAO没有 Service 层如果强行套 Spring 的层级反而别扭第三手写 JDBC 在展示时更好讲解评委问“你的登录是怎么实现的”你可以直接指到那一行PreparedStatement上。如果你的老师明确要求使用 Spring Boot那你需要做的不是把整个源码头重脚轻地塞进 Spring而是保留 Swing 逻辑单独抽取一个 REST 接口层。常见做法是新增一个ApiController把 DAO 查询结果转成 JSONSwing 用HttpClient去调。这个改造量不小但也是后面做局域网版的基础。在本章里我先按原始 JDBC 版本讲因为它是最容易一次跑通的状态。3. 把源码跑起来JDK 配置、数据库初始化与启动顺序3.1 环境准备JDK 版本与 java 环境变量配置很多新手卡在第一步不是代码不行是 JDK 版本和位数不对。这份源码基于 Java 8 写成的可能性最大因为 Swing 在 Java 8 里最稳定数据库驱动用的也是mysql-connector-java5.x 或 8.x 的兼容写法。我一般会直接装 JDK 8然后确认JAVA_HOME指向 java 目录而不是 jre 目录。这里要提醒一下java 环境变量配置详细教程网上很多但最容易翻车的是Path里重复出现了多个 Java 版本。你可以打开命令提示符执行java -version javac -version如果两条命令显示的不是同一个版本大概率是Path里旧版本的C:\Program Files\Java\jdk1.7.0_80\bin排在前面了。解决方法是把当前 JDK 的bin目录移到最顶部或者在系统变量里删掉旧的。JAVA_HOME配置好之后还要确认CLASSPATH里没有把tools.jar写错位置虽然 Java 8 不强制但有些老项目会依赖它。数据库方面这份源码对应的是 MySQL 5.7如果你装的是 MySQL 8.x需要把驱动换成com.mysql.cj.jdbc.Driver并且加上时区参数。下面这个 URL 是兼容写法jdbc:mysql://localhost:3306/elder_care?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/ShanghaiuseSSLfalse如果你用的是 MySQL 8 以上com.mysql.jdbc.Driver会直接报 ClassNotFoundException这就是后面避坑部分要讲的第一个问题。建议在动手前先建好一个空库名字尽量和源码里的db.properties保持一致避免后面因为库名不一致改来改去。3.2 导入工程与修改数据库连接配置常见做法是用 IntelliJ IDEA 打开源码根目录选择 JDK 8等待 Maven 或者 Gradle 自动刷新。如果这份源码没有pom.xml就是一个纯 Eclipse 工程直接 File - Open 选择目录IDEA 会自动识别成 Java 项目。导入后先找db.properties或DBUtil.java这是数据库连接的集中管理位置源码里一般写成这样的配置drivercom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/elder_care?useUnicodetruecharacterEncodingUTF-8 usernameroot password123456修改时千万注意password改成你自己的数据库密码url里的elder_care是库名要和后面导入的 SQL 文件保持一致。如果你是用 Navicat 导入 SQL建议先创建空库再导入否则容易因为字符集不一致出现表结构乱码。源码里如果带了lib目录里面有mysql-connector-java.jar那就保留现有驱动包如果没带去 Maven 仓库下对应版本的 jar 放进去。3.3 初始化数据表与启动顺序源码包里通常会带一个elder_care.sql文件里面包含建表语句和测试数据。我一般会打开这个 SQL 文件先确认里面有没有DROP TABLE IF EXISTS这是安全执行的前提。用 MySQL 命令行导入的常见做法mysql -u root -p elder_care.sql这条命令会把 SQL 文件里所有语句按顺序执行。导入完成后用下面这句检查核心表是否建全USE elder_care; SHOW TABLES;正常能看到user、elder、room、bed、nurse_record、payment这六张表。如果少了表多半是 SQL 脚本执行到一半报错把前面的DROP TABLE注释掉再重新导入即可。接着启动项目先编译主类。命令行的启动方式适合给老师演示javac -encoding UTF-8 -cp .;lib/* Main.java java -cp .;lib/* Main第一行把当前目录和lib目录下的所有 jar 包加入 classpath第二行运行主类。如果你在 IDEA 里用图形界面启动注意把项目编码改成 UTF-8否则界面中文会变成乱码。启动后默认账号通常在user表里可以直接查到比较常见的初始化数据是admin / admin123或admin / 123456少数版本会写成admin / admin直接用 SQL 查询就能确认不用猜。4. 核心代码走读登录鉴权、老人档案与收费联动4.1 登录模块MD5 加盐与 Session 校验登录模块是管理系统的门面。这个源码里的密码没有用明文存储而是用 MD5 加密。我在这个项目里看到的是单次 MD5没有加盐。先看一个典型的登录验证代码public User login(String username, String password) throws SQLException { String md5Pwd MD5Utils.md5(password); String sql SELECT id, username, role FROM user WHERE username ? AND password ?; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, md5Pwd); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User u new User(); u.setId(rs.getInt(id)); u.setUsername(rs.getString(username)); u.setRole(rs.getString(role)); return u; } } } return null; }这段代码用PreparedStatement参数化查询避免拼接 SQL 注入这是课程设计里比较少见的加分项。MD5Utils.md5()是工具类负责把密码转成 32 位小写十六进制字符串。登录成功后Swing 界面会把User对象存在当前窗口类的成员变量里相当于一次简单的会话管理。需要注意这里没有自动登录功能所以只要关闭窗口再打开就得重新输入这其实是合理的对一个单机版系统来说没有必要把复杂度提上去。如果你想把登录密码改得更安全可以在MD5Utils里改成MD5(md5(password) salt)但要注意user表里已有的密码全部要重新生成。课程设计阶段单次 MD5 已经足够应付课堂演示不要在答辩前临时改加密逻辑否则很容易出现旧账号登不进去的情况。4.2 老人档案管理增删改查的边界与防御老人档案这块抛开界面拖拽不谈核心是 DAO 层的addElder和updateElder。我看到的大部分版本SQL 语句都是这种模式public boolean addElder(Elder elder) throws SQLException { String sql INSERT INTO elder(name, id_card, phone, family_phone, entry_date, status, remark) VALUES(?, ?, ?, ?, ?, ?, ?); try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, elder.getName()); ps.setString(2, elder.getIdCard()); ps.setString(3, elder.getPhone()); ps.setString(4, elder.getFamilyPhone()); ps.setDate(5, new java.sql.Date(elder.getEntryDate().getTime())); ps.setString(6, elder.getStatus()); ps.setString(7, elder.getRemark()); return ps.executeUpdate() 0; } }这个写法值得注意三个点第一身份证号的类型用的是VARCHAR而不是BIGINT因为身份证有可能包含 X用长整型会丢信息第二entry_date从界面拿的是java.util.Date必须转成java.sql.Date才能绑定这个转换很多人忘掉第三saveOrUpdate这种通用方法强行把新增和修改合并反而容易造成字段遗漏这份源码分开写逻辑更清楚也方便在修改时不误触创建时间的字段。边界控制上调用层需要做一次空值校验。常见做法是点击“新增”按钮时先在窗口类里用isNotEmpty方法判断必填字段为空就弹出JOptionPane警告而不是直接调 DAO。数据库那边虽然也加了NOT NULL但界面提示的体验完全不同。查询逻辑上按老人姓名和身份证号做模糊查询时要记得 SQL 里写成LIKE ?参数传% 关键字 %而不是在业务代码里拼接字符串。4.3 收费与床位联动事务里最容易出错的顺序收费和床位联动是这部分代码里最有含金量的地方。当老人要换床时不能只改bed.elder_id还要同时生成一条收费变更记录。如果这两步只执行了一半原始床位变成空、新床位还没绑定系统就脏了。源码里常见做法是用Connection手动开启事务public boolean changeBed(int elderId, int oldBedId, int newBedId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); String sqlRelease UPDATE bed SET status空闲, elder_idNULL WHERE id?; String sqlBind UPDATE bed SET status占用, elder_id? WHERE id?; String sqlLog INSERT INTO nurse_record(elder_id, nurse_time, content) VALUES(?, NOW(), ?); try (PreparedStatement ps1 conn.prepareStatement(sqlRelease)) { ps1.setInt(1, oldBedId); ps1.executeUpdate(); } try (PreparedStatement ps2 conn.prepareStatement(sqlBind)) { ps2.setInt(1, elderId); ps2.setInt(2, newBedId); ps2.executeUpdate(); } try (PreparedStatement ps3 conn.prepareStatement(sqlLog)) { ps3.setInt(1, elderId); ps3.setString(2, 老人换床原床位 oldBedId 释放新床位 newBedId); ps3.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } return false; } finally { DBUtil.close(conn); } }这里的关键是setAutoCommit(false)之后所有 DML 语句都不会立即生效只有commit()才会一起写入。只要中间任何一条语句抛异常rollback()会还原所有改动。连接工具类DBUtil.close在finally里执行保证连接不会泄漏。对课程设计来说能写出这样一段事务代码已经超过大多数同类项目。如果你打算改造收费模块建议沿用这个结构不要在每个按钮里单独写conn。5. 避坑记录养老院管理系统从编译到运行的五个常见问题5.1 数据库连接失败驱动版本和 URL 写法各占一半原因现象运行后点登录窗口弹出ClassNotFoundException: com.mysql.jdbc.Driver或者Communications link failure。原因ClassNotFoundException是驱动 jar 没放进lib目录或 classpath 没包含。link failure是 URL 写错或 MySQL 端口不是 3306或者驱动版本与 MySQL 版本不匹配。最常见的坑是 MySQL 8 环境还沿用com.mysql.jdbc.Driver这个旧驱动类名。解决先确认lib目录下有没有mysql-connector-java.jar然后用压缩工具打开 jar看com/mysql/cj/jdbc/Driver.class是否存在。MySQL 8 就把驱动类改成com.mysql.cj.jdbc.Driver并在 URL 后加serverTimezoneAsia/Shanghai。如果测的是本地用jdbc:mysql://127.0.0.1:3306/elder_care不要写localhost在某些虚拟网卡环境下localhost会走 IPv6 导致连接超时。5.2 中文乱码连接参数与界面编码要同时统一现象老人姓名、护理记录里的中文在表格和文本框中显示成???或汉å—这样的乱码。原因数据库表字符集不是 UTF-8项目源码编译编码不是 UTF-8JDBC URL 缺少characterEncodingUTF-8三者中任何一个不一致都会乱码。???一般是写入时源数据已变成问号说明 JDBC 连接字符集不对显示成 mojibake 是读取后转码错误。解决把数据库连接 URL 加上useUnicodetruecharacterEncodingUTF-8并确认建表语句里写了DEFAULT CHARSETutf8mb4。IDEA 里把File - Settings - Editor - File Encodings里的全局编码和工程编码都改成 UTF-8。如果你用 Eclipse在 Run Configurations 的 VM 参数里加-Dfile.encodingUTF-8。改完后要把已经乱码的数据删掉重新插入因为坏数据不会自动恢复。5.3 按钮点了没反应事件监听器注册被忘在最后一行现象登录按钮、查询按钮点击后没有任何响应控制台也不报错。原因很大概率是界面初始化方法initFrame()里创建了按钮但忘记了button.addActionListener(this)或者监听器加上了却没在actionPerformed里对getSource()做判断。另一种原因是按钮对象的实例变量和局部变量重名导致监听的是另一个按钮。解决先确认按钮类实现了ActionListener接口再看initFrame()末尾有没有这行loginBtn.addActionListener(this);。然后检查actionPerformed里的if (e.getSource() loginBtn)用的是不是同一个实例。复现步骤上可以在监听器第一行打印一行日志比如System.out.println(click login)如果点了不打印说明监听没注册如果打印了但界面不动再去查后面的逻辑。5.4 导出报表内存溢出一次拿太多行不如分页查现象点击“导出费用报表”程序运行几分钟后抛出OutOfMemoryError: Java heap space。原因报表查询一般会SELECT * FROM payment WHERE ...如果养老院的记录有几万条全部加载到ResultSet里构建JTable内存自然不够。课程设计环境默认堆内存通常是 256MB加载几万行 Swing 对象很容易顶爆。解决改成一次只取 500 行用LIMIT offset, size分页查。或者用Statement的setFetchSize(200)让 JDBC 逐批取数而不是全量取到内存。更推荐的做法是导出报表时直接用Statement创建可滚动的ResultSet边读边写到 CSV 文件每一行 flush 一次这样内存占用是恒定的。给 JVM 加-Xmx512m只是延缓问题不是根治办法。5.5 SimpleDateFormat 并发报错日期格式化别用共享实例现象护理记录窗口连续保存两条记录时偶尔出现控制台输出java.lang.NumberFormatException: For input string: 或日期变成 1970-01-01。原因很多人把SimpleDateFormat定义为类的静态成员以为只是格式化工具其实它内部持有可变状态。两个线程同时调用时同一个实例的Calendar对象会被互相覆盖导致解析出来的日期字段错乱。Swing 事件派发线程虽然只有一个但里面若有后台线程导出数据就会踩中这个并发问题。解决每次使用时在方法内部新建一个局部实例在 Java 8 里更推荐用DateTimeFormatter formatter DateTimeFormatter.ofPattern(yyyy-MM-dd); LocalDate date LocalDate.parse(text, formatter);LocalDate是不可变且线程安全的。如果源码里到处是SimpleDateFormat最省事的做法是写一个DateUtils工具方法返回格式化后的字符串内部每次new SimpleDateFormat(...)把改动收敛到一处。6. 让单机版变成局域网可用的三招配置、启动脚本与快速验证6.1 数据库远程访问与防火墙端口Swing 客户端连接数据库天然支持局域网点对点访问不需要改造任何业务代码。首先要在 MySQL 里创建一个允许远程访问的用户而不是直接用 rootCREATE USER elder_app% IDENTIFIED BY App2024; GRANT SELECT, INSERT, UPDATE, DELETE ON elder_care.* TO elder_app%; FLUSH PRIVILEGES;这样做的原因是 root 远程登录容易被安全策略拦截而且万一数据库被扫到权限范围更小。%表示任意 IP也可以是具体的办公网段192.168.1.%。然后在服务器防火墙放行 3306 端口。Windows 上执行netsh advfirewall firewall add rule nameOpen 3306 dirin actionallow protocolTCP localport3306接下来把客户端里db.properties的localhost改成服务器的局域网 IP。改完测试连接。6.2 一键启动脚本每次手动开 IDEA 调试太慢给班主任演示或机房临时检查最好准备一个启动脚本。在项目根目录建start.batecho off cd /d %~dp0 javac -encoding UTF-8 -cp .;lib\* src\com\elder\*.java -d classes java -cp classes;lib\* com.elder.Main逻辑是切到脚本所在目录编译全部 Java 文件到classes目录再用 classpath 运行主类。注意-encoding UTF-8必须写否则 Windows 默认 GBK 会破坏代码里的中文。如果不想每次编译也可以先把可运行 jar 打好脚本只执行java -jar app.jar但需要额外处理清单文件所以我一般按编译运行处理。6.3 用 Smoke Test 验证登录和数据一致性改造完成后别急着说完成了。我会手动跑一遍冒烟用例固定五步花不了五分钟用 admin 登录新增一位老人分配一个新床位录入一条护理记录最后在收费管理里生成当月账单。每一步都检查界面状态和服务端 MySQL 数据。这一步看起来简单但能拦住 80% 的翻车。我还习惯在changeBed方法里临时加一行日志输出事务提交前的床位状态确认新老床位都不处于中间态。测试完再把这行日志删掉。从那以后我每次拿到这类 Java 课程设计源码都会先按这个清单走一遍而不是直接打开代码看。确认数据表外键关系、登录字符集、事务提交顺序这三件事花不了多少时间但能省下答辩现场重启电脑、客户现场改配置的尴尬。希望帮到你。本文还有配套的精品资源点击获取