
简介这份资源是面向高校计算机相关专业学生与Java Web初学者的毕业设计实践包主题为基于JavaJSPMySQL的电子健康档案系统可用于课程设计、毕设选题或医疗机构信息化入门参考。压缩包共760个文件约17.76MB以js脚本、gif与png图片、html页面、jar依赖包、css样式为主另含jsp页面、java源码、class文件及xml、properties等配置覆盖前端交互、后端逻辑与数据库连接各层。已有131人学习下载。源码完整呈现MVC分层结构读者可据此掌握Servlet请求处理、JavaBeans数据封装、JSP动态页面渲染、JDBC增删改查与事务处理并理解用户认证授权、SQL注入防范、异常捕获及缓存索引等性能调优思路适合对照学习与二次开发。1. 电子健康档案系统从一份 JSP 课设到能跑通的 JavaWeb 项目很多计算机专业的同学在选题阶段会卡在同一个地方想做一个「看起来像正经系统」的题目又怕技术栈太新、资料太少、答辩被问穿。电子健康档案系统就是这类选题里比较稳的一个——业务场景清晰居民、医生、档案、就诊记录技术栈经典Java JSP MySQL资料检索量大出问题也容易搜到答案。但真正动手时你会发现网上能搜到的要么是只有几张截图的「成品展示」要么是跑不起来的半截代码中间那层「怎么把表建对、把 JSP 页面串起来、把增删改查写扎实」的落地细节反而没人讲透。这篇笔记就按一线开发的思路把基于 Java JSP MySQL 的电子健康档案系统从建库、分层、页面到排错完整走一遍。适合两类人一是正在做毕业设计、需要一套能讲清楚、能演示、能答辩的实现路径的同学二是刚接触 JavaWeb、想拿一个完整业务练手的初学者。我不会假设你手里已经有一份现成源码而是把「如果从零搭每一步该怎么做、参数怎么设、哪里最容易翻车」讲清楚你照着敲就能跑起来。2. 技术选型与数据库设计为什么这套组合还没过时2.1 Java JSP MySQL 在课设场景里的真实定位先说清楚这套技术栈的边界。JSP 是服务端页面技术本质是 Servlet 的封装浏览器请求进来Tomcat 把 JSP 编译成 Servlet执行后把 HTML 吐回去。它不像现在的前后端分离那样清爽但胜在「一个页面里既能写 Java 逻辑又能写 HTML」对课设这种规模十几个页面、几张表来说开发速度快、调试直观。选型上我一般这样定JDK 用 8 或 118 最稳Tomcat 7/8/9 都兼容Tomcat 用 8.5 或 9MySQL 用 5.7 或 8.0JDBC 驱动跟着 MySQL 版本走——5.7 用mysql-connector-java-5.1.x8.0 用mysql-connector-java-8.0.x。这里有个高频翻车点MySQL 8.0 的驱动类名是com.mysql.cj.jdbc.DriverURL 还要带时区和 SSL 参数很多人直接抄 5.7 的配置结果连不上库报Communications link failure或者时区错误。提示如果你的 MySQL 是 8.0JDBC URL 建议写成jdbc:mysql://localhost:3306/health_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseuseSSLfalse能避开本地开发时的 SSL 连接报错。为什么不用 Spring Boot不是不能用而是课设答辩时评委更看重你对「请求怎么进来、数据怎么落库」这条链路的理解。JSP Servlet JDBC 把这条链路暴露得很清楚你讲得明白反而加分。等你把这套跑通了再迁移到 Spring Boot MyBatis 就是顺水推舟的事。2.2 电子健康档案系统的核心表结构设计数据库设计是这个项目的骨架表建错了后面页面怎么写都别扭。电子健康档案系统的核心实体有四个居民用户、医生、健康档案、就诊记录。我一般会拆成五张表把「档案」和「就诊记录」分开因为一个人可以有多条就诊记录但档案信息血型、过敏史、既往病史相对稳定。下面是我常用的建表 SQL字段名和类型都按实际能跑的标准来-- 居民表登录和基本信息 CREATE TABLE resident ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT 密码存MD5, real_name VARCHAR(50) COMMENT 真实姓名, gender CHAR(2) COMMENT 性别, birth_date DATE COMMENT 出生日期, phone VARCHAR(20) COMMENT 联系电话, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 健康档案表一人一条存稳定健康信息 CREATE TABLE health_record ( id INT PRIMARY KEY AUTO_INCREMENT, resident_id INT NOT NULL UNIQUE COMMENT 关联居民一人一档, blood_type VARCHAR(10) COMMENT 血型, allergy_history VARCHAR(500) COMMENT 过敏史, past_history VARCHAR(500) COMMENT 既往病史, height DECIMAL(5,1) COMMENT 身高cm, weight DECIMAL(5,1) COMMENT 体重kg, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (resident_id) REFERENCES resident(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 就诊记录表一人多条 CREATE TABLE visit_record ( id INT PRIMARY KEY AUTO_INCREMENT, resident_id INT NOT NULL, doctor_name VARCHAR(50) COMMENT 接诊医生, visit_date DATE COMMENT 就诊日期, diagnosis VARCHAR(500) COMMENT 诊断结果, prescription VARCHAR(1000) COMMENT 处方, remark VARCHAR(500) COMMENT 备注, FOREIGN KEY (resident_id) REFERENCES resident(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表时有三个参数要特别注意。第一字符集统一用utf8mb4不要用utf8否则存中文姓名里的生僻字或者特殊符号会出问题。第二health_record的resident_id加了UNIQUE这是业务约束——一个人只能有一份档案插入前要先查重。第三外键约束在课设里建议保留虽然有人嫌它影响插入顺序但它能帮你在开发阶段就发现「插了一条没有对应居民的记录」这种脏数据。2.3 连接池与 JDBC 工具类的落地写法每次DriverManager.getConnection()都新建连接页面一多就会卡。课设里最省事的做法是写一个 JDBC 工具类把连接参数集中管理再配一个简单的连接池。如果不想引入 Druid 或 C3P0 这些额外依赖用 Tomcat 自带的连接池也行但配置稍麻烦。我一般直接上 Druid一个 jar 包搞定。public class DBUtil { private static DruidDataSource dataSource; static { dataSource new DruidDataSource(); dataSource.setDriverClassName(com.mysql.cj.jdbc.Driver); dataSource.setUrl(jdbc:mysql://localhost:3306/health_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse); dataSource.setUsername(root); dataSource.setPassword(你的密码); dataSource.setInitialSize(5); // 初始连接数 dataSource.setMaxActive(20); // 最大连接数 dataSource.setMaxWait(3000); // 获取连接超时毫秒 } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } // 释放资源注意关闭顺序ResultSet - PreparedStatement - Connection public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (ps ! null) ps.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }initialSize设 5、maxActive设 20对课设演示的并发量完全够用。maxWait设 3000 毫秒意思是连接池满了之后最多等 3 秒超时就抛异常——这个值别设太大否则页面会一直转圈你反而不知道是哪里卡住了。释放资源时顺序不能乱先关ResultSet再关PreparedStatement最后关Connection连接池的close()实际是归还连接而不是真关闭所以放心调。3. 从登录到档案管理JSP 页面的完整实现路径3.1 登录与 Session 校验的最小闭环登录是整个系统的入口也是答辩时最容易被要求现场演示的功能。流程很固定login.jsp提交表单到LoginServletServlet 查库比对密码成功就把用户信息塞进Session跳转到主页失败就带着错误信息回到登录页。先看登录页的表单部分注意action指向 Servlet 的映射路径form actionLoginServlet methodpost 用户名input typetext nameusername requiredbr 密码input typepassword namepassword requiredbr input typesubmit value登录 span stylecolor:red${errorMsg}/span /formServlet 里做三件事取参数、查库、写 Session。密码存的是 MD5比对时也要先对输入做 MD5。WebServlet(/LoginServlet) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password MD5Util.encode(req.getParameter(password)); // 输入先加密 String sql SELECT id, real_name FROM resident WHERE username? AND password?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); ResultSet rs ps.executeQuery(); if (rs.next()) { HttpSession session req.getSession(); session.setAttribute(residentId, rs.getInt(id)); session.setAttribute(realName, rs.getString(real_name)); resp.sendRedirect(index.jsp); } else { req.setAttribute(errorMsg, 用户名或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); } } catch (SQLException e) { throw new ServletException(e); } } }这里有两个关键点。第一用PreparedStatement而不是字符串拼接 SQL这是防 SQL 注入的基本功答辩时如果被问到「怎么防注入」这就是答案。第二登录成功后用sendRedirect而不是forward重定向能避免用户刷新页面时重复提交表单。Session 里存residentId而不是整个用户对象减少内存占用需要详细信息时再查库。3.2 健康档案的增删改查与分页档案管理是系统的核心业务。居民登录后能看到自己的档案医生角色能看到所有居民的档案列表。列表页涉及分页这是课设里必须有的功能点也是容易写乱的地方。分页的核心 SQL 是LIMIT加偏移量。假设每页 10 条第page页的偏移量是(page-1)*10public ListHealthRecord getRecordsByPage(int page, int pageSize) { ListHealthRecord list new ArrayList(); String sql SELECT h.*, r.real_name FROM health_record h JOIN resident r ON h.resident_id r.id ORDER BY h.update_time DESC LIMIT ?, ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, (page - 1) * pageSize); // 偏移量 ps.setInt(2, pageSize); // 每页条数 ResultSet rs ps.executeQuery(); while (rs.next()) { HealthRecord hr new HealthRecord(); hr.setId(rs.getInt(id)); hr.setRealName(rs.getString(real_name)); hr.setBloodType(rs.getString(blood_type)); hr.setAllergyHistory(rs.getString(allergy_history)); list.add(hr); } } catch (SQLException e) { e.printStackTrace(); } return list; }LIMIT的第一个参数是偏移量第二个是条数顺序不能反。ORDER BY h.update_time DESC让最近更新的档案排前面符合实际使用习惯。总页数需要单独查一次COUNT(*)然后在前端用 JSTL 的c:forEach渲染页码链接。新增和修改档案时要注意health_record表的resident_id唯一约束。新增前先查这个居民有没有档案有就提示「档案已存在请使用修改功能」没有才执行INSERT。修改用UPDATE ... WHERE resident_id?不要用id因为前端传的可能是居民 ID 而不是档案 ID用错字段会更新到别人的档案这是血泪教训。3.3 就诊记录的关联查询与时间处理就诊记录和居民是一对多关系列表页要显示居民姓名就得做关联查询。前面分页 SQL 里已经用了JOIN这里重点说时间字段的处理。MySQL 的DATE类型取出来是java.sql.Date直接塞进 JSP 显示会带.0或者格式不对我一般统一转成字符串。// 在实体类里用 String 接收日期避免格式问题 SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd); hr.setVisitDate(sdf.format(rs.getDate(visit_date)));如果visit_date可能为空rs.getDate()返回null直接sdf.format(null)会抛NullPointerException。稳妥写法是先判断Date d rs.getDate(visit_date); hr.setVisitDate(d null ? : sdf.format(d));新增就诊记录时日期从表单的input typedate取格式是yyyy-MM-dd用java.sql.Date.valueOf(String)直接转成 SQL 日期比SimpleDateFormat解析更省事也不会有时区偏移问题。这个细节很多人踩坑用new java.util.Date()再转java.sql.Date结果日期差一天就是因为时区处理不一致。4. 避坑与排查那些让课设卡三天的具体问题4.1 中文乱码从请求到响应的三层排查现象表单提交的中文姓名存进库变成???或者页面显示乱码。原因乱码可能出现在三个环节——请求编码、数据库连接编码、响应编码。JSP 页面默认用 ISO-8859-1 解码 POST 请求MySQL 表如果建的时候没指定utf8mb4也会出问题。解决三层都要设。第一每个 JSP 页面顶部加% page contentTypetext/html;charsetUTF-8 %。第二在 Servlet 取参数前调用req.setCharacterEncoding(UTF-8)或者干脆写个 Filter 统一处理。第三JDBC URL 里带characterEncodingutf8建表用utf8mb4。三层都对了乱码基本消失。4.2 数据库连接报错驱动版本与 URL 参数不匹配现象启动时报ClassNotFoundException: com.mysql.jdbc.Driver或者连接时Unknown system variable query_cache_size。原因前者是驱动类名写错MySQL 8.0 的驱动类是com.mysql.cj.jdbc.Driver带cj。后者是驱动版本和数据库版本不匹配用 5.1 的驱动连 8.0 的库或者反过来。解决先确认 MySQL 版本SELECT VERSION();再选对应驱动。8.0 的库必须用 8.0 的驱动URL 加serverTimezone和useSSLfalse。如果还报时区错把serverTimezone改成Asia/Shanghai或GMT%2B8。4.3 JSP 页面修改后不生效Tomcat 缓存与编译目录现象改了 JSP 代码刷新浏览器还是旧内容。原因Tomcat 会把 JSP 编译成 Servlet 放在work目录有时候时间戳没更新或者浏览器缓存了旧页面。解决先强制刷新浏览器CtrlF5。还不行就清理 Tomcat 的work/Catalina/localhost/你的项目名目录重启 Tomcat。如果用的是 IDEA检查是否开启了热部署没开的话每次改 JSP 都要重新部署。这个坑不致命但很烦养成「改完 JSP 先清 work 目录」的习惯能省不少时间。4.4 外键约束导致插入失败现象插入就诊记录时报Cannot add or update a child row: a foreign key constraint fails。原因visit_record的resident_id在resident表里不存在外键约束拦住了。解决插入前先确认resident_id有效。如果是测试数据先插居民再插记录。如果业务上确实允许「先建记录后关联居民」那就去掉外键约束但课设里不建议这么做外键能帮你发现逻辑错误。排查时直接SELECT * FROM resident WHERE id你传的值查不到就是这个问题。4.5 Session 失效导致操作跳回登录页现象登录后操作几个页面突然跳回登录页或者报NullPointerException取不到 Session 里的值。原因Session 默认 30 分钟过期或者 Tomcat 重启导致 Session 丢失。也可能是代码里某处调了session.invalidate()。解决在web.xml里把超时时间调长比如session-configsession-timeout60/session-timeout/session-config单位是分钟。取 Session 值前先判空为空就重定向到登录页而不是直接强转。这个习惯能避免大部分「莫名其妙跳登录」的问题。5. 让课设更耐问几个能加分的进阶技巧5.1 用 Filter 统一做登录校验和编码处理与其在每个 Servlet 里写一遍setCharacterEncoding和 Session 判空不如写一个 Filter 统一处理。这样代码干净答辩时也能讲出「过滤器链」的概念。WebFilter(/*) public class AuthFilter implements Filter { 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(); // 放行登录页、登录接口和静态资源 if (uri.endsWith(login.jsp) || uri.endsWith(LoginServlet) || uri.contains(/css/) || uri.contains(/js/)) { chain.doFilter(req, resp); return; } // 其余请求校验 Session if (request.getSession().getAttribute(residentId) null) { response.sendRedirect(login.jsp); return; } chain.doFilter(req, resp); } }WebFilter(/*)拦截所有请求放行名单里加上登录相关路径和静态资源。这样新增页面时不用再单独写校验Session 一失效自动跳登录。注意chain.doFilter在放行和校验通过时都要调漏掉的话请求就断了。5.2 密码加密与敏感信息处理课设里密码明文存储是硬伤答辩容易被问。用 MD5 加盐是最低成本的改进。写一个工具类public class MD5Util { public static String encode(String input) { try { MessageDigest md MessageDigest.getInstance(MD5); byte[] digest md.digest(input.getBytes(UTF-8)); StringBuilder sb new StringBuilder(); for (byte b : digest) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (Exception e) { throw new RuntimeException(e); } } }注册时存MD5Util.encode(password)登录时对输入做同样处理再比对。虽然 MD5 现在不算安全但课设场景够用而且你能讲出「为什么不存明文」这个点比什么都不做强。如果想让评委眼前一亮可以提一句「生产环境应该用 BCrypt 加盐哈希」显示你知道更好的方案。5.3 用 JSTL 替代 JSP 脚本片段JSP 里写% %脚本片段是课设常态但页面会很难看也不利于维护。用 JSTL 的c:forEach、c:if替代循环和判断页面清爽很多答辩时也能讲「MVC 分层」。% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table c:forEach items${recordList} varr tr td${r.realName}/td td${r.bloodType}/td td${r.allergyHistory}/td /tr /c:forEach /table需要引入jstl.jar和standard.jar两个包。items是后端request.setAttribute(recordList, list)传过来的集合var是循环变量。这样 JSP 里几乎不出现 Java 代码Servlet 负责取数据JSP 只负责展示职责分明。5.4 答辩前必做的自检清单最后分享一个我每次交付前都会走的自检流程用表格列出来你照着过一遍能避开大部分现场翻车检查项具体操作预期结果数据库连接启动项目访问登录页不报 500页面正常显示登录功能用正确和错误密码各试一次正确跳主页错误提示信息中文存储注册一个中文姓名查库库里显示正常中文非乱码分页边界访问第 1 页和最后一页不报错页码正确空数据删除所有记录后访问列表页显示「暂无数据」而非报错Session 失效清浏览器 Cookie 后操作跳回登录页不报空指针外键约束手动插一条无效 resident_id插入失败有明确报错这份清单里的「空数据」和「Session 失效」两项最容易被忽略但恰恰是答辩现场评委最爱点的。提前跑一遍心里有底。我自己做第一个 JavaWeb 项目时光是一个中文乱码就折腾了一下午最后发现是 JSP 页面忘了加charsetUTF-8。后来养成习惯新建任何 JSP 文件第一行先把 page 指令写全建表先把字符集定死连库先把 URL 参数配齐。这些看起来琐碎的动作省下的是后面几小时的排查时间。希望帮到你。本文还有配套的精品资源点击获取