news 2026/10/1 22:33:00

Java+JSP+MySQL电子健康档案系统毕设源码拆解与实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java+JSP+MySQL电子健康档案系统毕设源码拆解与实战

简介:这是一套面向高校计算机相关专业毕业设计场景的电子健康档案系统完整源码,采用Java+JSP+MySQL技术栈实现,适合正在准备毕设的学生、需要Java Web实战案例的初学者,以及希望参考医疗信息化系统架构的开发者。压缩包共760个文件,约17.76MB,涵盖jsp页面、java源码、jar依赖、html与css样式、js脚本、xml配置及数据库文件等,构成从前端展示到后端逻辑再到数据存储的完整工程结构。项目围绕患者信息、病历记录、检查结果等模块展开,可帮助读者理解MVC分层、Servlet请求处理、JavaBeans数据封装、JDBC数据库操作以及用户认证与异常处理等关键知识点。目前已有131人学习关注,适合作为毕设选题参考、课程设计模板或Java Web入门练手项目,通过研读源码可快速掌握电子健康档案系统的整体实现思路与工程组织方式。

1. 一份能跑通的 JSP 毕设:电子健康档案系统源码拆解

很多同学做毕业设计时,卡住的不是"不会写代码",而是"不知道一个完整的 Java Web 项目长什么样"。这份基于 Java+JSP+MySQL 的电子健康档案系统源码,恰好补上了这一环——它不是零散的知识点堆砌,而是一个能跑起来的完整 Web 应用,涵盖患者信息管理、病历记录、检查结果录入等核心模块。适合正在做计算机毕业设计、需要一份可参考可复现项目的同学,也适合想从零理解传统 JSP 项目结构的 Java 初学者。拿到源码后,你要做的不是通读每一行,而是先跑通、再拆解、最后按需改造。

2. 环境搭建与项目导入:从 JDK 到 Tomcat 的完整链路

2.1 为什么传统 JSP 项目仍然值得学

现在新项目大多用 Spring Boot 甚至前后端分离,但传统 JSP 项目在毕业设计场景下依然有它的价值。原因很直接:结构透明。一个典型的 JSP+Servlet+JavaBeans 项目,请求从浏览器发出,经过 Servlet 处理,调用 JavaBeans 封装数据,再通过 JDBC 操作 MySQL,最后转发到 JSP 页面渲染——整条链路没有框架帮你"藏"逻辑,每一层你都能看到。

这对初学者来说是好事。你不需要先理解 Spring 的 IoC 容器和 AOP 代理,就能搞清楚一个 HTTP 请求到底经历了什么。而且很多学校的答辩老师对 JSP 项目更熟悉,讲解时沟通成本低。

从技术选型角度看,这套组合的依赖极少:JDK + Tomcat + MySQL + JDBC 驱动,四个东西配好就能跑。不像 Spring Boot 项目,光 Maven 依赖就要下载几十个包,网络稍差就卡住。

2.2 环境版本选择与安装要点

环境搭建是第一个拦路虎,版本不匹配是最高频的翻车原因。以下是我建议的版本组合:

组件推荐版本说明
JDK1.8(8u系列)兼容性最好,Tomcat 7/8/9 都支持
Tomcat8.5 或 9.0不要用 Tomcat 10,包名从 javax 变成 jakarta 会导致 Servlet 报错
MySQL5.7 或 8.05.7 更省心,8.0 需要改 JDBC 驱动类和连接串
JDBC 驱动mysql-connector-java 5.1.x 或 8.0.x与 MySQL 版本对应
IDEIntelliJ IDEA 或 EclipseIDEA 对 Web 项目支持更好

安装 JDK 后记得配环境变量。Windows 下新建JAVA_HOME指向 JDK 安装目录,然后在Path里加%JAVA_HOME%\bin。验证方式:

java -version javac -version

两条命令都能输出版本号才算配好。只配了java没配javac是常见疏漏,后面编译 Servlet 时会报"找不到 javac"。

MySQL 安装时注意字符集选utf8mb4,排序规则选utf8mb4_general_ci。如果安装时选了latin1,后面存中文患者姓名会变问号,这个坑我在帮人调毕设时见过不下五次。

2.3 导入项目与配置数据库连接

拿到源码压缩包后,先解压看目录结构。典型结构如下:

