news 2026/10/7 18:22:31

JSP+Servlet+MySQL宠物诊所系统源码解析:部署、避坑与优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JSP+Servlet+MySQL宠物诊所系统源码解析:部署、避坑与优化

简介:一套基于JSP与SQL数据库的『爱心宠物诊所』系统完整源码包,面向Java Web课程设计、毕业设计以及宠物诊所信息化管理初学者。内容覆盖需求分析、MVC架构设计、数据库设计、前端页面实现等关键环节,重点展示了JSP动态页面与后台数据库之间的查询、添加、更新和删除操作;数据库侧设计有宠物、主人、就诊记录等核心数据表,通过外键关联维护数据关系,帮助理解关系型数据库的建模思路。压缩包内共446个文件,主体为JSP页面、Java源码、编译后的class文件与数据库相关脚本,同时包含Servlet控制器、JavaBeans业务对象、数据库连接配置、备份文件及说明文档,整体约2.55MB,目录结构清晰,配合文章说明可快速上手部署与调试。已有334人学习下载,可用于课程设计参考、完整项目结构梳理、系统设计流程分析,也可在本地部署运行后做二次功能扩展。

1. 一个基于 JSP + SQL 的宠物诊所系统源码包:先想清楚再动手下载

这套"爱心宠物诊所"系统,是 JSP + Servlet + DAO + MySQL 的典型课程设计工程,压缩包里装的是 .bak 备份源码和 .class 编译产物,没有完整可运行的工程目录。它要解决的事很具体:用网页完成添加主人、添加宠物、按名字搜索宠物、查看就诊记录这几条典型增删改查链路,数据落在三张表里并通过外键关联。适合三类人拿它上手——Java Web 课程设计还没着落的学生、想快速搭一个"能跑通"管理后台做验证的开发者、以及想从老式 JSP 工程里读懂 MVC 分层和 JDBC 写法的一线从业者。新手按文章步骤能还原出可运行系统,熟手可以直接跳到第 5 章避坑和第 6 章索引优化。

2. 拆源码包:从 .bak 备份到可运行工程,JSP + Servlet + DAO 怎么协作

2.1 压缩包里都有什么:.bak、.class 与页面文件各自的角色

打开压缩包第一眼会觉得乱,addowner.jsp.bak、addownerservlet.java.bak、addpet_jsp.class、petview_005fname_jsp.class 混在一起。其实命名已经把分工写明白了:.bak 只是把源码扩展名改掉的备份,把后缀还原成 .java 或 .jsp 就能被 IDE 和 Tomcat 正常识别;.class 是 JSP 或 Servlet 编译后的字节码,直接改它不现实,还原源码后重新编译才是正道。

文件角色在系统里干什么
addowner.jsp.bak视图层添加主人的表单页面
addownerDAO.java.bak数据访问层主人表的 JDBC 增删改查
addownerservlet.java.bak控制器接收表单请求、调 DAO、转发结果
addpetservlet.java.bak控制器添加宠物的请求入口
addpet_jsp.class编译产物添加宠物页面的字节码
petsearch.jsp.bak视图层按名字搜索宠物的页面
petview_005fname_jsp.class编译产物查看宠物详情页的字节码

这套系统的业务主线和压缩包里的文件对得上:先添加主人,再给这位主人添加宠物,最后在 petsearch.jsp 里按名字搜宠物、点进详情看就诊记录。文件数量不多,但覆盖了 JSP 项目里最重要的三个层次:页面、控制、数据。把这几个文件的位置摆对,整个工程就算立起来了。

2.2 MVC 在这个系统里到底谁干了谁

为什么教程反复强调 MVC,对着这套源码看最直观。JSP 页面要是塞满 Java 代码,改一个样式就要碰业务逻辑;业务逻辑全堆在 Servlet 里,一个请求一段大杂烩,后期维护会疯。这套系统采用经典三层划分:JSP 负责渲染表单和结果列表,Servlet 做参数接收与请求转发,DAO 封装 JDBC 把 SQL 送到数据库。

一个完整的"添加主人"流程长这样:浏览器提交 addowner.jsp 的表单;addownerservlet.java.bak 的 doPost 被触发,取出 name、phone、address 参数;Servlet 把参数组装成 Owner 对象交给 addownerDAO.insert();DAO 内部用 PreparedStatement 执行 INSERT;Servlet 根据执行结果转发回 addowner.jsp,并在页面上显示成功或失败提示。

