拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

JSP+SQL宠物诊所系统全解析:数据库设计、代码链路与避坑指南

JSP+SQL宠物诊所系统全解析:数据库设计、代码链路与避坑指南

简介:毕业设计项目包基于JSP与SQL实现宠物诊所管理系统,面向Web开发初学者、计算机专业学生,也适合需要快速搭建管理后台的开发者。系统以真实业务场景为主线,覆盖用户注册登录、宠物档案维护、预约挂号、诊疗记录与费用管理等完整功能链路,是理解JSP页面交互、Servlet控制逻辑、JavaBean业务封装及JDBC数据库操作的典型实例。压缩包共445个文件,大小仅2.77MB,包含38个Java源文件、26个JSP页面、83个class编译产物,以及1份项目报告doc文档、若干jar依赖和XML配置;开发期间的备份文件(.bak、~等)也一并保留,便于追溯修改过程。代码采用MVC分层结构,视图层由JSP与EL表达式承担,模型层使用JavaBean操作宠物、用户、预约等表,控制层通过Servlet完成请求分发,数据库访问部分体现SQL增删改查与事务处理要点。目前已有96人学习,适合用于毕业设计参考、课程综合实训或就业项目复盘。对照源码和报告,可以快速掌握从需求分析、数据库设计到Web端编码落地的完整开发流程。

1. 这个毕设项目,为什么到2025年还有人抢着做

JSP+SQL的宠物诊所系统,听起来像是十年前的技术栈,但它在毕业设计选题里生命力极其顽强。原因很实在:它同时踩中了“技术难度适中”和“业务逻辑完整”两个关键点——JSP负责页面展示和请求转发,SQL负责数据存储和查询,一个宠物诊所需要的病例管理、预约挂号、药品库存、收费记录,恰好能把增删改查、多表关联、事务处理这些数据库核心操作全部覆盖一遍。你不需要去啃微服务、分布式这些在毕业设计里根本用不上的东西,却能稳稳地把JavaWeb开发的主线走通。

这套代码包适合三类人:JavaWeb刚入门、想用一个完整项目把Servlet+JSP+JDBC串起来的人;马上要答辩、需要一个能讲清楚“表结构为什么这么设计”的人;以及打算把这套本地项目改成Spring Boot版本作为进阶练手的人。我当年做类似项目时最深的体会是:项目本身不难,难的是你知不知道每一行代码为什么存在、每个SQL为什么这么写。这篇笔记就把这套系统的设计与实现从头到尾拆开,从环境搭建到数据库建模,再到代码链路和常见坑,全部按可复现的标准写。

2. 环境搭建与项目导入:先把“能跑起来”这件事做扎实

2.1 JDK、Tomcat、IDE和数据库的选型搭配

做JSP+SQL的毕设,最常见的搭配是JDK 8 + Tomcat 8.5/9.0 + IDEA(或Eclipse)+ SQL Server 2019(或MySQL 5.7)。为什么是这一套?因为JDK 8是目前JSP老项目兼容性最好的版本,Tomcat 8.5以上支持Servlet 3.1规范,IDEA的社区版就能完成全部开发。数据库方面,如果你们学校机房统一装SQL Server,那就沿着SQL Server走;如果自己电脑无所谓,MySQL 5.7对JSP的JDBC连接更友好——mysql-connector-java的驱动包直接丢进WEB-INF/lib就行。

提示:不要一上来就装最新版。JDK 17 + Tomcat 10.1的组合会让你的JSP项目因为javax.servlet改成jakarta.servlet包名而大面积报错,这不是你代码有问题,是规范升级导致的API迁移。毕设阶段别给自己找这个不痛快。

2.2 导入项目到IDEA:两个容易翻车的地方

拿到代码包后,我会先解压到纯英文路径下,比如D:\pet-clinic,路径里带中文会导致Tomcat的静态资源加载偶尔抽风。然后打开IDEA,选择File -> New -> Project from Existing Sources,选中解压后的目录,一路Next。到“选择项目类型”那一步,选“Import project from external model”再选Eclipse,因为很多毕设代码包本身是在Eclipse里写的,.classpath和.project文件都是Eclipse格式。如果你直接按Maven导入会报“找不到pom.xml”,这很正常,因为传统JSP项目根本不走Maven依赖管理,所有jar包都物理放在WEB-INF/lib下。