EHRSystem/ ├── src/ # Java 源码 │ ├── com/ehr/dao/ # 数据访问层 │ ├── com/ehr/model/ # JavaBeans 实体类 │ ├── com/ehr/servlet/ # Servlet 控制器 │ └── com/ehr/util/ # 工具类(数据库连接等) ├── WebContent/ 或 web/ # Web 资源 │ ├── WEB-INF/ │ │ ├── web.xml # 部署描述符 │ │ └── lib/ # 第三方 jar 包 │ ├── css/ # 样式文件 │ ├── js/ # JavaScript 文件 │ └── *.jsp # JSP 页面 └── sql/ # 数据库脚本

在 IDEA 中导入时选"从现有源创建项目",指定项目根目录,然后手动配置 Facets:添加 Web Facet,指向web或WebContent目录,web.xml路径设为WEB-INF/web.xml。接着在 Artifacts 中添加 Web Application Exploded,确保WEB-INF/lib下的 jar 包被包含进去。

数据库连接配置通常在com.ehr.util包下的工具类里,找到类似下面的代码:

// DBUtil.java - 数据库连接工具类 public class DBUtil { // 数据库驱动类,MySQL 5.7 用 com.mysql.jdbc.Driver // MySQL 8.0 用 com.mysql.cj.jdbc.Driver private static final String DRIVER = "com.mysql.jdbc.Driver"; // 连接地址:localhost:3306 是默认端口,ehr_db 是数据库名 // useUnicode=true 和 characterEncoding=utf-8 保证中文不乱码 private static final String URL = "jdbc:mysql://localhost:3306/ehr_db?useUnicode=true&characterEncoding=utf-8&useSSL=false"; private static final String USER = "root"; // 改成你自己的 MySQL 用户名 private static final String PASSWORD = "123456"; // 改成你自己的密码 public static Connection getConnection() throws Exception { Class.forName(DRIVER); // 加载驱动 return DriverManager.getConnection(URL, USER, PASSWORD); } }

这里有几个参数必须按你的实际环境改:URL中的数据库名要和你在 MySQL 里建的库名一致;USER和PASSWORD改成你本地的;MySQL 8.0 用户还要把驱动类改成com.mysql.cj.jdbc.Driver,并在 URL 末尾加上&serverTimezone=Asia/Shanghai,否则会报时区错误。

数据库脚本一般在sql/目录下,用 Navicat 或命令行导入:

mysql -u root -p ehr_db < sql/ehr_db.sql

导入后进 MySQL 确认表结构:

USE ehr_db; SHOW TABLES; -- 应该能看到 patient、medical_record、examination 等表 DESC patient; -- 查看患者表字段

2.4 部署到 Tomcat 并验证

在 IDEA 中配置 Tomcat Server:Run → Edit Configurations → 添加 Tomcat Local,指定 Tomcat 安装目录,在 Deployment 标签页添加 Artifact。启动后浏览器访问http://localhost:8080/EHRSystem/,如果看到登录页就说明部署成功。

如果报 404,先检查 Application context 是否设成了/EHRSystem;如果报 500 且堆栈指向数据库,回头检查DBUtil里的连接参数。这一步没有玄学,报错信息基本都能定位到具体原因。

3. 核心模块拆解:Servlet 请求流转与 JSP 页面渲染

3.1 从登录请求看 MVC 三层怎么串起来

登录功能是理解整个项目架构的最佳切入点。用户在login.jsp输入用户名密码,表单提交到某个 Servlet,Servlet 调用 DAO 查数据库,验证通过后跳转到主页。整条链路把 Model(JavaBeans + DAO)、View(JSP)、Controller(Servlet)三层都串了一遍。

先看登录页面的表单部分:

<!-- login.jsp - 登录表单 --> <form action="LoginServlet" method="post"> <label>用户名:</label> <input type="text" name="username" required /> <label>密码:</label> <input type="password" name="password" required /> <button type="submit">登录</button> </form>

表单的action指向LoginServlet,这个映射关系在web.xml里配置,或者用@WebServlet注解声明。method="post"保证密码不会出现在 URL 里。

对应的 Servlet 处理逻辑:

// LoginServlet.java - 处理登录请求 @WebServlet("/LoginServlet") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 设置编码,防止中文乱码 request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); // 2. 获取表单参数 String username = request.getParameter("username"); String password = request.getParameter("password"); // 3. 调用 DAO 层验证 UserDao userDao = new UserDao(); User user = userDao.login(username, password); // 4. 根据结果跳转 if (user != null) { // 登录成功,把用户信息存入 Session request.getSession().setAttribute("currentUser", user); response.sendRedirect("index.jsp"); // 重定向到主页 } else { // 登录失败,带回错误信息 request.setAttribute("errorMsg", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); } } }