这条链路里 JSP 不直接连数据库,Servlet 不写 SQL,DAO 不碰 HTML 标签。三层各管一段,谁改了哪一层,另外两层不用跟着动。这套源码最值得学的不是某段代码本身,而是这种"课程设计级别"的项目已经在按工程化的方式组织代码,熟悉这套分工之后再看 SpringMVC 的 Controller、Service、Mapper,思路几乎是平移的。

2.3 Servlet 的 doPost 里藏着请求处理的全部套路

把 addownerservlet.java.bak 还原成 .java 后,核心逻辑大致长这样:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 必须在读取任何参数之前设置编码,否则中文直接乱码 request.setCharacterEncoding("UTF-8"); String name = request.getParameter("name"); String phone = request.getParameter("phone"); String address = request.getParameter("address"); Owner owner = new Owner(); owner.setName(name); owner.setPhone(phone); owner.setAddress(address); OwnerDAO dao = new OwnerDAO(); boolean ok = dao.insert(owner); if (ok) { request.setAttribute("msg", "添加成功"); } else { request.setAttribute("msg", "添加失败"); } // forward 是服务器内部转发,浏览器地址栏不会变化 request.getRequestDispatcher("addowner.jsp").forward(request, response); }

逻辑说明:doPost 是 Servlet 处理 POST 请求的入口,只有表单 method="post" 时才会走到这里。请求里带中文时,request.setCharacterEncoding("UTF-8")必须放在读取参数之前,顺序反了参数已经按默认编码解过码,后面再怎么设置都救不回来。getParameter按表单里 input 的 name 属性取值,所以 JSP 输入框的 name 和这里必须严格一致,差一个字母就是 null。forward是服务器内部转发,浏览器地址栏不变,提示信息放在 request 属性里,目标 JSP 页面用 EL 表达式或 JSP 脚本取出来渲染。

这套动作是 JSP+Servlet 时代最标准的写法。之后学 SpringMVC 的@RequestParam和 Model 传参,本质是同一套流程换了写法而已。源码里如果连接信息直接写在 DAO 类里面,我一般建议顺手抽一个 DBUtil,第 4 章部署时会讲怎么改。

2.4 DAO 层 JDBC 是数据库操作的承重墙

DAO 里那段 JDBC 是整个系统唯一碰数据库的地方,写得好不好直接决定系统能不能跑、查得动不动。常规做法是用 PreparedStatement 预编译 SQL,避免字符串拼接带来的语法错误和注入风险:

