
简介这份资源是面向高校计算机专业学生与Java Web初学者的毕业设计实践包主题为基于JavaJSPMySQL的电子健康档案系统可用于课程设计、毕设选题或医疗机构信息化入门参考。压缩包共760个文件约17.76MB以js脚本、gif与png图片、html页面、jar依赖包、css样式为主另含jsp页面、java源码、xml配置、properties文件及数据库db文件覆盖前端交互、后端逻辑与数据存储各层。已有131人学习下载。源码完整呈现了MVC分层结构读者可据此掌握Servlet请求处理、JavaBeans数据封装、JSP动态页面渲染、JDBC数据库连接与增删改查、用户认证与SQL注入防护、异常处理及缓存索引等性能调优思路是理解Java Web项目从建表到部署全流程的实用案例。1. 从一份 .rar 说起JavaJSPMySQL 电子健康档案系统到底在做什么很多人拿到「基于JavaJSPMySQL电子健康档案系统毕业设计实现源码.rar」这个标题第一反应是去搜现成源码第二反应是发现跑不起来。我见过太多这种情况压缩包解压出来一堆.java和.jsp数据库脚本缺失或者字段对不上Tomcat 一启动就是 404 或者 500。这篇笔记不聊虚的就讲清楚这套技术栈组合到底解决什么问题、一个能跑起来的电子健康档案系统最小骨架长什么样、参数怎么配、坑在哪。电子健康档案系统的核心业务其实不复杂居民基本信息、既往病史、过敏记录、体检数据、就诊记录这几张表加上医生和居民两种角色的权限区分。用 Java 做后端、JSP 做页面渲染、MySQL 存数据是十年前企业内训和课程设计最经典的组合放到今天依然能跑通因为它的依赖链短、调试直观。适合谁看正在做计算机毕业设计、需要一套能演示能答辩的系统、或者想拿它练手 Java Web 全流程的人。下面从环境搭建一路讲到能登录、能建档、能查数据。2. 环境与数据库把 MySQL 和 Tomcat 的版本对齐2.1 JDK、Tomcat、MySQL 的版本选择逻辑这套技术栈最怕版本乱配。JSP 依赖 Servlet 容器Tomcat 8.5 对应 Servlet 3.1Tomcat 9 对应 Servlet 4.0Tomcat 10 之后包名从javax.servlet改成jakarta.servlet老代码直接编译不过。所以我的建议是锁死一套组合JDK 8 或 JDK 11、Tomcat 8.5 或 9、MySQL 5.7 或 8.0。JDK 8 兼容性最好网上大部分 JSP 教程和 jar 包都是这个时代的产物。MySQL 8.0 和 5.7 的差别主要在认证插件和默认字符集。8.0 默认用caching_sha2_password老版本的 JDBC 驱动连不上会报Unable to load authentication plugin。解决办法要么升级驱动到mysql-connector-java 8.0.x要么把用户认证方式改回mysql_native_password。字符集统一用utf8mb4别用utf8否则存中文姓名和病历描述时会出现乱码或者截断。组件推荐版本关键原因JDK8 或 11兼容老 jar 包避免模块化报错Tomcat8.5 或 9保持javax.servlet包名MySQL5.7 或 8.05.7 省心8.0 注意驱动版本JDBC 驱动5.1.x 或 8.0.x与 MySQL 版本对应IDEEclipse 或 IDEA支持 Dynamic Web Project2.2 建库建表电子健康档案的核心表结构数据库是整个系统的地基。电子健康档案的核心表我一般拆成五张用户表、居民档案表、病史表、体检记录表、就诊记录表。用户表管登录和角色居民档案表存基本信息和档案编号后面三张通过居民 ID 外键关联。-- 创建数据库字符集必须用 utf8mb4 CREATE DATABASE health_record DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE health_record; -- 用户表区分管理员、医生、居民三种角色 CREATE TABLE sys_user ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, real_name VARCHAR(50), role TINYINT DEFAULT 3 COMMENT 1管理员 2医生 3居民, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 居民健康档案主表 CREATE TABLE health_archive ( archive_id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, id_card VARCHAR(18) UNIQUE, name VARCHAR(50) NOT NULL, gender TINYINT COMMENT 1男 2女, birth_date DATE, phone VARCHAR(20), address VARCHAR(200), blood_type VARCHAR(10), allergy_history TEXT, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES sys_user(user_id) ); -- 就诊记录表 CREATE TABLE visit_record ( visit_id INT PRIMARY KEY AUTO_INCREMENT, archive_id INT NOT NULL, doctor_id INT, visit_date DATE, diagnosis VARCHAR(500), prescription TEXT, remark VARCHAR(500), FOREIGN KEY (archive_id) REFERENCES health_archive(archive_id) );建表时几个参数要盯住id_card加唯一索引防止重复建档allergy_history用 TEXT 因为过敏史可能很长update_time用ON UPDATE CURRENT_TIMESTAMP让数据库自动维护修改时间省得在 Java 代码里手动 set。外键约束建议保留虽然有人为了性能去掉但毕业设计阶段数据量小外键能帮你提前发现脏数据。2.3 JDBC 连接配置与连接池JSP 里直接写DriverManager.getConnection是最简单的做法但每次请求都建连接页面一多就卡。常见做法是用 Druid 或者 C3P0 连接池。下面给一个不依赖框架的纯 JDBC 工具类适合不想引入额外依赖的场景。// DBUtil.java —— 数据库连接工具类 import java.sql.*; public class DBUtil { // MySQL 8.0 驱动类名5.1 版本用 com.mysql.jdbc.Driver private static final String DRIVER com.mysql.cj.jdbc.Driver; // useSSLfalse 关闭 SSL 警告serverTimezone 必须指定否则时间差 8 小时 private static final String URL jdbc:mysql://localhost:3306/health_record ?useUnicodetruecharacterEncodingutf8mb4 useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 你的密码; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new RuntimeException(驱动加载失败检查 jar 包是否在 WEB-INF/lib 下, 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 ignored) {} try { if (stmt ! null) stmt.close(); } catch (SQLException ignored) {} try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }serverTimezoneAsia/Shanghai这个参数是血泪经验不写的话存进去的时间会比实际少 8 小时排查半天以为是代码问题。useSSLfalse是为了消掉启动时那一堆 SSL 警告本地开发没必要开。驱动 jar 包必须放在WEB-INF/lib目录下放错位置 Tomcat 找不到类报ClassNotFoundException。3. JSP 页面与后端交互从登录到建档的完整链路3.1 登录模块Session 管理与角色跳转登录是第一个能验证环境是否跑通的模块。流程是JSP 表单提交用户名密码 → Servlet 查询数据库 → 匹配成功写入 Session → 根据角色跳转不同页面。这里最容易翻车的地方是中文乱码和 Session 超时。// LoginServlet.java —— 登录处理 WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 必须在获取参数前设置编码否则中文用户名乱码 req.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); String username req.getParameter(username); String password req.getParameter(password); String sql SELECT user_id, real_name, role FROM sys_user WHERE username? AND password?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); // 实际项目密码要加盐哈希毕业设计演示可用 MD5 ps.setString(2, MD5Util.encode(password)); ResultSet rs ps.executeQuery(); if (rs.next()) { HttpSession session req.getSession(); session.setAttribute(userId, rs.getInt(user_id)); session.setAttribute(realName, rs.getString(real_name)); int role rs.getInt(role); session.setAttribute(role, role); session.setMaxInactiveInterval(30 * 60); // 30 分钟超时 // 按角色跳转 if (role 1) resp.sendRedirect(admin/index.jsp); else if (role 2) resp.sendRedirect(doctor/index.jsp); else resp.sendRedirect(resident/index.jsp); } else { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); } } catch (SQLException e) { throw new ServletException(登录查询失败, e); } } }req.setCharacterEncoding(UTF-8)必须放在getParameter之前放后面无效这是 JSP 中文乱码最常见的坑。PreparedStatement用占位符而不是字符串拼接防 SQL 注入。Session 超时设 30 分钟太短答辩演示时容易掉线太长不安全。密码用 MD5 只是演示级别真实系统必须用 BCrypt 加盐。3.2 建档页面表单提交与数据校验建档页面是电子健康档案系统的核心功能。JSP 负责渲染表单Servlet 负责接收和入库。前端校验用 JavaScript 做第一层后端再用 Java 做第二层两层都不能省。!-- archive_add.jsp —— 建档表单片段 -- form action${pageContext.request.contextPath}/archive/add methodpost onsubmitreturn checkForm() label姓名/label input typetext namename idname required label身份证号/label input typetext nameidCard ididCard maxlength18 required label性别/label input typeradio namegender value1 checked男 input typeradio namegender value2女 label出生日期/label input typedate namebirthDate label血型/label select namebloodType option valueAA型/option option valueBB型/option option valueOO型/option option valueABAB型/option /select label过敏史/label textarea nameallergyHistory rows3/textarea button typesubmit提交建档/button /form script function checkForm() { var idCard document.getElementById(idCard).value; // 身份证 18 位正则最后一位可能是 X var reg /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/; if (!reg.test(idCard)) { alert(身份证号格式不正确); return false; } return true; } /script前端正则只做格式校验后端还要查身份证是否已存在。Servlet 里先SELECT COUNT(*) FROM health_archive WHERE id_card?大于 0 就返回「该身份证已建档」。这个逻辑不能只靠数据库唯一索引抛异常来处理因为异常信息对用户不友好。3.3 档案列表与分页查询档案列表页要处理分页数据量大了不能一次全查出来。MySQL 用LIMIT offset, size实现分页配合COUNT(*)算总页数。// ArchiveListServlet.java —— 分页查询核心逻辑 int page 1; int size 10; String pageStr req.getParameter(page); if (pageStr ! null !pageStr.isEmpty()) { page Integer.parseInt(pageStr); } int offset (page - 1) * size; String countSql SELECT COUNT(*) FROM health_archive; String listSql SELECT archive_id, name, gender, phone, blood_type, update_time FROM health_archive ORDER BY update_time DESC LIMIT ?, ?; try (Connection conn DBUtil.getConnection()) { // 先查总数 PreparedStatement countPs conn.prepareStatement(countSql); ResultSet countRs countPs.executeQuery(); countRs.next(); int total countRs.getInt(1); int totalPages (int) Math.ceil((double) total / size); // 再查当前页数据 PreparedStatement listPs conn.prepareStatement(listSql); listPs.setInt(1, offset); listPs.setInt(2, size); ResultSet rs listPs.executeQuery(); ListMapString, Object list new ArrayList(); while (rs.next()) { MapString, Object row new HashMap(); row.put(archiveId, rs.getInt(archive_id)); row.put(name, rs.getString(name)); row.put(gender, rs.getInt(gender) 1 ? 男 : 女); row.put(phone, rs.getString(phone)); row.put(bloodType, rs.getString(blood_type)); row.put(updateTime, rs.getTimestamp(update_time)); list.add(row); } req.setAttribute(archiveList, list); req.setAttribute(currentPage, page); req.setAttribute(totalPages, totalPages); req.getRequestDispatcher(archive_list.jsp).forward(req, resp); }LIMIT的 offset 从 0 开始第 1 页 offset 是 0第 2 页是 10别算错。ORDER BY update_time DESC让最近修改的档案排前面符合实际使用习惯。分页参数page要做边界检查小于 1 归 1大于总页数归总页数否则会查出空列表或者报错。4. 避坑与排查这套技术栈最容易翻车的五个地方4.1 中文乱码从请求到响应到数据库的三层排查现象页面显示「??????」或者「测试」这种乱码。原因通常有三层JSP 页面pageEncoding没设、Servlet 请求编码没设、数据库连接字符集没设。解决顺序是从上往下查JSP 头部加% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Servlet 里req.setCharacterEncoding(UTF-8)放在第一行JDBC URL 加characterEncodingutf8mb4数据库和表都用utf8mb4。四层全对齐才不会乱。4.2 404 错误路径和 web.xml 的对应关系现象访问/login报 404。原因一般是WebServlet(/login)注解没生效或者web.xml里url-pattern写错或者项目没有部署到 Tomcat 的webapps目录。检查步骤先看 Tomcat 启动日志有没有Deployment of web application archive成功再看访问路径是否带了项目名如http://localhost:8080/health/login最后确认WEB-INF/classes下有编译后的.class文件。IDEA 里还要检查 Artifact 配置有没有把 lib 包进去。4.3 数据库连接超时连接池参数与 MySQL 等待时间现象系统跑一段时间后报Communications link failure。原因是 MySQL 默认wait_timeout是 8 小时连接池里的空闲连接被服务端断开了但客户端不知道。解决JDBC URL 加autoReconnecttrue或者连接池配置validationQuerySELECT 1和testWhileIdletrue。Druid 的话设testWhileIdletrue、timeBetweenEvictionRunsMillis60000每分钟检测一次空闲连接。4.4 JSP 编译报错jar 包冲突与重复类现象Tomcat 启动报java.lang.NoClassDefFoundError或者ClassCastException。原因通常是WEB-INF/lib下放了多个版本的同一个 jar比如servlet-api.jar和 Tomcat 自带的冲突。解决servlet-api.jar和jsp-api.jar不要放进WEB-INF/libTomcat 已经提供了。用 Maven 的话把这两个依赖的 scope 设为provided。检查lib目录有没有重复的mysql-connector不同版本。4.5 表单重复提交刷新页面导致重复建档现象用户提交建档后按 F5 刷新数据库多了一条重复记录。原因是表单提交后直接forward到结果页刷新时浏览器重新提交了 POST 请求。解决提交成功后用resp.sendRedirect()重定向到列表页这是 PRGPost-Redirect-Get模式。或者在 Session 里放一个 token提交时校验用完即删。5. 进阶技巧让这套系统在答辩时更稳的几个细节5.1 用 Filter 统一处理编码和登录拦截每个 Servlet 都写一遍setCharacterEncoding太啰嗦用 Filter 统一处理。同时登录拦截也放 Filter 里没登录的请求直接踢回登录页。// EncodingFilter.java —— 编码 登录拦截 WebFilter(/*) public class EncodingFilter implements Filter { // 不需要登录就能访问的路径 private static final String[] WHITE_LIST {/login, /login.jsp, /css/, /js/}; public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); String uri request.getRequestURI(); String path uri.substring(request.getContextPath().length()); // 白名单直接放行 for (String white : WHITE_LIST) { if (path.startsWith(white)) { chain.doFilter(req, resp); return; } } // 检查 Session if (request.getSession().getAttribute(userId) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }WebFilter(/*)拦截所有请求白名单里的路径放行。注意静态资源路径要放行否则 CSS 和 JS 加载不了页面样式全丢。request.getContextPath()拿到项目名重定向时拼上避免路径错误。5.2 数据库备份与一键初始化脚本答辩前最怕数据库被改乱。准备一个init.sql包含建库、建表、插入测试数据演示前直接执行一遍恢复初始状态。测试数据要覆盖各种情况有过敏史的、没过敏史的、不同血型的这样演示查询和统计功能时不会空屏。# 命令行执行初始化脚本 mysql -u root -p init.sql # 备份当前数据 mysqldump -u root -p health_record backup_$(date %Y%m%d).sqlmysqldump备份时加--single-transaction参数可以在不锁表的情况下备份 InnoDB 表演示环境数据量小无所谓但养成习惯没坏处。恢复用mysql -u root -p health_record backup.sql。5.3 答辩演示的检查清单演示前按这个顺序过一遍Tomcat 能启动无报错、登录三种角色都能进、建档能提交且列表能看到、修改档案后update_time变了、删除有二次确认、分页能翻页、退出登录后直接访问内页会被踢回登录页。这几项过了基本功能就稳了。我自己的习惯是答辩前一天把整个流程走三遍每遍都从「关掉 Tomcat → 启动 → 登录 → 建档 → 查询 → 退出」完整走中间不跳步骤。有一次就是跳过了退出登录这步答辩时老师正好点了退出再访问内页结果没做拦截直接进去了当场被问住。这套 JavaJSPMySQL 的组合虽然老但把编码、路径、Session、分页这几个点吃透后面换 Spring Boot 也是同样的思路。希望帮到你。本文还有配套的精品资源点击获取