这段代码有几个关键点值得注意。request.setCharacterEncoding("UTF-8")必须放在获取参数之前,否则中文参数会乱码。登录成功后用sendRedirect重定向而不是forward,是为了避免用户刷新页面时重复提交表单。失败时用forward保留请求域中的错误信息,JSP 页面可以通过${errorMsg}取出来显示。

DAO 层的实现:

// UserDao.java - 用户数据访问 public class UserDao { public User login(String username, String password) { User user = null; String sql = "SELECT * FROM user WHERE username = ? AND password = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setRole(rs.getString("role")); // 角色:admin/doctor/nurse } } } catch (Exception e) { e.printStackTrace(); } return user; } }

这里用了PreparedStatement而不是Statement,通过?占位符传参,能有效防止 SQL 注入。如果直接把用户输入拼接到 SQL 字符串里,输入' OR '1'='1就能绕过登录验证——这是毕设答辩时老师最爱问的安全问题之一。

3.2 患者信息管理模块的增删改查

患者信息管理是系统的核心功能,包含列表展示、新增、编辑、删除四个操作。这部分代码量大但模式统一,理解一个就能套用其余。

列表查询的 Servlet:

// PatientListServlet.java - 患者列表 @WebServlet("/PatientListServlet") public class PatientListServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // 获取搜索关键词(可选) String keyword = request.getParameter("keyword"); PatientDao patientDao = new PatientDao(); List<Patient> patientList; if (keyword != null && !keyword.trim().isEmpty()) { // 按姓名模糊查询 patientList = patientDao.searchByName(keyword.trim()); } else { // 查询全部 patientList = patientDao.findAll(); } // 把列表存入请求域,转发到 JSP 展示 request.setAttribute("patientList", patientList); request.getRequestDispatcher("patient_list.jsp").forward(request, response); } }

对应的 JSP 页面用 JSTL 标签遍历数据:

<!-- patient_list.jsp - 患者列表展示 --> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <table border="1"> <tr> <th>编号</th><th>姓名</th><th>性别</th><th>年龄</th><th>操作</th> </tr> <c:forEach items="${patientList}" var="p"> <tr> <td>${p.id}</td> <td>${p.name}</td> <td>${p.gender}</td> <td>${p.age}</td> <td> <a href="PatientEditServlet?id=${p.id}">编辑</a> <a href="PatientDeleteServlet?id=${p.id}" onclick="return confirm('确认删除?')">删除</a> </td> </tr> </c:forEach> </table>