导入完成后,先检查三件事,顺序不能乱:

  • File -> Project Structure -> Project里确认SDK是1.8,Language Level是8。
  • File -> Project Structure -> Artifacts里确认有名为pet-clinic:war exploded的构建产物,没有就手动建一个,Type选Web Application: Exploded。
  • Run/Debug Configurations里新增Tomcat Server Local,Deployment选项卡里把pet-clinic:war exploded加进去,Application context改成/pet。

2.3 数据库连接配置:改一处就能连上你的库

整个项目里只需要改一个文件就能切换数据库连接,一般在src下的db.properties或DBUtil.java里。以下是一份最常见的配置写法:

# db.properties jdbc.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver jdbc.url=jdbc:sqlserver://localhost:1433;DatabaseName=pet_clinic jdbc.username=sa jdbc.password=123456

对应的DBUtil工具类长这样:

import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ResourceBundle; public class DBUtil { private static String driver; private static String url; private static String username; private static String password; static { ResourceBundle bundle = ResourceBundle.getBundle("db"); driver = bundle.getString("jdbc.driver"); url = bundle.getString("jdbc.url"); username = bundle.getString("jdbc.username"); password = bundle.getString("jdbc.password"); try { Class.forName(driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } }

这段代码的逻辑是:用ResourceBundle读取db.properties资源文件,在静态代码块里加载驱动类并注册到DriverManager——Class.forName(driver)这一步不能省,虽然JDBC 4.0之后驱动可以自动注册,但在老版本Tomcat里不写这一句偶尔会出现“No suitable driver found”的诡异报错。getConnection()每次调用都从连接池底层新建物理连接,这在毕设项目里是够用的,但你要心里有数:连接用完必须关,否则SQL Server默认连接数一满,整个网页就卡死。

提示:很多代码包里默认连的是SQL Server,而你自己电脑上装的是MySQL,这时把driver改成com.mysql.cj.jdbc.Driver,url改成jdbc:mysql://localhost:3306/pet_clinic?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8即可。注意MySQL 8的驱动类已经改名成cj结尾了,用老名字会报ClassNotFoundException。

3. 数据库设计:宠物诊所的表结构,决定你答辩时有没有底气

3.1 核心业务表:五张表就能讲完整条业务线

宠物诊所系统的数据模型不需要做得特别复杂,但要能自圆其说。我的建议是至少包含这五张核心表:用户表(owner/user)、宠物表(pet)、预约表(appointment)、病历表(medical_record)、药品表(medicine)。它们之间的关系是:一个用户(宠物主人)可以养多只宠物,一只宠物可以多次预约就诊,每次就诊产生一份或多份病历记录,一份病历里会涉及多种药品。

这里有一个很多毕设代码里做得不扎实的地方:病历和药品通常需要一张中间表record_medicine来记录“本次开了什么药、开了几份”,而不是在medical_record表里用一个medicine_ids字段塞一个逗号拼接的字符串。前者是标准的关系型数据库设计,后者在答辩时被老师一问就露馅。中间表结构如下:

CREATE TABLE record_medicine ( id INT IDENTITY(1,1) PRIMARY KEY, record_id INT NOT NULL FOREIGN KEY REFERENCES medical_record(id), medicine_id INT NOT NULL FOREIGN KEY REFERENCES medicine(id), quantity INT NOT NULL DEFAULT 1, dosage VARCHAR(50) );

在SQL Server里,IDENTITY(1,1)是自增字段的写法,等价于MySQL里的AUTO_INCREMENT。record_id和medicine_id都建外键约束,quantity记录用药数量,dosage记录“一天几次、一次几粒”之类的用法说明。这张中间表的存在非常关键——它一方面避免了重复数据,另一方面让你能在成绩单上展示“我做过数据库范式设计”。

3.2 主键策略和索引设计:别用UUID当主键,尤其是String类型

我见过太多毕设代码的主键用VARCHAR(32)存UUID字符串,理由是“全局唯一、不会冲突”,但这在JSP+SQL的老项目里是一场灾难。原因很简单:UUID作为字符串主键,长度32位,插入时索引树随机分裂,数据量到几千条时写入性能就开始劣化;更麻烦的是,你在JSP页面里拼URL时,一串32位的UUID出现在地址栏里既难看又容易拷贝出错。

正确做法是全部用INT IDENTITY自增主键,如果要对外暴露编号,可以在应用层加一个业务号字段,比如预约单号appointment_no用'AP' + yyyyMMdd + 三位流水号的格式生成。这样既保持主键的自增有序,在WHERE条件里查询appointment_no走普通索引就够了。索引方面,业务外键字段必须建索引——比如medical_record表里查“某只宠物所有病历”,如果你不在pet_id上建索引,这个查询每执行一次都是全表扫描,你写页面的时候不觉得,等你录了200条测试数据、用慢SQL日志一查就知道差距了。

CREATE INDEX idx_record_pet_id ON medical_record(pet_id); CREATE INDEX idx_appointment_pet_id ON appointment(pet_id); CREATE INDEX idx_record_medicine_record_id ON record_medicine(record_id);

3.3 初始化数据:没有测试数据,你连页面都调不好

建完表之后,要往数据库里灌一批“看起来真实”的测试数据。很多同学栽在这里:表建好了,页面也能打开,但列表全是空的,看起来像系统没开发完。我一般会写一个init_data.sql脚本,造5个用户、6只宠物、10条预约、15条病历记录,每个字段都填真实值而不是随便塞个xxx。病历记录的diagnosis字段建议写上“犬细小病毒感染”“猫藓感染”这类真实疾病名称,prescription字段写上对应药品。这样你去调JSP列表页的时候,一眼就能看出页面渲染对不对,答辩时演示也有说服力。

注意:写初始化数据时别只填英文或拼音。老师大概率会打开你的数据库看一眼,如果能直接看到“感冒”“肠胃炎”这类中文诊断,印象分会高很多。中文乱码问题出现在这一环节的几率很高,后面第5章会详细说怎么排查。

4. 从JSP页面到数据库:完整代码链路的每一步拆解

4.1 页面发起请求:JSP的表单提交和超链接传参

整套系统的开发模式是典型的JSP + Servlet + DAO三层。前端JSP页面负责展示数据、收集用户输入,Servlet负责接收请求、调用业务方法、完成页面跳转,DAO层用JDBC封装所有SQL操作。以“新增宠物预约”为例,JSP页面里的表单是这样写的:

<form action="${pageContext.request.contextPath}/appointment/add" method="post"> <input type="hidden" name="petId" value="${param.petId}" /> <select name="doctorId"> <c:forEach items="${doctorList}" var="doctor"> <option value="${doctor.id}">${doctor.name}</option> </c:forEach> </select> <input type="date" name="appointmentDate" required /> <input type="text" name="remark" placeholder="备注描述" /> <button type="submit">提交预约</button> </form>

表单提交到/appointment/add这个URL,method="post"是防止中文乱码的关键前提之一——Tomcat对GET和POST请求的字符集处理方式不一样,GET请求的参数编码在Tomcat 8.5之后默认才是UTF-8,而POST请求必须显式设置request.setCharacterEncoding("UTF-8")。这里用的是JSTL和EL表达式,${pageContext.request.contextPath}这一句必须写,否则你部署的应用(context path是/pet)和开发时的resource路径一旦不一致,全部请求都会404。

4.2 Servlet层:参数接收、业务调度、跳转控制

对应的AppointmentAddServlet核心逻辑:

@WebServlet("/appointment/add") public class AppointmentAddServlet extends HttpServlet { private AppointmentDAO appointmentDAO = new AppointmentDAO(); @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); int petId = Integer.parseInt(request.getParameter("petId")); int doctorId = Integer.parseInt(request.getParameter("doctorId")); String appointmentDate = request.getParameter("appointmentDate"); String remark = request.getParameter("remark"); Appointment appointment = new Appointment(); appointment.setPetId(petId); appointment.setDoctorId(doctorId); appointment.setAppointmentDate(java.sql.Date.valueOf(appointmentDate)); appointment.setRemark(remark); appointment.setStatus("待就诊"); boolean success = appointmentDAO.insert(appointment); if (success) { response.sendRedirect(request.getContextPath() + "/appointment/list"); } else { request.setAttribute("errorMsg", "新增预约失败,请重试"); request.getRequestDispatcher("/appointment/add.jsp").forward(request, response); } } }