public boolean insert(Owner owner) { Connection conn = null; PreparedStatement ps = null; try { // 驱动加载必须放在 getConnection 之前 Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/pet_clinic?useUnicode=true&characterEncoding=UTF-8", "root", "123456"); String sql = "INSERT INTO owner(name, phone, address) VALUES(?, ?, ?)"; ps = conn.prepareStatement(sql); ps.setString(1, owner.getName()); ps.setString(2, owner.getPhone()); ps.setString(3, owner.getAddress()); return ps.executeUpdate() > 0; } catch (Exception e) { e.printStackTrace(); return false; } finally { // 先关 Statement,再关 Connection,顺序不能反 if (ps != null) { try { ps.close(); } catch (Exception e) { } } if (conn != null) { try { conn.close(); } catch (Exception e) { } } } }

参数说明:Class.forName("com.mysql.jdbc.Driver")加载 JDBC 驱动,MySQL 8.0 环境要换成com.mysql.cj.jdbc.Driver,否则直接报 ClassNotFoundException。连接 URL 里的useUnicode=true&characterEncoding=UTF-8是给中文数据兜底的,少了它页面看着正常,库里全是问号。PreparedStatement 的占位符从 1 开始编号,setString 的下标和它一一对应,错一位执行时就报参数数量不匹配。executeUpdate()返回受影响行数,插入成功返回 1,所以返回值大于 0 就可以当成功判断。

finally 里关资源的顺序有讲究:先关 Statement 再关 Connection。反过来先关连接,Statement 句柄就悬空了,在小项目里不致命,一旦换到连接池场景就是连接泄漏。这套代码把连接信息硬编码在每个 DAO 里,十几个 DAO 类改起来很痛苦,后面部署章节我会建议把连接配置收敛到一个 DBUtil 里。

3. 数据库设计:三张表、外键级联与测试数据落库

3.1 宠物诊所最少需要几张表

顺着业务主线捋,三张表就够了:owner 表存主人档案,pet 表存宠物基本信息,visit_record 表存每次就诊的症状、诊断和用药。每张表的主键都用自增整型,字段类型按"够用但不过度设计"的原则来定。

表字段类型说明
ownerowner_idINT AUTO_INCREMENT主键
ownernameVARCHAR(50)主人姓名
ownerphoneVARCHAR(20)联系电话
owneraddressVARCHAR(100)住址
petpet_idINT AUTO_INCREMENT主键
petnameVARCHAR(50)宠物名
petspeciesVARCHAR(20)猫/狗/其他
petbreedVARCHAR(50)品种
petageINT年龄
petowner_idINT外键,指向 owner
visit_recordrecord_idINT AUTO_INCREMENT主键
visit_recordpet_idINT外键,指向 pet
visit_recordvisit_dateDATETIME就诊时间
visit_recordsymptomVARCHAR(200)症状
visit_recorddiagnosisVARCHAR(200)诊断
visit_recordmedicineVARCHAR(200)用药

owner 和 pet 是一对多,一个主人能养多只宠物;pet 和 visit_record 也是一对多,一只宠物可以看多次病。外键存在的意义是防止出现"查无此主人"的宠物档案和"查无此宠物"的就诊记录,数据脱了锚,页面统计全乱。

3.2 建表 SQL:从库到表一次建齐

下面脚本在 MySQL 里执行,库和表就齐了。注意执行顺序不能乱,先建 owner 才能建 pet 和 visit_record,外键引用的一定是已存在的表。

CREATE DATABASE pet_clinic DEFAULT CHARACTER SET utf8mb4; USE pet_clinic; CREATE TABLE owner ( owner_id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, phone VARCHAR(20), address VARCHAR(100) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 删主人时级联删除它名下的宠物 CREATE TABLE pet ( pet_id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, species VARCHAR(20), breed VARCHAR(50), age INT, owner_id INT, CONSTRAINT fk_pet_owner FOREIGN KEY (owner_id) REFERENCES owner(owner_id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 删宠物时,它的就诊记录也一起清掉 CREATE TABLE visit_record ( record_id INT AUTO_INCREMENT PRIMARY KEY, pet_id INT, visit_date DATETIME DEFAULT CURRENT_TIMESTAMP, symptom VARCHAR(200), diagnosis VARCHAR(200), medicine VARCHAR(200), CONSTRAINT fk_visit_pet FOREIGN KEY (pet_id) REFERENCES pet(pet_id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

逐行说明:AUTO_INCREMENT PRIMARY KEY让数据库自己分配 id,Java 代码里不用手动生成主键。NOT NULL加在业务必需字段上,主人姓名和宠物名不能为空。DEFAULT CURRENT_TIMESTAMP让 visit_date 在插入时自动取当前时间,省去在 Java 里拼日期字符串的麻烦。外键约束ON DELETE CASCADE的意思是:删除主人时,名下的宠物自动删除,宠物的就诊记录也自动清掉,不会留下孤儿行。这一点在课程设计答辩时经常被问到,建议把级联前和级联后的数据变化说清楚。

一定要用ENGINE=InnoDB,MyISAM 不支持外键约束,建了也不生效,这是新手最容易踩的一脚空。

3.3 翻车点:id 生成方式、时间字段和冗余字段

建表时有两个细节我每次都会盯。一是时间字段用 DATETIME 而不是 TIMESTAMP,DATETIME 不操心时区偏移,也不会有 2038 年问题;二是不要在 pet 表里冗余一个"主人姓名"字段,需要显示时用 JOIN 把 owner.name 带出来。我看到不少人往业务表里塞了另一个表的名字字段,后面改地址要改两处,纯属给自己挖坑。

id 的生成交给数据库自增,别用 Java 代码生成 UUID 当主键,单表几万条数据时插入性能差距明显,外键关联也会非常难写。这套系统主键全部走自增,查询定位用 owner_id 直接用数值,简单直接。需要展示有哪些宠物物种时,一条SELECT DISTINCT species FROM pet就够,不必专门开一张物种表。

3.4 造测试数据:让页面第一次打开就有内容

数据库建完是空的,页面跑起来看不到任何效果。我会直接在脚本末尾补一段测试数据,这样部署完不用手动往库里插值就能走通全流程。

-- 两个主人 INSERT INTO owner(name, phone, address) VALUES ('张三', '13800000001', '幸福小区1号'), ('李四', '13800000002', '阳光花园2号'); -- 对应主人的宠物,owner_id 要和上面自增结果对应 INSERT INTO pet(name, species, breed, age, owner_id) VALUES ('旺财', '狗', '金毛', 3, 1), ('咪咪', '猫', '英短', 2, 1), ('豆豆', '狗', '泰迪', 1, 2); -- 旺财看了两次病,验证一对多查询 INSERT INTO visit_record(pet_id, symptom, diagnosis, medicine) VALUES (1, '食欲不振三天', '肠胃炎', '益生菌片'), (1, '呕吐一次', '吞食异物', '观察'), (2, '频繁打喷嚏', '上呼吸道感染', '喵感冒灵');

说明:pet 表的 owner_id 填 1 或 2,对应 owner 表的主键;visit_record 的 pet_id 指向 pet 表的旺财和咪咪,前两条记录都挂在旺财名下,正好演示一只宠物多次就诊的查询效果。测试数据之间能不能对上号,直接检验外键设计是否合理。这几个 INSERT 语句的字段顺序和表结构完全一致,如果你改过表结构,这里要同步调整。

4. 部署到 Tomcat:从备份文件到能点的增删改查页面

4.1 环境版本搭配:选对组合少走一半弯路

JSP+Servlet 是老技术栈,版本搭配不对能浪费一下午。本地复现这套源码时我用的是 JDK 1.8 + Tomcat 8.5 + MySQL 5.7 + mysql-connector-java-5.1.49 的组合,这几个版本互相兼容,报错最少。

如果你手上是 MySQL 8.0,有两处必须改:驱动类从com.mysql.jdbc.Driver换成com.mysql.cj.jdbc.Driver,连接 URL 末尾加serverTimezone=Asia/Shanghai,否则启动时直接报不支持 serverTimezone 或时区异常。如果用 Tomcat 10 以上,老工程的 javax.servlet 包整个迁移成了 jakarta.servlet,所有 import 都要改,不建议拿这套老代码去适配新容器,费劲还不讨好。

4.2 工程目录怎么摆:JSP、WEB-INF 和 classes 各归各位

Tomcat 对目录名不敏感,但 JSP+Servlet 的工程结构有约定。把源码包还原后的内容按下面结构整理,放在 webapps 下:

webapps/pet_clinic/ ├── addowner.jsp ├── addpet.jsp ├── petsearch.jsp ├── petview_name.jsp ├── WEB-INF/ │ ├── web.xml │ ├── lib/ │ │ └── mysql-connector-java-5.1.49-bin.jar │ └── classes/ │ ├── AddOwnerServlet.class │ ├── OwnerDAO.class │ ├── PetServlet.class │ └── PetDAO.class └── src/ ├── addownerservlet.java └── addownerDAO.java

说明:JSP 页面必须放在 webapps/pet_clinic 的可见目录,浏览器才能直接访问;WEB-INF 是受保护目录,外部访问不到,里面放 web.xml、lib 下的驱动 jar、classes 下的编译类。Tomcat 启动时先读 web.xml,再按配置加载 Servlet,lib 里的 jar 自动进入 classpath。src 目录放还原出来的 .java 源码,纯粹为了维护方便,不参与运行时加载。

4.3 Servlet 编译:把 .bak 还原成 .java 再出 .class

还原工程最机械也最容易出错的就是这一步。先把 .bak 后缀去掉,再编译:

# 备份文件还原成可编译的 .java cp addownerservlet.java.bak addownerservlet.java cp addownerDAO.java.bak addownerDAO.java cp addpetservlet.java.bak addpetservlet.java # 编译,-d 指定输出目录 javac -encoding UTF-8 -cp .:WEB-INF/lib/mysql-connector-java-5.1.49-bin.jar \ -d WEB-INF/classes addownerservlet.java addownerDAO.java addpetservlet.java

参数说明:-encoding UTF-8必须有,否则源码里的中文字符串常量编译后直接变乱码;-cp把当前目录和驱动 jar 都放进 classpath,编译 Servlet 时才会找到 javax.servlet 相关类;-d WEB-INF/classes指定编译输出目录,Tomcat 只扫描这个位置。编译报错先看是不是 import 缺失,再看类名和文件名是否一致,Java 对公共类名和文件名一致性要求很死。如果源码带包名,javac 会按包名自动建目录,classes 下会生成对应的多级目录。

4.4 web.xml 映射:Servlet 类写好了,请求路径也要配上

编译完 Servlet 还差最后一步:在 web.xml 里声明 Servlet 并配置 url-pattern。表单的 action 写的是 /addowner,Servlet 已经在代码里,两边不配上,点提交就是 404。

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <servlet> <servlet-name>AddOwnerServlet</servlet-name> <servlet-class>AddOwnerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>AddOwnerServlet</servlet-name> <url-pattern>/addowner</url-pattern> </servlet-mapping> </web-app>

逻辑说明:servlet-name 是逻辑名,servlet-class 必须和实际类名完全一致,带包名时写全限定名。url-pattern 是浏览器请求的路径。JSP 页面里表单的 action 如果写的是 addownerservlet 而不是 /addowner,要以页面里的实际值为准,两边对齐才能请求到。一个系统里有几个 Servlet 就配几组这样的节点,风格完全一样。

4.5 数据库连接配置单独抽出来

源码里连接信息硬编码在 DAO,复现时我习惯先抽一个 DBUtil.java,把 URL、用户名、密码集中到一处:

public class DBUtil { // 三个常量收敛全系统的连接配置,改库改密码只动这里 private static final String URL = "jdbc:mysql://localhost:3306/pet_clinic?useUnicode=true&characterEncoding=UTF-8"; private static final String USER = "root"; private static final String PASSWORD = "你的数据库密码"; public static Connection getConnection() throws Exception { Class.forName("com.mysql.jdbc.Driver"); return DriverManager.getConnection(URL, USER, PASSWORD); } }

这段没什么花活,就是把散落在各个 DAO 里的连接参数收拢。改完记得先单独跑一次getConnection()验证,连不上库说明密码、库名、端口三者里至少有一个写错,趁早暴露比页面报错强。之后再想做得规范些,可以把这三个常量挪到 properties 文件里,用ResourceBundle读取,那是后话。

4.6 启动 Tomcat:日志才是第一现场

环境、目录、代码、配置全部就位,剩下就是启动验证。

# 启动 Tomcat cd /path/to/tomcat/bin ./startup.sh # 实时盯日志,报错第一现场在 catalina.out tail -f ../logs/catalina.out

浏览器访问http://localhost:8080/pet_clinic/addowner.jsp,把添加主人、添加宠物、搜索、看详情完整走一遍。这一轮通过,部署就算闭环。日志里出现 SQLException、ClassNotFoundException、字符集异常三类报错,分别回看 4.1、4.2、4.3 相关位置,基本都能定位。页面打不开先看 catalina.out,别急着清浏览器缓存。

5. 避坑清单:这套源码最常见的五个翻车点

做 JSP+SQL 课程设计踩过的坑,集中在编码、类加载、数据库连接和 SQL 安全四个方面,每条按现象、原因、解决三步走,照着排查能省大半天。

5.1 中文乱码:页面、请求、数据库三层编码必须一致

现象:addowner.jsp 里输入"张三",保存后页面显示"张?",或者数据库里直接变成"????"。

原因:JSP 页面没声明 UTF-8,Servlet 读参数时用默认编码解码,数据库连接 URL 没带 characterEncoding=UTF-8。三层编码只要有一层不一致,中文就保不住,这是老 JSP 项目里最经典的脸黑现场。

解决:JSP 顶部加<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>;Servlet 的 doPost 第一行加request.setCharacterEncoding("UTF-8");连接 URL 保持useUnicode=true&characterEncoding=UTF-8。三个位置统一成 UTF-8,中文问题一次根治。

5.2 ClassNotFoundException:jar 没进 lib,或 Tomcat 版本太新

现象:Tomcat 启动或点击按钮时,控制台报ClassNotFoundException: com.mysql.jdbc.Driver,或者报找不到 Servlet 类。

原因:MySQL 驱动 jar 没放到 WEB-INF/lib 下,或者编译后的 .class 不在 WEB-INF/classes 下。Tomcat 的类加载是有边界的,放错位置等于没放。另一种情况是老项目跑在 Tomcat 10 上,报NoClassDefFoundError: javax/servlet/ServletException,这是 javax 包改成了 jakarta,代码层面直接不兼容。

解决:确认 lib 目录下驱动 jar 存在;确认 classes 目录下有编译好的 DAO 和 Servlet;Tomcat 版本控制在 9 及以下。三个条件都满足,类加载问题基本不会再出现。

5.3 Access denied 或连接超时:密码写错、MySQL 没起来

现象:连接数据库时报Access denied for user 'root'@'localhost',或者等很久报Communications link failure。

原因:DAO 里硬编码的密码和你本地 MySQL 实际密码不一致;MySQL 服务本身没启动,3306 端口根本没在监听。后者表现就是连接卡住然后超时,和代码一点关系都没有。

解决:先用mysql -u root -p在命令行验证密码,再用lsof -i:3306(Windows 上用netstat -ano | findstr 3306)看端口是否在监听。两关都过了,再去改 DBUtil 里的密码。遇到链路故障先去查 MySQL 是否运行,别一头扎进代码里找半天。

5.4 搜索框输入 % 或单引号:典型的 SQL 注入入口

现象:petsearch.jsp 搜索框输入%,返回了全部宠物;输入' OR '1'='1直接抛 SQL 语法错误。

原因:搜索 SQL 用字符串拼接,用户输入被当成 SQL 片段执行,%成了 LIKE 通配符,单引号破坏了 SQL 结构。这是最经典的注入写法,在实际项目中数据无价,这条必须改掉。

解决:换成 PreparedStatement 占位符:

// keyword 来自搜索框,作为参数绑定,而不是拼进 SQL 字符串 String sql = "SELECT * FROM pet WHERE name LIKE ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, "%" + keyword + "%"); ResultSet rs = ps.executeQuery();

逻辑说明:keyword 被当作普通字符串参数绑定,数据库层面把它当字面值处理,里面的%和单引号不再有特殊含义,注入路径从根上断掉。源码包里的搜索页具体实现我没逐一打开验证,但凡是字符串拼接 SQL 的地方,都值得改成这种写法。这套系统能不能从"能跑"升级到"拿得出手",这一步最关键。

5.5 改了 Servlet 和 JSP,刷新还是老页面

现象:改完代码重新编译、重启了 Tomcat,浏览器刷新还是显示旧内容。

原因:Tomcat 默认不会热更新 webapps 下已加载的类,旧 .class 还在;浏览器端把页面缓存了,F5 有时候拿到的就是本地缓存。JSP 改了没生效,是 work 目录里的编译缓存没清。

解决:改 Servlet 后停掉 Tomcat,手动删掉 work/Catalina 目录再启动;浏览器做一次强制刷新,或者用无痕窗口测试。我一般做法是每次改完代码,把 stop、清 work、start 这三步走完整,再开浏览器验证,这套流程下来"改完没生效"基本绝迹。

6. 验收与进阶:黑盒验证、索引优化与两个实用小技巧

6.1 黑盒验证清单:别等展示时才发现页面假死

系统跑通后先别忙着加功能,按下面的清单过一轮黑盒测试,每条路径都真实走一遍:

功能操作预期结果
添加主人表单填张三/手机/地址提示成功,owner 表有记录
添加宠物选主人再填旺财/金毛/3岁pet 表新增,owner_id 正确
搜索宠物输入"旺"返回名字含"旺"的宠物
查看详情点宠物名显示档案与历史就诊记录
容错空表单直接提交有提示,不抛异常

每条路径跑完,把页面上看到的和数据库三张表里的数据对一遍,防止页面渲染的是缓存数据。这个阶段截图存好,后面写报告、做答辩都能直接用。

6.2 数据量上来之后:EXPLAIN 和索引

当宠物记录上千条,按名字搜索会明显变慢。先看执行计划再决定加不加索引:

EXPLAIN SELECT * FROM pet WHERE name LIKE '旺%';

EXPLAIN 输出里 type 列如果是 ALL,说明全表扫描;加上索引后变为 range,说明走索引了。对应的索引语句:

ALTER TABLE pet ADD INDEX idx_pet_name (name);

加完重新 EXPLAIN 对比一下,答辩时把这个过程讲出来,比干巴巴说"我加了索引"有说服力得多。就诊记录检索频率更高,visit_date 字段同样值得建索引。

6.3 两个低成本小技巧:分页与参数校验

第一个技巧是分页。宠物多了以后全部渲染在一页会非常卡,常见做法是 SQL 层做 limit 分页,每页 20 条:

int pageSize = 20; // 第 1 页 offset 是 0,第 2 页是 20,依次类推 int offset = (currentPage - 1) * pageSize; String sql = "SELECT * FROM pet LIMIT ?, ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, offset); ps.setInt(2, pageSize);

LIMIT 的两个参数分别是偏移量和数量,第一个问号从(currentPage-1)*pageSize算起,第二个问号固定 20。这样就能把一万条数据全查进内存的做法丢掉。

第二个技巧是在 Servlet 里做参数校验。空姓名、空手机号在 DAO 执行前拦下来,页面提示比数据库报错友好得多。两三行 if 判断的事,效果立竿见影。

从那以后我拆任何一套下载源码,第一件事永远是先建库、改连接配置、跑通一个最小闭环,再去看页面和代码。绕过数据库层面直接看前端,十有八九会被假象骗了。这套流程救过我不少次,希望你也能用上。希望帮到你。

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

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

DeepSeek Harness桌面端深度体验:安装、插件、Skill与模型接入全攻略

DeepSeek Harness 这工具我从命令行时代就开始用了&#xff0c;说白了就是个把 DeepSeek 的能力包装成自动化工作流的终端工具&#xff0c;插件、Skill、定时任务什么都能挂。所以当我听说它出了桌面端的时候&#xff0c;第一反应其实是有点懵的——命令行用得好好的&#xff0…

作者头像 李华
网站建设 2026/10/7 18:21:54

Tampermonkey用户脚本获取百度网盘直链,突破限速下载

简介&#xff1a;这是一份名为“百度网盘直接下载助手”的用户脚本&#xff0c;供 Tampermonkey 或 Greasemonkey 管理器加载&#xff0c;面向需要绕过百度网盘客户端与非会员限速、直接在浏览器中完成文件下载的普通用户和脚本爱好者。脚本会解析百度网盘页面中的文件信息&…

作者头像 李华
网站建设 2026/10/7 18:21:20

Codex软件工程智能体:从代码生成到Agent闭环的实践指南

Codex这几年在开发者圈子里的含义变了好几回。2021年它是能把注释变成Python函数的代码生成大模型&#xff1b;到了2025年&#xff0c;它已经成了一类能自己读仓库、改代码、跑测试、提PR的软件工程智能体。我从Codex模型时期一直用到现在&#xff0c;踩过不少坑&#xff0c;也…

作者头像 李华
网站建设 2026/10/7 18:21:00

短视频解析源码PHP实现:部署、二次开发与避坑指南

简介&#xff1a;一套可直接部署的短视频解析源码&#xff0c;面向需要批量获取视频链接、封面、标题、播放量及评论等数据的开发者、内容运营与数据分析人员。它通过调用短视频平台公开接口&#xff0c;简化数据采集流程&#xff0c;用户上传视频链接即可自动完成解析并输出关…

作者头像 李华
网站建设 2026/10/7 18:20:29

普通摄像头升级隐患巡检员:视觉大模型实战指南

1. 从“看得见”到“看得懂”&#xff1a;为什么普通摄像头需要一次身份升级绝大多数人装摄像头&#xff0c;图的就是“能录下来、能回看”。不管是家门口的POE摄像头&#xff0c;还是仓库角落那台宇视摄像头&#xff0c;甚至是拿树莓派加OV5647模块自己搭的简易监控&#xff0…

作者头像 李华
网站建设 2026/10/7 18:20:20

superpowers安装全攻略:从概念到落地的完整指南

1. 从“superpowers”这个热词说起&#xff1a;它到底指什么最近“superpowers”这个词在技术圈和效率工具圈里被反复提起&#xff0c;很多人第一次看到它是在某个开源项目的讨论区&#xff0c;或者是在朋友转发的一张截图里。有人把它当成一个插件&#xff0c;有人以为它是一个…

作者头像 李华