<c:forEach>是 JSTL 的核心标签,items指定要遍历的集合,var是每次迭代的变量名。相比在 JSP 里写<% for(...) { %>脚本片段,JSTL 更清晰也更安全。删除操作加了confirm确认框,防止误点。

新增患者时需要注意表单字段和数据库表字段的对应关系。常见做法是先用request.getParameter逐个取值,封装成 Patient 对象,再调用 DAO 的insert方法。日期字段要用SimpleDateFormat转换,否则会报类型不匹配。

3.3 病历记录与检查结果的关联查询

电子健康档案系统的价值在于把患者的基本信息、病历记录、检查结果关联起来。这部分涉及多表查询,是理解数据库设计的好素材。

假设三张表的关系是:patient表存基本信息,medical_record表存病历(外键patient_id关联患者),examination表存检查结果(外键record_id关联病历)。查询某个患者的所有病历及检查结果时,需要 JOIN 操作:

// MedicalRecordDao.java - 关联查询 public List<MedicalRecord> findByPatientId(int patientId) { List<MedicalRecord> records = new ArrayList<>(); String sql = "SELECT mr.*, e.item_name, e.result_value " + "FROM medical_record mr " + "LEFT JOIN examination e ON mr.id = e.record_id " + "WHERE mr.patient_id = ? " + "ORDER BY mr.visit_date DESC"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, patientId); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { MedicalRecord record = new MedicalRecord(); record.setId(rs.getInt("id")); record.setDiagnosis(rs.getString("diagnosis")); record.setVisitDate(rs.getDate("visit_date")); // 检查结果可能为空(LEFT JOIN),需要判空 record.setExamItem(rs.getString("item_name")); record.setExamResult(rs.getString("result_value")); records.add(record); } } } catch (Exception e) { e.printStackTrace(); } return records; }

用LEFT JOIN而不是INNER JOIN,是因为有些病历可能还没录入检查结果,用 INNER JOIN 会把这些记录过滤掉。ORDER BY visit_date DESC让最近的就诊记录排在最前面,符合实际使用习惯。

4. 避坑与排查:部署和运行中最容易翻车的五个点

4.1 中文乱码:从页面到数据库的全链路排查

现象:JSP 页面输入的中文存到数据库变成???,或者从数据库读出来显示为乱码。

原因:乱码可能出现在三个环节——JSP 页面编码、Servlet 请求编码、数据库字符集。任何一环不是 UTF-8 都会出问题。

解决:逐层排查。JSP 页面头部加<%@ page contentType="text/html;charset=UTF-8" %>;Servlet 里第一行加request.setCharacterEncoding("UTF-8");数据库建库时指定CHARACTER SET utf8mb4;JDBC 连接串加useUnicode=true&characterEncoding=utf-8。四个地方都确认一遍,基本能解决 99% 的乱码问题。

4.2 ClassNotFoundException:JDBC 驱动找不到

现象:启动后访问页面报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver。

原因:mysql-connector-java.jar没有放到WEB-INF/lib目录下,或者 IDEA 的 Artifact 配置里没有包含这个 jar。

解决:确认WEB-INF/lib下有驱动 jar 包。如果用的是 IDEA,检查 Project Structure → Artifacts → 输出布局里是否包含了 lib 目录。另一个容易忽略的点是:MySQL 8.0 的驱动类是com.mysql.cj.jdbc.Driver,如果 jar 包是 8.0 版本但代码里写的还是旧类名,同样会报这个错。

4.3 Tomcat 10 的 jakarta 包名不兼容

现象:项目在 Tomcat 8 上跑得好好的,换到 Tomcat 10 后所有 Servlet 报 404 或 500。

原因:Tomcat 10 把 Servlet API 的包名从javax.servlet改成了jakarta.servlet。老项目里import javax.servlet.*的代码全部失效。

解决:最省事的办法是换回 Tomcat 8.5 或 9.0。如果非要用 Tomcat 10,需要把所有javax.servlet替换成jakarta.servlet,同时确认web.xml的 schema 版本也要更新。毕业设计场景下没必要折腾这个,直接用 Tomcat 9 最稳。

4.4 数据库连接池耗尽:Connection 没关闭

现象:系统运行一段时间后报Too many connections,或者页面越来越慢直到无响应。

原因:DAO 层每次调用DBUtil.getConnection()都新建连接,但用完没有close()。MySQL 默认最大连接数是 151,每次请求泄漏几个连接,很快就把池子占满了。

解决:确保每个 Connection 都在 finally 块里关闭,或者用 try-with-resources 语法自动关闭。上面示例代码里用的就是 try-with-resources,Connection、PreparedStatement、ResultSet都会在代码块结束时自动关闭。如果项目里还有手写close()的地方,检查是否放在了 finally 里——放在 try 块末尾的话,一旦中间抛异常就执行不到了。

4.5 JSP 页面修改后不生效

现象:改了 JSP 文件,刷新浏览器还是旧内容。

原因:Tomcat 会缓存编译后的 JSP Servlet,有时候热部署没触发重新编译。

解决:先试 Ctrl+F5 强制刷新浏览器;不行就在 Tomcat 配置里把 On frame deactivation 设为 Update classes and resources;再不行就重启 Tomcat。另外注意,如果改的是web.xml或 Java 代码,必须重启 Tomcat 才生效,热部署只对 JSP 有效。

5. 二次开发与答辩加分:三个能拉开差距的改造方向

跑通项目只是起点,真正让毕设出彩的是你能在原有基础上做出有说服力的改造。以下三个方向投入产出比最高。

方向一:把密码明文存储改成 MD5 加盐哈希。原项目大概率是明文存密码,这在答辩时容易被质疑。改造方式是在用户注册和登录时对密码做哈希处理:

// PasswordUtil.java - 密码哈希工具 public class PasswordUtil { // 对密码做 MD5 加盐哈希 public static String hash(String password, String salt) { try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(salt.getBytes("UTF-8")); byte[] digest = md.digest(password.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); } } }

注册时生成随机盐值存入用户表,登录时用同样的盐值哈希后比对。这样即使数据库泄露,攻击者也无法直接拿到明文密码。答辩时能说清楚"为什么不能用 MD5 裸哈希"(彩虹表攻击)和"盐值的作用"(让相同密码产生不同哈希),就是加分项。

方向二:加一个操作日志表。在关键操作(新增患者、修改病历、删除记录)的 Servlet 里插入一条日志记录,包含操作人、操作时间、操作类型和操作对象。这个功能代码量不大,但能让系统显得更完整。答辩演示时展示"谁在什么时候改了哪条病历",比单纯说"我做了增删改查"有说服力得多。

方向三:用 Filter 统一处理编码和登录校验。原项目可能每个 Servlet 都写了一遍request.setCharacterEncoding("UTF-8"),而且没有登录拦截——不登录直接敲 URL 也能访问内部页面。加一个 Filter 就能统一解决:

// EncodingFilter.java - 编码与登录校验过滤器 @WebFilter("/*") public class EncodingFilter 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;charset=UTF-8"); String uri = request.getRequestURI(); // 放行登录页、登录请求和静态资源 if (uri.endsWith("login.jsp") || uri.endsWith("LoginServlet") || uri.contains("/css/") || uri.contains("/js/")) { chain.doFilter(req, resp); return; } // 检查是否已登录 if (request.getSession().getAttribute("currentUser") == null) { response.sendRedirect("login.jsp"); return; } chain.doFilter(req, resp); } }

这个 Filter 做了两件事:统一设置编码,以及拦截未登录请求。代码不长,但体现了你对"横切关注点"的理解——这类逻辑不应该散落在每个 Servlet 里。答辩时老师问"你的系统怎么做权限控制",这就是现成的答案。

最后说一个我自己的习惯:每次拿到别人的源码,第一件事不是读代码,而是先跑通,跑通之后用git init建一个本地仓库,把原始版本提交一次。之后每做一个改造就提交一次,改崩了随时能回退。这个习惯帮我省过很多次重头再来的时间。希望帮到你。

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

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

海明码与海明距离:数据链路层差错控制的核心原理与计算

网络传输没有什么是绝对可靠的。信号在介质里跑一圈&#xff0c;可能被电磁干扰、被噪声顶了一下&#xff0c;一个0就变成了1。数据链路层为了解决这种问题&#xff0c;引入了差错控制。而在这个话题里&#xff0c;最绕不开的两个词就是海明距离和海明码——一个告诉你编码的抗…

作者头像 李华
网站建设 2026/10/1 22:31:20

C++中关键字constexpr的实现示例

constexpr 是 C11 引入并在后续标准&#xff08;C14/C17/C20&#xff09;中持续增强的关键字&#xff0c;其核心作用是在编译期计算常量或表达式&#xff0c;既保证了编译期的安全性检查&#xff0c;又能消除运行期计算的开销&#xff0c;是实现“零成本抽象”和编译期元编程的…

作者头像 李华
网站建设 2026/10/1 22:30:21

后缀表达式求值:栈原理、中缀转逆波兰表达式完整解析

第一次在洛谷刷到P1449 后缀表达式时&#xff0c;我盯着这个名词愣了好一会儿。平时写惯了3*(5-2)7这种中缀式子&#xff0c;突然冒出一个把运算符全丢在后面的3.5.2.-*7.&#xff0c;第一反应是&#xff1a;这玩意儿真的是给人读的吗&#xff1f;不过也正是这道题&#xff0c;…

作者头像 李华
网站建设 2026/10/1 22:30:11

grep组合拳:高效排查大日志文件的实用技巧

上周三下午&#xff0c;我正在工位上改脚本&#xff0c;隔壁同事探过头来&#xff0c;一脸无奈&#xff1a;“哥&#xff0c;这个日志文件十几个 G&#xff0c;我用 VS Code 一打开就卡死&#xff0c;等半天只看到转圈。我把报错那几行复制给你行不行&#xff1f;”我说你先别急…

作者头像 李华
网站建设 2026/10/1 22:30:11

高校教材征订管理系统实战:Spring Boot+MyBatis从部署到订单汇总

简介&#xff1a;这份高校教材征订管理系统源码包面向计算机相关专业学生与Java初学者&#xff0c;提供一套可直接参考的课程设计完整实现&#xff0c;用于解决教材信息维护、学生选课订购、教师需求提交与订单统计等业务场景。压缩包共603个文件&#xff0c;约3.29MB&#xff…

作者头像 李华
网站建设 2026/10/1 22:30:09

OpenRig 实质:Codex 本地化代理与 YAML 策略引擎

1. OpenRig 是什么&#xff1a;一个被误读的开源项目代号OpenRig 这个词在当前技术社区里&#xff0c;正经历一场典型的“语义漂移”——它既不是官方发布的成熟产品&#xff0c;也不是某个知名开源组织背书的标准化工具&#xff0c;而更像是一组围绕Codex&#xff08;微软早期…

作者头像 李华