写Servlet时最容易忽略的一点是**doGet和doPost的分工**。这段代码只处理了doPost,因为新增操作不允许通过URL直接触发;而如果你的Servlet同时需要承担“跳转到新增页面”和“处理新增请求”两个职责,那就要在doGet里转发页面、在doPost里处理业务,两个方法都要重写并加上注解@WebServlet——这是Servlet 3.0之后的注解式配置,省掉了web.xml里一长串的servlet-mapping,但老代码包里仍然大量使用web.xml方式,两种方式都能跑,别在答辩时给自己挖坑说“注解和XML不能共存”,它们可以共存,优先级是注解更高。

4.3 DAO层:PreparedStatement是底线,不是可选项

DAO层的JDBC代码是整个项目里最容易出问题、也最值得在答辩时展开讲的部分。一个“标准且安全”的插入方法如下:

public boolean insert(Appointment appointment) { String sql = "INSERT INTO appointment (pet_id, doctor_id, appointment_date, remark, status) " + "VALUES (?, ?, ?, ?, ?)"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, appointment.getPetId()); ps.setInt(2, appointment.getDoctorId()); ps.setDate(3, new java.sql.Date(appointment.getAppointmentDate().getTime())); ps.setString(4, appointment.getRemark()); ps.setString(5, appointment.getStatus()); return ps.executeUpdate() > 0; } catch (SQLException e) { e.printStackTrace(); return false; } }

