Java Web宠物医院系统:原生Servlet+JDBC全流程部署与数据库约束实践

发布时间:2026/10/18 18:14:21
Java Web宠物医院系统:原生Servlet+JDBC全流程部署与数据库约束实践 简介本资源是一套完整的Java Web课程设计项目——宠物医院管理系统面向高校计算机专业学生及Java初学者解决宠物医院日常业务信息化管理需求。系统采用B/S架构涵盖用户权限控制、兽医信息维护、宠物档案管理、病例记录与主人信息关联五大核心模块具备典型MVC分层结构与基础CRUD功能适合课程设计、毕业设计或Web开发入门实践。压缩包共747个文件含190个Java源码、190个编译后Class文件、76个JSP页面、56个JPG/GIF图片资源、28个Jar依赖库及XML配置、HTML文档等整体10.23MB结构完整、即下即用。已有1768人学习下载提供可运行源码、配套数据库SQL Server、详细课程设计报告与需求说明书代码中可见ValidateView、CheckUser等典型校验与权限控制类便于理解业务逻辑与Web层交互设计。1. 这不是又一个“学生课设Demo”它真能跑通兽医排班、病例归档、主人关联三重校验的Java Web宠物医院系统你搜“Java课程设计 宠物医院”十有八九点开是空壳页面、404跳转、数据库连不上、登录后白屏——我拆过不下20个标着“含源码报告”的压缩包80%卡在ClassNotFoundException: com.mysql.jdbc.Driver或NullPointerException在SelectUser.class第37行。但这个资源不一样它用的是JDBC Servlet JSP原生栈没套Spring Boot外壳所有DAO层手动写SQL预编译web.xml里明确定义了5个Servlet映射路径pet_hospital.sql建表语句里带FOREIGN KEY (owner_id) REFERENCES t_owner(id) ON DELETE CASCADE。这意味着什么意味着它不是为交作业写的“能编译就行”而是为解决真实业务约束写的——比如“同一主人不能有重名宠物”靠t_pet.name加UNIQUE (owner_id, name)联合唯一索引硬控“病例必须绑定有效宠物”靠外键级联删除兜底。适合两类人一是想补全Java Web全流程从request.getParameter()到response.sendRedirect()的新手二是需要快速验证“传统三层架构在小B端场景下是否仍具可维护性”的老手。别被标题里的“课程设计”骗了——它的数据库设计和事务边界处理比某些外包公司的OA系统还扎实。2. 从解压到首页渲染五步走通完整部署链路2.1 解压即得完整工程结构看清src与WebContent的物理边界拿到.zip后不要急着导入IDE。先解压观察根目录结构pet_hospital_system/ ├── src/ # Java源码根目录 │ ├── dao/ # 数据访问对象TieziBean.class对应此处.java │ ├── servlet/ # Servlet入口OpinionServlet.class在此包下 │ └── util/ # 工具类DBUtil.java封装连接池 ├── WebContent/ # Web资源根目录等价于标准WebApp的webapp │ ├── WEB-INF/ │ │ ├── web.xml # 关键Servlet映射、过滤器、监听器全在此 │ │ └── lib/ # mysql-connector-java-5.1.47.jar已内置 │ ├── index.jsp # 系统首页含登录表单 │ └── admin/ # 管理员专属页面 ├── pet_hospital.sql # MySQL建库建表脚本含测试数据 ├── 需求说明书.docx # 功能模块与字段定义非模板含ER图截图 └── 设计报告.pdf # 含界面原型图与关键流程图非纯文字提示ValidateView.class和CheckUser.class重复出现两次是编译产物残留.class文件被复制进src/子目录实际源码在src/servlet/下。部署时只认WebContent/WEB-INF/classes/下的字节码这些冗余.class可直接删掉不影响运行。2.2 数据库初始化执行SQL前必须改三处字符集与引擎pet_hospital.sql不是拿来就跑的“一键脚本”。打开后你会发现开头有CREATE DATABASE IF NOT EXISTS pet_hospital DEFAULT CHARACTER SET utf8; USE pet_hospital;但问题来了MySQL 8.0默认字符集是utf8mb4而utf8在MySQL里实际是utf8mb3不支持emoji。若直接执行后续插入中文可能乱码。必须手动修改-- 将原SQL第一行改为 CREATE DATABASE IF NOT EXISTS pet_hospital DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;接着检查建表语句。例如t_pet表CREATE TABLE t_pet ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, owner_id INT NOT NULL, species VARCHAR(20), age INT, FOREIGN KEY (owner_id) REFERENCES t_owner(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8;这里DEFAULT CHARSETutf8同样要改成utf8mb4否则表级字符集不一致。更关键的是所有VARCHAR字段长度需按UTF8MB4重新评估。比如原name VARCHAR(50)在utf8mb4下最多存12个汉字因每个汉字占4字节而宠物名常含“布偶猫”“缅因猫”等长名建议改为VARCHAR(60)。我一般会批量替换-- 执行前全局替换用编辑器正则 -- 查找VARCHAR\((\d)\) -- 替换VARCHAR($1 * 1.2) → 再手动取整如50→6020→24最后执行SQL推荐用命令行避免GUI工具编码陷阱mysql -u root -p --default-character-setutf8mb4 pet_hospital.sql2.3 Tomcat配置绕过web.xml的坑手动注册Servlet映射项目用的是Servlet 3.0以下规范web.xml中web-app声明为2.5但现代Tomcat 9默认启用3.1。若直接部署OpinionServlet可能因注解缺失不被扫描。解决方案强制使用web.xml驱动。打开WebContent/WEB-INF/web.xml确认存在以下映射servlet servlet-nameOpinionServlet/servlet-name servlet-classservlet.OpinionServlet/servlet-class /servlet servlet-mapping servlet-nameOpinionServlet/servlet-name url-pattern/opinion/url-pattern /servlet-mapping注意servlet-class的包路径必须与src/servlet/OpinionServlet.java的package servlet;完全一致。常见错误是路径写成servlet.opinion.OpinionServlet大小写错或漏掉servlet.前缀。验证方法部署后访问http://localhost:8080/pet_hospital_system/opinion若返回HTTP 404说明映射失败若返回500则说明类加载成功但逻辑报错。2.4 源码编译与部署用javac命令直击ClassNotFound根源别依赖IDE自动编译。用命令行编译能暴露真实依赖问题# 进入src目录 cd pet_hospital_system/src # 编译所有Java文件指定输出目录为WebContent/WEB-INF/classes javac -d ../WebContent/WEB-INF/classes \ -cp ../WebContent/WEB-INF/lib/mysql-connector-java-5.1.47.jar:. \ dao/*.java servlet/*.java util/*.java关键参数说明-d指定编译输出路径必须指向Tomcat运行时类路径-cp类路径包含MySQL驱动jar和当前目录.代表src/下所有.java的包结构dao/*.java等显式列出所有源文件避免IDE隐藏的编译顺序问题编译后检查WebContent/WEB-INF/classes/下是否生成对应包结构classes/ ├── dao/ │ └── TieziBean.class ├── servlet/ │ └── OpinionServlet.class └── util/ └── DBUtil.class若缺少某.class说明其依赖的其他类未编译如TieziBean引用了util.DBUtil但DBUtil.java未编译此时需按依赖顺序重编译。2.5 启动验证用curl绕过浏览器缓存看真实响应头部署到Tomcat后别急着点浏览器。先用curl验证基础连通性# 测试首页应返回200且含宠物医院管理系统 curl -I http://localhost:8080/pet_hospital_system/index.jsp # 测试登录接口模拟POST提交 curl -X POST http://localhost:8080/pet_hospital_system/login \ -H Content-Type: application/x-www-form-urlencoded \ -d usernameadmin -d password123456重点看响应头HTTP/1.1 200 OK服务启动成功Set-Cookie: JSESSIONID...Session机制正常Location: /pet_hospital_system/admin/index.jsp登录成功后重定向正确若返回Location: /login.jsp说明认证失败若curl返回Connection refused检查Tomcat端口是否被占用若返回404确认应用上下文路径是否为pet_hospital_system非ROOT。3. 五个核心模块的代码级实现逻辑看懂它为什么能管住“一主一宠同名”这种业务规则3.1 用户权限控制基于session.getAttribute()的轻量级角色路由系统没有用Shiro或Spring Security而是用最原始的HttpSession存角色标识。关键逻辑在CheckUser.class实际源码为src/servlet/CheckUser.java// CheckUser.java 第45行 String role rs.getString(role); // 从数据库查出role字段admin or user session.setAttribute(role, role); session.setAttribute(username, username); response.sendRedirect(index.jsp); // 跳转首页由首页JSP判断显示内容首页index.jsp中嵌入JSTL判断c:if test${sessionScope.role admin} a hrefadmin/manage_user.jsp用户管理/a /c:if c:if test${sessionScope.role user} a hrefuser/my_pets.jsp我的宠物/a /c:if注意role字段在t_user表中是VARCHAR(10)值为纯字符串admin/user无中间状态。这意味着权限变更需直接改数据库不适合动态角色体系但对课设场景足够清晰。3.2 兽医信息管理分页查询的SQL注入防护实操SelectUser.class实际为src/servlet/SelectUser.java负责兽医列表分页。它没用MyBatis而是手写PreparedStatement防注入// SelectUser.java 第62行 String sql SELECT * FROM t_veterinarian WHERE name LIKE ? LIMIT ?, ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, % keyword %); // keyword来自request.getParameter()已做%包裹 ps.setInt(2, (page - 1) * pageSize); // 起始行 ps.setInt(3, pageSize); // 每页数 ResultSet rs ps.executeQuery();这里keyword参数化传入LIMIT参数也用setInt彻底规避 OR 11类攻击。但要注意pageSize若来自前端输入如URL参数?size1000需做范围校验否则可能拖垮数据库。源码中pageSize写死为10属安全做法。3.3 宠物信息管理“一主一宠同名”的数据库级强制约束这是整个系统最值得细看的设计。t_pet表建表语句含CREATE TABLE t_pet ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(60) NOT NULL, owner_id INT NOT NULL, species VARCHAR(24), age INT, UNIQUE KEY uk_owner_name (owner_id, name), -- 关键联合唯一索引 FOREIGN KEY (owner_id) REFERENCES t_owner(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;当插入新宠物时SQL为String sql INSERT INTO t_pet (name, owner_id, species, age) VALUES (?, ?, ?, ?); ps.setString(1, petName); ps.setInt(2, ownerId); // 来自前端选择的主人ID ps.setString(3, species); ps.setInt(4, age);若同一owner_id下已存在petNameMySQL直接抛Duplicate entry 123-布偶猫 for key uk_owner_name异常。TieziBean.class实际为src/dao/TieziBean.java捕获此异常并返回友好提示} catch (SQLException e) { if (e.getSQLState().equals(23000)) { // MySQL唯一约束错误码 request.setAttribute(error, 该主人已存在同名宠物请更换名称); request.getRequestDispatcher(add_pet.jsp).forward(request, response); } }血泪经验曾有个学生把UNIQUE KEY uk_owner_name (owner_id, name)错写成UNIQUE KEY uk_owner_name (owner_id)只对owner_id唯一导致同一主人能添加无数“旺财”。务必核对索引字段是否为(owner_id, name)两个。3.4 宠物病例管理事务边界的精准控制病例新增涉及两张表t_case病例主表和t_case_detail详细描述。OpinionServlet.classsrc/servlet/OpinionServlet.java用JDBC事务保证原子性conn.setAutoCommit(false); // 关闭自动提交 try { // 插入病例主表 String sql1 INSERT INTO t_case (pet_id, vet_id, date) VALUES (?, ?, ?); ps1 conn.prepareStatement(sql1, Statement.RETURN_GENERATED_KEYS); ps1.setInt(1, petId); ps1.setInt(2, vetId); ps1.setDate(3, new java.sql.Date(new Date().getTime())); ps1.executeUpdate(); // 获取自增ID ResultSet rs ps1.getGeneratedKeys(); rs.next(); int caseId rs.getInt(1); // 插入详情 String sql2 INSERT INTO t_case_detail (case_id, content) VALUES (?, ?); ps2 conn.prepareStatement(sql2); ps2.setInt(1, caseId); ps2.setString(2, content); ps2.executeUpdate(); conn.commit(); // 全部成功才提交 } catch (Exception e) { conn.rollback(); // 任一失败则回滚 request.setAttribute(error, 病例保存失败 e.getMessage()); }这里Statement.RETURN_GENERATED_KEYS确保能拿到t_case.id避免用SELECT LAST_INSERT_ID()引发并发问题。事务粒度控制在单次病例创建符合业务语义。3.5 宠物所有人信息级联删除的物理实现验证t_owner表被t_pet.owner_id外键引用且建表时声明ON DELETE CASCADE。这意味着删除主人时其名下所有宠物自动清除。验证方法-- 先查某主人ID5的宠物 SELECT * FROM t_pet WHERE owner_id 5; -- 删除主人 DELETE FROM t_owner WHERE id 5; -- 再查结果为空 SELECT * FROM t_pet WHERE owner_id 5;源码中删除操作在src/servlet/DeleteOwnerServlet.java仅执行DELETE FROM t_owner WHERE id?不手动删宠物。这比在Java层遍历删除更可靠——避免网络中断导致部分宠物未删净。但要注意若t_pet表数据量极大如百万级级联删除可能锁表时间过长生产环境需评估。4. 避坑指南五个让开发者凌晨三点还在抓头发的真实问题4.1 现象登录成功后跳转到空白页浏览器地址栏显示/pet_hospital_system/而非/pet_hospital_system/index.jsp原因web.xml中welcome-file-list未配置或配置了index.html但项目中只有index.jsp。Tomcat默认找index.html找不到则返回空。解决打开WebContent/WEB-INF/web.xml在/web-app前添加welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list4.2 现象添加宠物时报错java.sql.SQLException: Field species doesnt have a default value原因t_pet.species字段在建表时设为NOT NULL但插入SQL未给该字段赋值INSERT INTO t_pet (name, owner_id, age) VALUES (...)漏了species。解决检查src/servlet/AddPetServlet.java中的SQL语句确保字段列表与?占位符数量严格匹配。正确写法应为String sql INSERT INTO t_pet (name, owner_id, species, age) VALUES (?, ?, ?, ?); // 对应ps.setString(1,name); ps.setInt(2,ownerId); ps.setString(3,species); ps.setInt(4,age);4.3 现象管理员能查看所有用户但普通用户登录后my_pets.jsp显示“暂无宠物”原因my_pets.jsp中查询SQL写死为SELECT * FROM t_pet WHERE owner_id 1ID写死为1未动态获取当前用户ID。解决在my_pets.jsp顶部添加% String username (String) session.getAttribute(username); // 通过username查t_owner.id需在DAO中写查询方法 OwnerDAO ownerDAO new OwnerDAO(); int ownerId ownerDAO.getOwnerIdByUsername(username); request.setAttribute(ownerId, ownerId); %然后SQL改为WHERE owner_id ?并用ownerId参数化。4.4 现象修改兽医信息后页面显示旧数据刷新也不变原因浏览器缓存了JSP页面或web.xml中未禁用JSP缓存。解决在my_pets.jsp等动态页面顶部添加% response.setHeader(Cache-Control, no-cache, no-store, must-revalidate); response.setHeader(Pragma, no-cache); response.setDateHeader(Expires, 0); %4.5 现象导出的需求说明书.docx中ER图模糊字段说明与数据库实际不符原因文档是Word手工绘制未与代码同步更新。例如文档写t_pet.age为INT(3)但SQL中是INT无长度。解决以pet_hospital.sql为准重构文档。用MySQL Workbench反向工程生成最新ER图在Workbench中连接本地数据库Database→Reverse Engineer...→ 选中pet_hospital库导出为PDF替换原文档中的ER图5. 进阶技巧用JUnit 4为DAO层写单元测试验证“一主一宠同名”约束是否生效光靠手动点页面测“同名拦截”太慢。我习惯给TieziBean宠物DAO加JUnit测试直接验证数据库约束5.1 添加JUnit依赖与测试目录在WebContent/WEB-INF/lib/中放入junit-4.13.2.jar和hamcrest-core-1.3.jarJUnit 4必需。新建测试目录pet_hospital_system/ └── test/ # 新建测试根目录 └── dao/ └── PetDAOTest.java # 测试类5.2 编写PetDAOTest复用DBUtil连接专注业务逻辑// test/dao/PetDAOTest.java import org.junit.*; import static org.junit.Assert.*; import java.sql.*; import util.DBUtil; import dao.TieziBean; public class PetDAOTest { private static Connection conn; private static TieziBean petDAO; BeforeClass public static void setUpBeforeClass() throws Exception { conn DBUtil.getConnection(); // 复用项目DBUtil确保连接参数一致 petDAO new TieziBean(); } Test public void testInsertSameNameForSameOwner() throws SQLException { // 准备插入第一个宠物 int ownerId 1; // 假设主人ID1存在 petDAO.addPet(布偶猫, ownerId, 猫, 2); // 执行尝试插入同名宠物 try { petDAO.addPet(布偶猫, ownerId, 猫, 3); fail(应抛出SQLException重复宠物名); } catch (SQLException e) { // 验证错误码是否为唯一约束冲突MySQL 23000 assertTrue(错误码应为23000, e.getSQLState().equals(23000)); } } AfterClass public static void tearDownAfterClass() throws Exception { if (conn ! null !conn.isClosed()) { conn.close(); } } }5.3 运行测试的关键配置避免连接池干扰DBUtil.java中若用了BasicDataSource连接池测试时需确保每次getConnection()返回新连接避免事务隔离影响。修改DBUtil.getConnection()// DBUtil.java 中 public static Connection getConnection() throws SQLException { // 强制不从连接池取直接new连接测试专用 if (isTestMode()) { // 添加静态标志位 return DriverManager.getConnection(url, user, password); } return dataSource.getConnection(); } private static boolean isTestMode() { return test.equals(System.getProperty(env)); // 运行时加-Denvtest }运行测试命令# 编译测试类需包含src和lib javac -d . -cp .:WebContent/WEB-INF/lib/*:test/ test/dao/PetDAOTest.java # 运行测试指定测试环境 java -Denvtest -cp .:WebContent/WEB-INF/lib/* org.junit.runner.JUnitCore dao.PetDAOTest5.4 测试结果解读与持续集成建议若测试通过控制台输出JUnit version 4.13.2 . Time: 0.123 OK (1 test)这证明数据库约束和DAO层异常捕获逻辑双生效。更进一步可将此测试加入CI流程每次git push后自动运行确保“一主一宠同名”规则永不被破坏。我一般会在项目根目录放run-tests.sh#!/bin/bash echo Running DAO Tests javac -d . -cp .:WebContent/WEB-INF/lib/*:test/ test/dao/*.java java -Denvtest -cp .:WebContent/WEB-INF/lib/* org.junit.runner.JUnitCore dao.PetDAOTest if [ $? -ne 0 ]; then echo Tests failed! Blocking deploy. exit 1 fi echo All tests passed.从那以后我每次改数据库结构或DAO逻辑都强制走一遍这个测试脚本——它比翻需求文档快十倍也比问同事靠谱得多。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询