ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于JSP的宠物诊所管理系统:毕设选题与完整实现

基于JSP的宠物诊所管理系统:毕设选题与完整实现 简介这是一套面向计算机专业学生与Java Web初学者、课程设计或毕业设计参考者的宠物诊所管理系统完整资料基于JSP与SQL数据库实现围绕宠物资料、主人信息、就诊记录与药品库存等业务展开帮助读者理解动态网页与关系型数据库的交互方式。压缩包共446个文件约2.55MB以java源码、jsp页面、class编译文件为主另含jar依赖、xml配置、sql脚本与doc说明文档覆盖Servlet控制器、JavaBeans业务对象、数据库连接配置及建表语句等模块并保留部分bak备份便于对照修改。已有334人学习下载。资料包含可运行的JSP源码、数据库脚本与文章说明读者可据此掌握MVC分层思路、表间外键关联设计以及增删改查的实现方法适合作为Web开发入门与项目实战的参考范例。1. 宠物诊所系统为什么还在用 JSP一个被低估的毕设选题宠物诊所的日常运营里挂号、接诊、开处方、收费、疫苗记录、住院观察这些环节如果全靠纸质登记和 Excel 表格一周下来就会出现病历找不到、疫苗到期没人提醒、收费对不上账的情况。一个基于 JSP 和 SQL 数据库的宠物诊所系统本质上就是把这些环节搬到浏览器里用一张张表单和一条条 SQL 语句串起来。它不追求微服务架构也不依赖前端框架而是用最传统的 Java Web 技术栈解决一个真实存在的管理问题。这个选题特别适合两类人一类是正在找 Java Web 课程设计或毕设题目的学生另一类是想快速搭一个内部管理工具的小型诊所经营者。JSP 虽然被很多人说“过时”但它的学习曲线平缓、部署简单、资料丰富配合 MySQL 或 SQL Server 做增删改查两三天就能跑出一个能演示的版本。热搜里常出现的“jsp入门”“java课程设计案例源码”“基于jsp的毕设选题”恰恰说明这个方向的需求一直存在。接下来我会把整个系统的设计思路、数据库表结构、核心代码和部署步骤拆开讲清楚让你能照着复现。2. 系统功能拆解与数据库表设计从挂号到收费的完整链路2.1 角色划分与功能模块的对应关系宠物诊所系统通常涉及三种角色前台/护士、兽医、管理员。前台负责挂号、登记宠物信息、收费兽医负责接诊、填写病历、开处方管理员负责用户管理、药品库存、数据统计。在 JSP 里这三种角色通过 session 中的 role 字段区分登录后跳转到不同的主页面。常见做法是在 web.xml 里配置过滤器拦截未登录请求再根据角色决定是否放行某个 URL。功能模块可以拆成六个宠物档案管理、挂号预约、接诊病历、处方与药品、收费结算、统计报表。每个模块至少对应一张数据库表部分模块需要关联表。比如处方模块需要处方主表和处方明细表收费模块需要收费记录表和支付方式字典表。模块之间的数据流是宠物档案 → 挂号 → 接诊 → 处方 → 收费每一步都依赖前一步产生的 ID。2.2 数据库表结构设计与字段说明下面给出核心表的建表语句以 MySQL 为例SQL Server 只需把 AUTO_INCREMENT 换成 IDENTITYVARCHAR 换成 NVARCHAR。表名统一用英文小写加下划线避免中文表名带来的编码问题。-- 宠物主人表 CREATE TABLE owner ( owner_id INT PRIMARY KEY AUTO_INCREMENT, owner_name VARCHAR(50) NOT NULL, phone VARCHAR(20) NOT NULL, address VARCHAR(200), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 宠物表 CREATE TABLE pet ( pet_id INT PRIMARY KEY AUTO_INCREMENT, owner_id INT NOT NULL, pet_name VARCHAR(50) NOT NULL, species VARCHAR(20) COMMENT 犬/猫/其他, breed VARCHAR(50), birth_date DATE, weight DECIMAL(5,2), FOREIGN KEY (owner_id) REFERENCES owner(owner_id) ); -- 挂号表 CREATE TABLE registration ( reg_id INT PRIMARY KEY AUTO_INCREMENT, pet_id INT NOT NULL, doctor_id INT, reg_time DATETIME DEFAULT CURRENT_TIMESTAMP, status VARCHAR(20) DEFAULT 待接诊 COMMENT 待接诊/已接诊/已取消, FOREIGN KEY (pet_id) REFERENCES pet(pet_id) ); -- 病历表 CREATE TABLE medical_record ( record_id INT PRIMARY KEY AUTO_INCREMENT, reg_id INT NOT NULL, doctor_id INT NOT NULL, diagnosis TEXT, treatment TEXT, record_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (reg_id) REFERENCES registration(reg_id) ); -- 药品表 CREATE TABLE medicine ( med_id INT PRIMARY KEY AUTO_INCREMENT, med_name VARCHAR(100) NOT NULL, unit VARCHAR(20), price DECIMAL(10,2), stock INT DEFAULT 0 ); -- 处方明细表 CREATE TABLE prescription_detail ( detail_id INT PRIMARY KEY AUTO_INCREMENT, record_id INT NOT NULL, med_id INT NOT NULL, quantity INT NOT NULL, dosage VARCHAR(100), FOREIGN KEY (record_id) REFERENCES medical_record(record_id), FOREIGN KEY (med_id) REFERENCES medicine(med_id) ); -- 收费记录表 CREATE TABLE payment ( pay_id INT PRIMARY KEY AUTO_INCREMENT, reg_id INT NOT NULL, total_amount DECIMAL(10,2), pay_method VARCHAR(20) COMMENT 现金/微信/支付宝/刷卡, pay_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (reg_id) REFERENCES registration(reg_id) );字段设计上有几个关键点。owner 表的 phone 字段要加唯一索引防止同一个手机号重复建档。pet 表的 species 用 VARCHAR 而不是 ENUM方便后续扩展新物种。registration 表的 status 字段用中文值虽然不符合范式但在 JSP 页面直接显示时省去了映射转换这是很多课程设计里的常见做法。payment 表的 total_amount 在插入前由后台根据处方明细计算不要在前端算好再传否则容易被篡改。2.3 表之间的关联查询与索引建议最常用的关联查询是“根据挂号 ID 查宠物、主人、病历、处方、费用”。这条链路涉及五张表写 SQL 时用 LEFT JOIN 保证即使没有处方也能查出挂号信息。SELECT r.reg_id, p.pet_name, o.owner_name, o.phone, m.diagnosis, m.treatment, md.med_name, pd.quantity, pd.dosage, pay.total_amount, pay.pay_method FROM registration r JOIN pet p ON r.pet_id p.pet_id JOIN owner o ON p.owner_id o.owner_id LEFT JOIN medical_record m ON r.reg_id m.reg_id LEFT JOIN prescription_detail pd ON m.record_id pd.record_id LEFT JOIN medicine md ON pd.med_id md.med_id LEFT JOIN payment pay ON r.reg_id pay.reg_id WHERE r.reg_id ?;索引方面registration 表的 pet_id 和 reg_time 各建一个普通索引因为按宠物查历史和按日期查挂号量是高频操作。medical_record 表的 reg_id 建唯一索引一个挂号只对应一份病历。prescription_detail 表的 record_id 建普通索引。payment 表的 reg_id 建唯一索引。这些索引在数据量超过几千条后效果明显早期不建也能跑但养成习惯没坏处。提示如果使用 SQL Server把 AUTO_INCREMENT 改为 IDENTITY(1,1)把 DATETIME DEFAULT CURRENT_TIMESTAMP 改为 DATETIME DEFAULT GETDATE()。字段注释在 SQL Server 里需要用扩展属性单独设置建表时可以先不写 COMMENT。3. 用 JSP 和 Servlet 跑通核心业务从登录到开处方的代码实现3.1 项目目录结构与 web.xml 配置传统 JSP 项目的目录结构很固定用 IDEA 新建项目时选择 Java Enterprise → Web Application会自动生成 web 目录和 WEB-INF。我一般会再建几个包com.clinic.dao 放数据库操作com.clinic.servlet 放控制器com.clinic.model 放实体类com.clinic.util 放工具类。JSP 页面放在 web 根目录下按模块建子文件夹比如 /pet/、/registration/、/medical/。web.xml 里主要配置三样东西欢迎页、过滤器和 Servlet 映射。过滤器用来做登录校验和编码统一。web-app welcome-file-list welcome-filelogin.jsp/welcome-file /welcome-file-list filter filter-nameAuthFilter/filter-name filter-classcom.clinic.filter.AuthFilter/filter-class /filter filter-mapping filter-nameAuthFilter/filter-name url-pattern/*/url-pattern /filter-mapping filter filter-nameEncodingFilter/filter-name filter-classcom.clinic.filter.EncodingFilter/filter-class /filter filter-mapping filter-nameEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping /web-appEncodingFilter 里设置 request.setCharacterEncoding(UTF-8) 和 response.setContentType(text/html;charsetUTF-8)解决中文乱码。AuthFilter 里判断 session 是否有 user 对象没有就重定向到 login.jsp同时放行 login.jsp、login Servlet 和静态资源。注意过滤器的顺序EncodingFilter 要放在 AuthFilter 前面否则登录请求的中文参数会乱码。3.2 数据库连接工具类与连接池配置不要在每个 Servlet 里都写 DriverManager.getConnection那样连接数一多就崩。常见做法是用一个 DBUtil 类配合连接池。Tomcat 自带的 DBCP 或 Druid 都可以这里以 Druid 为例在 WEB-INF/lib 下放入 druid-1.2.x.jar然后在 src 下建 druid.properties。driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/pet_clinic?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai usernameroot passwordyour_password initialSize5 maxActive20 maxWait3000DBUtil 类用静态代码块加载配置并初始化 DataSource。public class DBUtil { private static DataSource dataSource; static { try { Properties props new Properties(); props.load(DBUtil.class.getClassLoader() .getResourceAsStream(druid.properties)); dataSource DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new RuntimeException(数据库连接池初始化失败, e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(Connection conn, Statement stmt, ResultSet rs) { // 依次关闭忽略异常 } }参数说明initialSize 是启动时创建的连接数maxActive 是最大连接数maxWait 是获取连接的超时毫秒数。本地开发 initialSize 设 2 就够maxActive 设 10。生产环境根据并发量调整但课程设计一般用不到。注意 url 里的 serverTimezone 参数MySQL 8 以上不写会报时区错误。3.3 挂号与开处方的 Servlet 实现挂号流程前台在 registration.jsp 填写宠物 ID 和医生 ID提交到 RegistrationServletServlet 调用 RegistrationDao.insert()然后重定向到挂号列表页。开处方流程兽医在 medical_record.jsp 填写诊断和治疗方案同时在页面下方动态添加药品行提交到 MedicalRecordServletServlet 先插入病历拿到 record_id再循环插入处方明细。protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int regId Integer.parseInt(request.getParameter(regId)); int doctorId (int) request.getSession().getAttribute(userId); String diagnosis request.getParameter(diagnosis); String treatment request.getParameter(treatment); String[] medIds request.getParameterValues(medId); String[] quantities request.getParameterValues(quantity); String[] dosages request.getParameterValues(dosage); Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); int recordId MedicalRecordDao.insert(conn, regId, doctorId, diagnosis, treatment); if (medIds ! null) { for (int i 0; i medIds.length; i) { PrescriptionDao.insert(conn, recordId, Integer.parseInt(medIds[i]), Integer.parseInt(quantities[i]), dosages[i]); } } RegistrationDao.updateStatus(conn, regId, 已接诊); conn.commit(); response.sendRedirect(medical/record_list.jsp); } catch (Exception e) { if (conn ! null) conn.rollback(); throw new ServletException(e); } finally { DBUtil.close(conn, null, null); } }这段代码的关键是手动事务控制。插入病历和处方明细必须在一个事务里否则病历插入成功但处方失败数据就不一致。conn.setAutoCommit(false) 开启事务所有操作成功后 commit任何一步抛异常就 rollback。注意 finally 里只关闭连接不关闭 Statement 和 ResultSet因为 DAO 方法内部已经关闭了。参数说明regId 来自挂号列表页的隐藏域doctorId 从 session 取medIds 和 quantities 是数组对应页面上动态添加的多行药品。3.4 收费结算的金额计算与页面展示收费页面需要根据 reg_id 查出所有处方明细计算总金额然后插入 payment 表。计算逻辑放在 Service 层不要放在 JSP 里。public BigDecimal calculateTotal(int regId) throws SQLException { String sql SELECT md.price, pd.quantity FROM prescription_detail pd JOIN medicine md ON pd.med_id md.med_id JOIN medical_record m ON pd.record_id m.record_id WHERE m.reg_id ?; BigDecimal total BigDecimal.ZERO; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, regId); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { BigDecimal price rs.getBigDecimal(price); int qty rs.getInt(quantity); total total.add(price.multiply(new BigDecimal(qty))); } } } return total; }用 BigDecimal 而不是 double避免浮点精度问题。页面展示时用 JSTL 的 fmt:formatNumber 保留两位小数。收费成功后把 registration 的 status 改为“已收费”防止重复收费。如果诊所支持医保或折扣可以在 payment 表加 discount 字段计算时先乘折扣再累加。注意JSP 页面里不要写 Java 代码块% %用 JSTL 和 EL 表达式。比如 ${pet.petName} 代替 %pet.getPetName()%。这样页面更干净也避免空指针直接抛到页面上。4. 部署与打包把 JSP 项目跑在 Tomcat 上的完整步骤4.1 开发环境搭建与依赖版本选择我一般用 JDK 8 或 JDK 11Tomcat 8.5 或 9.0MySQL 5.7 或 8.0。JDK 17 配 Tomcat 10 也可以但 Tomcat 10 把 javax.servlet 换成了 jakarta.servlet老代码直接跑会报 ClassNotFoundException。如果拿到的源码是 javax 包就老老实实用 Tomcat 9。IDEA 里配置 Tomcat 时在 Run → Edit Configurations 里添加 Tomcat Server → Local指定 Tomcat 安装目录然后在 Deployment 标签页添加 artifact。依赖 jar 包至少需要mysql-connector-java8.0 以上用 com.mysql.cj.jdbc.Driver、druid、jstl、standard。把这些 jar 放到 WEB-INF/lib 下IDEA 会自动识别。如果用 Maven在 pom.xml 里加对应依赖scope 设为 provided 的只有 servlet-api 和 jsp-api。4.2 数据库初始化与数据导入建库语句CREATE DATABASE pet_clinic DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 然后执行第 2 章的建表语句。初始数据至少要有一个管理员账号、两个医生账号、五个药品、三个宠物主人和宠物。管理员密码存 MD5 或 BCrypt不要存明文。如果源码里附带了 .sql 文件直接用 Navicat 或命令行导入。mysql -u root -p pet_clinic pet_clinic.sql导入后检查表数量和字段类型特别是 DATETIME 字段的默认值是否生效。如果报“Invalid default value”说明 MySQL 版本不支持 CURRENT_TIMESTAMP 作为 DATETIME 默认值改成 TIMESTAMP 或升级到 5.6.5 以上。4.3 打包 WAR 与部署到 Tomcat在 IDEA 里用 Maven 的 package 命令或 Build → Build Artifacts 生成 WAR 包。WAR 包放到 Tomcat 的 webapps 目录下启动 Tomcat 会自动解压。访问地址是 http://localhost:8080/项目名/login.jsp。如果 404检查 WAR 包名和 context path 是否一致。如果 500看 Tomcat 日志 catalina.out 里的堆栈通常是数据库连接失败或 jar 包缺失。# 在 Tomcat 的 bin 目录下 ./startup.sh # Linux/Mac startup.bat # Windows部署后先测登录再测挂号再测开处方和收费。每一步都看浏览器控制台和 Tomcat 日志。常见问题是中文乱码检查数据库连接 url 是否带了 characterEncodingUTF-8以及 Tomcat 的 server.xml 里 Connector 是否加了 URIEncodingUTF-8。4.4 从 JSP 迁移到前后端分离的边界判断如果诊所规模扩大JSP 的页面刷新体验会变差这时候可以考虑把后端改成 Servlet JSON前端用 Vue 或 React。但迁移成本不低需要把每个 JSP 拆成 API 接口和前端页面。我的建议是如果只是课程设计或小型诊所内部使用JSP 完全够用不要为了技术而技术。如果确实要迁移先迁移挂号查询和收费统计这两个高频模块病历和处方模块因为表单复杂可以最后迁。5. 避坑与排查JSP SQL 项目里最容易翻车的五个地方5.1 中文乱码从页面到数据库的全链路排查现象挂号页面输入“金毛”保存到数据库变成“金毛”或者页面显示问号。原因JSP 页面编码、request 编码、数据库连接编码、数据库表编码、Tomcat Connector 编码五处只要有一处不是 UTF-8 就会乱。解决JSP 顶部加 % page contentTypetext/html;charsetUTF-8 languagejava %EncodingFilter 里设 request.setCharacterEncoding(UTF-8)数据库 url 加 characterEncodingUTF-8建库时用 utf8mb4Tomcat server.xml 的 Connector 加 URIEncodingUTF-8。五处都改完重启 Tomcat。5.2 数据库连接泄漏连接池耗尽后的连锁反应现象系统跑一段时间后所有页面都卡住Tomcat 日志报“Cannot get a connection, pool exhausted”。原因DAO 方法里 Connection、Statement、ResultSet 没有在 finally 里关闭或者异常时跳过了关闭逻辑。解决用 try-with-resources 语法或者确保每个 DAO 方法都在 finally 里调用 DBUtil.close。连接池的 maxActive 不要设太大20 足够设 100 反而容易把数据库拖垮。5.3 SQL 注入登录框里的万能密码是怎么进来的现象登录页面输入 or 11 就能直接进系统。原因登录 SQL 用字符串拼接比如 SELECT * FROM user WHERE username name AND password pwd 。解决全部改用 PreparedStatement参数用 ? 占位。JSP 里的查询条件也要走 DAO不要在 JSP 里直接拼 SQL。如果源码里已经有拼接的写法逐个改成 PreparedStatement这是血泪教训不要心存侥幸。5.4 事务未回滚开处方时药品库存扣了但病历没存上现象兽医提交处方后药品库存减少了但病历列表里找不到记录。原因插入病历和扣库存是两个独立事务或者一个事务里部分操作失败但没有 rollback。解决把相关操作放在同一个 Connection 里setAutoCommit(false)全部成功后 commitcatch 里 rollback。注意 rollback 之前要判断 conn 是否为 null否则会抛空指针。5.5 Tomcat 版本与 javax/jakarta 包冲突现象项目在 Tomcat 9 能跑换到 Tomcat 10 启动就报 NoClassDefFoundError: javax/servlet/http/HttpServlet。原因Tomcat 10 把 Servlet API 的包名从 javax.servlet 改成了 jakarta.servlet老代码不兼容。解决要么换回 Tomcat 9要么把所有 import javax.servlet 改成 import jakarta.servlet同时更新 web.xml 的 schema 版本。如果源码里用了第三方框架还要看框架是否支持 jakarta。课程设计建议直接用 Tomcat 9省去迁移麻烦。6. 让系统更耐用的三个进阶技巧从能跑到好用第一个技巧是给挂号表加一个“叫号”字段。前台挂号后生成一个当日序号兽医页面按序号排序避免插队。实现方式是在 registration 表加 queue_no 字段插入时用 SELECT COUNT(*)1 FROM registration WHERE DATE(reg_time)CURDATE() 计算。注意并发下这个计算可能重复简单做法是加唯一索引然后重试或者用数据库自增但按日期重置。第二个技巧是疫苗到期提醒。在 pet 表加 last_vaccine_date 和 vaccine_interval 字段每天定时跑一个 SQL 查出到期宠物在管理员首页用红色列表展示。定时任务可以用 ServletContextListener 的 contextInitialized 里起一个 Timer或者用 Spring Task。课程设计里用 Timer 就够注意 Timer 线程要设为 daemon否则 Tomcat 关不掉。第三个技巧是收费统计的慢 SQL 优化。当 payment 表数据超过几万条按日期范围统计会变慢。给 pay_time 建索引查询时避免在字段上做函数运算比如 WHERE DATE(pay_time)2024-01-01 改成 WHERE pay_time 2024-01-01 AND pay_time 2024-01-02。如果还慢考虑按月建汇总表每天凌晨跑一次 INSERT INTO summary SELECT ...。-- 按月汇总收费 CREATE TABLE payment_summary ( summary_month VARCHAR(7) PRIMARY KEY, total_amount DECIMAL(12,2), visit_count INT ); INSERT INTO payment_summary SELECT DATE_FORMAT(pay_time, %Y-%m) AS m, SUM(total_amount), COUNT(*) FROM payment GROUP BY m ON DUPLICATE KEY UPDATE total_amount VALUES(total_amount), visit_count VALUES(visit_count);这段 SQL 用 ON DUPLICATE KEY UPDATE 实现幂等重复执行不会报错。参数说明summary_month 存“2024-01”这样的字符串total_amount 是当月总收入visit_count 是当月收费笔数。查询时直接查汇总表响应时间从秒级降到毫秒级。我自己的习惯是每次改完数据库表结构先备份一份 .sql 文件再改代码。JSP 项目最容易出现的问题是“代码改了但数据库没改”或者“数据库改了但 Tomcat 没重启”。所以改完一定要清 Tomcat 缓存、重启、重新部署。这个习惯帮我省了很多后悔药。希望帮到你。本文还有配套的精品资源点击获取
返回列表