为什么必须用PreparedStatement而不是Statement?最直接的原因是防SQL注入。Statement是直接把参数拼进SQL字符串里,比如"SELECT * FROM user WHERE username='" + input + "'",只要用户在输入框里敲一个' OR '1'='1就能绕过密码校验,这是毕设答辩时老师最可能挑刺的点。而PreparedStatement在数据库端预编译SQL骨架,?占位符只作为参数传递,不会参与SQL语法解析。即使你不关心安全性,PreparedStatement的预编译机制在循环插入大量数据时也能提升性能,因为SQL语句只编译一次。

另一个细节是try-with-resources写法(try (Connection conn = ...)),它能在代码块结束后自动关闭Connection、PreparedStatement和ResultSet,不用手动在finally块里判断空指针再关闭——这是很多老代码里写得不干净的地方,手动关闭时一旦异常抛出,连接就会泄漏,第5章会专门讲。

4.4 列表页渲染:JSTL循环和分页显示的取舍

预约列表页通常是appointment/list.jsp,数据在Servlet里查出来放进request.setAttribute("list", list),然后JSP页面用JSTL循环渲染:

<table class="table table-bordered"> <thead> <tr> <th>预约编号</th><th>宠物名</th><th>医生</th> <th>预约日期</th><th>状态</th><th>操作</th> </tr> </thead> <tbody> <c:forEach items="${list}" var="item"> <tr> <td>${item.appointmentNo}</td> <td>${item.petName}</td> <td>${item.doctorName}</td> <td>${item.appointmentDate}</td> <td>${item.status}</td> <td> <a href="${pageContext.request.contextPath}/medical/detail?appointmentId=${item.id}">查看病历</a> </td> </tr> </c:forEach> </tbody> </table>

如果数据量超过几十条,一定要做分页,否则你的页面会一次查全部数据,体验非常差。最简单的分页写法是SQL里用OFFSET-FETCH,SQL Server 2012以上版本都支持:

SELECT * FROM appointment ORDER BY id DESC OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY;

对应的,你的DAO层方法要接收两个参数pageNum和pageSize,在SQL里动态拼接OFFSET ? ROWS FETCH NEXT ? ROWS ONLY,同时还需要一个count查询拿到总条数来计算总页数——这个COUNT(*)查询很关键,但很多新手会把它漏掉,导致分页条只有“上一页/下一页”没有页码。

提示:OFFSET-FETCH在MySQL里不通用,MySQL的分页写法是LIMIT ? OFFSET ?。如果你用的是MySQL,两个数据库的SQL方言差异会在切换时踩坑,我这篇里以SQL Server为主线,第5章的避坑清单里会单独提这个差异。

5. JSP+SQL的避坑清单:这些坑我当年全部踩过

5.1 部署后页面能开,但一查数据库就报“No suitable driver found”

现象:项目在IDEA里用Tomcat跑得好好的,导出成war包丢到解压版Tomcat的webapps目录里,访问JSP页面正常,但只要一执行SQL查询就报java.sql.SQLException: No suitable driver found for jdbc:sqlserver://...。

原因:这个坑十有八九是jar包没进WEB-INF/lib。IDEA里运行项目时,依赖jar包只要在File -> Project Structure -> Libraries里配置过就能找到;但打包成war时,IDEA默认只打包WEB-INF/lib下的物理文件,如果你之前是通过“Add as Library”方式引的jar,它们在工程目录里并不存在,war包里自然没有。

解决:检查WEB-INF/lib文件夹下是否有sqljdbc4.jar或mysql-connector-java-版本.jar。如果没有,去本地Maven仓库或网上下载对应版本的驱动jar,直接复制到WEB-INF/lib目录下,然后重新Build -> Build Artifacts -> Rebuild,再导出war包即可。这也是我为什么建议所有JSP项目都保持“jar包物理放lib目录”这个习惯。

5.2 数据库里中文正常,JSP页面上中文全变成问号

现象:SQL Server的表中用SQL语句插入中文没问题,但在JSP页面上通过表单新增的中文记录,存进库里变成????;或者倒过来,页面展示库里已有的中文全是乱码。

原因:这是典型的字符集不一致问题。三个环节的编码必须统一:JSP页面本身的pageEncoding、Tomcat接收请求的解码字符集、数据库连接的字符集参数。很多老项目的JSP头写的是pageEncoding="GBK",而db.properties里连接串带的characterEncoding=utf8,两边对不上。

解决:全部统一成UTF-8。JSP头的写法是<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>;Servlet的doPost开头加request.setCharacterEncoding("UTF-8");数据库连接串加;characterEncoding=utf8(SQL Server的JDBC驱动是;characterEncoding=UTF-8,MySQL驱动是?characterEncoding=utf8)。改完重启Tomcat,把之前写入的乱码数据清掉重新插入。

5.3 系统用几天后卡死,SQL Server报“连接数已满”

现象:本地开发时一点问题没有,部署到机房机器上运行两天后,打开网页一直转圈,SQL Server管理工具里能看到几十个Sleeping状态的连接,占满了user connections。

原因:代码里的Connection、PreparedStatement、ResultSet没有全部关闭。最常见的手动关闭写法是:

Connection conn = null; try { conn = DBUtil.getConnection(); ... } finally { conn.close(); }

你以为close()了连接就完事,但PreparedStatement和ResultSet没关,它们持有的数据库游标和连接资源不会被释放。如果每执行一次查询都泄漏一部分资源,几百次之后连接池资源就被耗尽。

解决:全部改用try-with-resources语法,把Connection、PreparedStatement、ResultSet三个都在try()里声明,一个都不能少。

try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { // 业务处理 } } catch (SQLException e) { e.printStackTrace(); }

这段写法的关闭顺序是反的:先关ResultSet,再关PreparedStatement,最后关Connection,完全符合JDBC规范。你只要全项目都按这个模式写,连接泄漏问题从根上杜绝。

5.4 SQL语法都对,SQL Server就是报“关键字‘OFFSET’附近有语法错误”

现象:明明按第4章写好了OFFSET-FETCH分页SQL,在SQL Server Management Studio里执行没问题,但通过JDBC执行就报语法错误。

原因:SQL Server 2012之前的版本(比如SQL Server 2008 R2)不支持OFFSET-FETCH语法。很多毕设项目在Windows Server 2008环境里用的是SQL Server 2008 R2,而你的开发机装的是2019,两边语法不兼容。

解决:要么升级数据库,要么换成兼容写法。SQL Server 2008 R2的分页用ROW_NUMBER()实现:

SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY id DESC) AS row_num FROM appointment ) AS t WHERE t.row_num BETWEEN 1 AND 10;

BETWEEN的起始行号由(pageNum - 1) * pageSize + 1和pageNum * pageSize计算。如果你不确定生产环境的SQL Server版本,建议直接上ROW_NUMBER()这种老写法,通用性最强。

5.5 表单提交成功但数据没入库,页面也没报错

现象:点击“提交”按钮后,页面跳转到了列表页,但列表里没有新数据,后台日志也没有任何异常。

原因:绝大多数情况是数据校验在Servlet里静默失败。比如Integer.parseInt传入了空字符串会抛NumberFormatException,但你没有catch就顺着栈打印出去了,而页面跳转逻辑被catch吞掉或者走空了。更深一层的原因可能是表单里某个input的name和Servlet里getParameter的名字不一致——前端拼了一个petId,Servlet读的是petId,但多写了个空格,这是HTML属性值里最常见的隐形错误。

解决:先在浏览器开发者工具的Network面板里看提交的Form Data,对照Servlet读参数的各行代码逐一核对;然后在Servlet里包一层try-catch,把e.printStackTrace()改成logger.error("新增预约失败", e)并request.setAttribute("errorMsg", e.getMessage()),让错误显形。记住:页面没报错不等于代码没异常,异常被吞是JSP项目里最坑的表现形式。

6. 进阶优化:从“能跑”到“值得给学弟学妹当模板”

6.1 把重复的JDBC代码收进一个BaseDAO

这套系统里如果每个DAO都写一遍getConnection()、close()、PreparedStatement设置参数,代码量会膨胀到每人三千行。常见做法是抽一个BaseDAO,把“执行增删改”和“执行查询结果集”封装成泛型方法。查询部分可以用JavaBean的属性名和ResultSet的列名做映射,也可以更粗暴地用Map<String, Object>接结果——毕设阶段用Map反而更省事。

public class BaseDAO { protected int executeUpdate(String sql, Object... params) { try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { if (params != null) { for (int i = 0; i < params.length; i++) { ps.setObject(i + 1, params[i]); } } return ps.executeUpdate(); } catch (SQLException e) { throw new RuntimeException("执行更新失败", e); } } protected List<Map<String, Object>> executeQuery(String sql, Object... params) { List<Map<String, Object>> resultList = new ArrayList<>(); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { if (params != null) { for (int i = 0; i < params.length; i++) { ps.setObject(i + 1, params[i]); } } try (ResultSet rs = ps.executeQuery()) { ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount(); while (rs.next()) { Map<String, Object> row = new HashMap<>(); for (int i = 1; i <= columnCount; i++) { row.put(metaData.getColumnLabel(i), rs.getObject(i)); } resultList.add(row); } } } catch (SQLException e) { throw new RuntimeException("执行查询失败", e); } return resultList; } }

setObject这个方法是核心技巧——它自动适配Integer、String、Date等类型,你不用再手动调setInt、setString、setDate。所有DAO的增删改都变成一行调用:

public boolean insert(Appointment appointment) { String sql = "INSERT INTO appointment (pet_id, doctor_id, appointment_date, remark, status) VALUES (?,?,?,?,?)"; return executeUpdate(sql, appointment.getPetId(), appointment.getDoctorId(), appointment.getAppointmentDate(), appointment.getRemark(), appointment.getStatus()) > 0; }

这个改造做完,你的代码量能减少三分之一,答辩时还能理直气壮地讲“我做了DAO层封装,便于后续换ORM框架”。

6.2 给列表页加一个真正的慢SQL排查手段

所有JSP+SQL项目最后的性能瓶颈几乎都在SQL。你在SQL Server Management Studio里把“启用执行计划”的开关打开,重点看三类查询:全表扫描(Table Scan)、Key Lookup、嵌套循环操作符。比如按宠物名查宠物列表,如果没有在name字段上建索引,执行计划里就是全表扫描;按预约日期范围查询,如果appointment_date上有索引但查询条件写了WHERE date_format(appointment_date, '%Y-%m-%d') = '2024-01-01',索引就失效了,因为函数作用在索引列上,SQL Server无法使用索引快速定位。正确的写法是WHERE appointment_date >= '2024-01-01' AND appointment_date < '2024-01-02',这段我可以负责任地告诉你:很多做了两年后端的人在这上面栽过。

6.3 加一道登录过滤器,把系统拼完整

宠物诊所系统必须有用户登录,否则任意访问者都能看到患者的病历记录,这在答辩演示时是个减分项。常见的做法是写一个LoginFilter,检查Session里有没有用户标记,没有就跳回登录页。过滤器在web.xml里配置:

<filter> <filter-name>LoginFilter</filter-name> <filter-class>com.pet.clinic.filter.LoginFilter</filter-class> </filter> <filter-mapping> <filter-name>LoginFilter</filter-name> <url-pattern>/*</url-pattern> <exclude-url-pattern>/login</exclude-url-pattern> <exclude-url-pattern>/assets/*</exclude-url-pattern> </filter-mapping>

过滤器写完后,我的个人习惯是顺手加一个“记住密码”的功能验证Session的生命周期——登录成功后session.setMaxInactiveInterval(30 * 60),30分钟无操作自动失效。这个小细节在答辩时一提,老师就知道你对会话管理有概念。

这套项目做完,我最深的体会是:毕设的价值不在于用了多新的框架,而在于你能不能把一条完整的数据链路讲清楚——用户在浏览器点了一个按钮,请求怎么经过Tomcat到Servlet,Servlet怎么调DAO,DAO怎么拼PreparedStatement,SQL怎么在SQL Server里执行并返回,结果怎么一路渲染回JSP页面成为用户看到的表格。把这个链路里的每个环节都验证过、每个坑都踩平了,你收获的绝不只是一个zip包里的代码,而是一套排查问题的思路。希望这篇笔记能帮你在做这套宠物诊所系统时少走点弯路。

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

返回列表