简介:这是一份基于jsp+servlet+mysql开发的简单宠物管理系统源码,面向Java Web初学者,适合用来练习JSP页面、Servlet控制层与MySQL数据库之间的增删改查操作。系统支持宠物分类查询、添加、编辑和删除,功能简洁,同时涵盖了数据库表设计、JDBC连接和页面数据回显等典型学习点。压缩包共41个文件,包含7个Java源文件、4个JSP页面、4个依赖jar包、SQL数据库脚本,以及png效果图、docx配置说明、pdf文档和mp4操作演示视频,总大小6.37MB,各类型文件分工明确,便于对照学习。目前已有1580人学习参考,热度较好。资源在原版本基础上新增了编辑与删除功能,形成完整的增删改查模块,并附带数据库初始化脚本、环境配置文档和运行录像,读者可以按文档配置数据源、导入SQL并部署到Tomcat,快速跑通项目,理解jsp+servlet+mysql的整合流程,也可作为课程设计或毕业设计前的练手模板。
1. JSP+Servlet+MySQL 宠物管理系统:课程设计源码到底能给你什么
JSP+Servlet+MySQL 这三件套至今还是 Java Web 课程设计里的常客,很多课程设计案例源码都绕不开它。这份宠物管理系统源码就是典型:数据库脚本、petmgr 工程、配置文档、演示视频一次给全,纯 Servlet 处理请求、JSP 渲染页面、JDBC 连 MySQL,没有框架干扰。适合刚学完 Java Web 基础、想拿一份能跑通全流程的代码来入门的人。
它把完整请求链路压缩到很小代码量:浏览器发请求,Servlet 接收处理,DAO 层访问数据库,结果回填到 JSP。按分类查询、添加、编辑、删除都在,原版只有查询和添加,后来补全编辑和删除,才成为完整增删改查模块。想快速交课程设计或看懂传统 javaweb 骨架的,这份很合适;但不懂 JDBC 和 Tomcat 原理的话,照视频配完照样踩坑。
2. 压缩包结构与请求链路:从 db_petmgr.sql 看到 Servlet 的完整流转
2.1 压缩包内部结构:哪些文件负责什么
拿到压缩包第一件事不是急着导入 IDE,而是先把包里每个文件认一遍,不然很容易在“配置文档”和“代码”之间来回折腾还找不到东西。解压后大致是这个布局:
006_java+jsp+servlet+mysql简答宠物管理系统/ ├── 功能描述.txt ├── 图片/1.png、2.png、3.png ├── 代码/petmgr/ ├── 配置/详细配置.docx ├── 数据/db_petmgr.sql ├── 文档/宠物管理系统.pdf └── 视频/006_...(演示视频).mp4db_petmgr.sql 是数据库初始化脚本,建库、建表、插样例数据全在里面;详细配置.docx 是环境搭建和部署的图文教程,适合第一次跑 Java Web 项目的人照着点;宠物管理系统.pdf 是项目说明文档,交作业或答辩时可以直接引用;演示视频和几张图片是运行效果的佐证。
petmgr 是真正的 Web 工程文件夹,src 目录放 Java 源码,WebRoot(有的是 web 目录)放 JSP 和静态资源。如果你打开代码发现类名、目录名和后面写的略有出入,不用纠结,这是不同版本的命名差异,逻辑对应关系是一致的。功能描述.txt 里也写了这个包经历过一次升级:原版只有查询和添加,后来补了编辑和删除,所以你在代码里能同时看到“增删改查”四套处理逻辑,而不只是某个残缺的半成品。
在宠物管理系统这类课设项目里,工程结构基本遵守同一个套路。src 下面一般拆成几个包:bean 放数据模型类,dao 放数据库访问层,servlet 放请求处理,工具类如 DBUtil 专门管理数据库连接。WebRoot 下放 JSP 页面和 WEB-INF/web.xml,web.xml 是整个 Web 应用的入口描述文件。把这个两层结构记牢,后面阅读代码会顺很多,自己动手改功能时也知道该去哪一层的哪个文件动刀。
2.2 数据库设计:db_petmgr.sql 核心表结构
这个系统功能简单,数据库也没有复杂设计,核心就是一张宠物信息表 pet,所有业务都围绕它展开。下面这段 SQL 是这类项目的典型写法,和脚本里实际字段命名可能有细微差别,但结构基本一致:
CREATE DATABASE IF NOT EXISTS db_petmgr DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE db_petmgr; CREATE TABLE IF NOT EXISTS pet ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '宠物ID', name VARCHAR(50) NOT NULL COMMENT '宠物名称', category VARCHAR(20) NOT NULL COMMENT '分类,如猫/狗/鸟', age VARCHAR(10) DEFAULT NULL COMMENT '年龄,用字符串方便展示', sex VARCHAR(10) DEFAULT NULL COMMENT '公/母', description VARCHAR(500) DEFAULT NULL COMMENT '描述' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宠物信息表';有两个设计细节值得你注意。第一,category 直接存字符串,没有单独拆分类表。小项目这么选是合理的:查询不用 JOIN,页面下拉框写死几个选项就行;代价是以后想维护分类层级的联动关系会比较麻烦。第二,age 字段用 VARCHAR 而不是 INT,因为用户可能会填“3个月”或“2岁半”,字符串在展示层最省事。这是课设项目里最常见的取舍,我个人是认可这种务实做法的,毕竟系统目标是跑通增删改查,不是做一套完整的数据规范。
脚本导入后可以用这条命令快速确认表结构就位:
mysql -uroot -p -e "USE db_petmgr; SHOW TABLES; DESC pet;"看到 pet 表和 id、name、category 这些字段,说明数据库初始化完成,可以进入部署环节了。如果表不存在,先别急着改代码,回头看 SQL 导入过程有没有报错,大概率是没选对库或者脚本没执行完。
2.3 一次完整请求的流转:web.xml、Servlet、DAO 是怎么串起来的
这个系统没有用 Spring MVC 之类的框架,请求从页面到数据库完全靠 web.xml 的映射和 Servlet 内部分发逻辑驱动。我从访问列表页这个场景说起。
浏览器输入 http://localhost:8080/petmgr/,Tomcat 根据 welcome-file 找到 index.jsp,index.jsp 一般会跳转到 listPet.jsp。listPet.jsp 上有个查询按钮或分类下拉框,提交到类似 pet 的 URL。这个 pet 不是文件名,而是 web.xml 里声明的 Servlet 映射:
<?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_4_0.xsd" version="4.0"> <display-name>petmgr</display-name> <servlet> <servlet-name>PetServlet</servlet-name> <servlet-class>com.petmgr.servlet.PetServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>PetServlet</servlet-name> <url-pattern>/pet</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>这段配置把 /pet 这个 URL 交给 com.petmgr.servlet.PetServlet 处理。注意 schema 版本,web-app_4_0.xsd 对应 Tomcat 9,如果本地是 Tomcat 8.5,要把 schema 里的 4.0 改成 3.1,否则启动时解析会报错。
PetServlet 收到请求后,先读一个 action 参数,用 if/else 或 switch 分发到具体方法。这是传统 Servlet 项目最常见的写法,也是这个系统用一个 Servlet 完成列表、添加、编辑、删除的原因:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String action = request.getParameter("action"); if ("list".equals(action)) { listPet(request, response); // 查全部或按条件查 } else if ("toEdit".equals(action)) { showEditPage(request, response); // 跳转编辑页并回显 } else if ("delete".equals(action)) { deletePet(request, response); // 删除记录 } else { listPet(request, response); } }action 参数的值由 JSP 页面的表单或超链接决定。列表页查询按钮提交 action=list,编辑链接带 action=toEdit&id=1。request.setCharacterEncoding("UTF-8") 必须放在所有 getParameter 之前,否则读到的中文参数已经乱码,这是整个系统里最容易踩的坑,后面避坑章节专门展开。
Servlet 拿到参数后,把业务委托给 DAO 层。PetDAO 用 JDBC 执行 SQL,把 ResultSet 逐行封装成 Pet 对象,再返回 List。Servlet 把 List 放进 request 域,使用 request.getRequestDispatcher("listPet.jsp").forward(...) 转发到 JSP,页面再用 EL 或 JSTL 遍历 List 渲染表格。到这里,“页面 → Servlet → DAO → 数据库 → 页面”的闭环就完成了。理解这条链路,比背十个增删改查代码片段都管用,因为整个项目所有功能都是在这条链路上增加分支而已。
3. 部署配置:JDK、Tomcat、MySQL 三件套的版本搭配与连接参数
3.1 版本搭配:为什么推荐 JDK 8 + Tomcat 9 + MySQL 8
先给一个我验证过能稳定跑起来的组合:
| 组件 | 推荐版本 | 注意事项 |
|---|---|---|
| JDK | 1.8(8u202 及以上) | 大多数教学环境默认版本 |
| Tomcat | 8.5.x 或 9.0.x | 对应 Servlet 3.1 / 4.0 schema |
| MySQL | 5.7 或 8.0.x | 8.0 需注意驱动类名和时区参数 |
| mysql-connector-java | 5.1.49 或 8.0.x | 版本不同,驱动类名不同 |
这套组合推荐的理由很简单:网上大量 Java Web 课程设计源码都以 JDK 8 为基准编译,Tomcat 8.5 和 9.0 在 Servlet 层面的行为差异对这个小项目没有实际影响。JDK 11 以上也能跑,但如果你用老版本 IDE,反而容易在编译级别上卡住。JDK 环境变量配置这部分,详细配置.docx 里应该有截图,如果还卡在 java -version,先不要往下走,否则部署阶段报的错会让你分不清是环境问题还是代码问题。
MySQL 版本是这个组合里最需要小心的一环。如果本地 MySQL 是 8.0,而源码自带的驱动 jar 是 5.1.x,连接时大概率会报 caching_sha2_password 相关错误,因为 MySQL 8 默认的认证插件和 5.x 不兼容。解决方式有两种:把 mysql-connector-java 升级到 8.0.x,并修改 DBUtil 里的驱动类名为 com.mysql.cj.jdbc.Driver;或者在 MySQL 里把 root 用户的认证插件改回 mysql_native_password。
注意:如果你打算用 MySQL 8.0,优先选第一种方案,改驱动不动数据库账号,以后换环境也更省事。
连接 URL 在 MySQL 8 下建议写全这几个参数:
jdbc:mysql://localhost:3306/db_petmgr?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=utf8mb4&serverTimezone=Asia/ShanghaiuseSSL=false 是为了避免 SSL 握手报错,serverTimezone 解决 8.0 的时区差异,characterEncoding=utf8mb4 保证中文写入正常。这串参数几乎能覆盖 MySQL 8 下的大部分连接异常。
3.2 导入数据库:命令行、Navicat 两条路径
数据库脚本导入是最容易出乱码的环节,这里给两种方式。命令行方式最简单,把终端切到 db_petmgr.sql 所在目录,执行:
mysql -uroot -p < db_petmgr.sql输入密码回车,看到没有任何报错,脚本就执行完了。Navicat 方式是打开连接后右键数据库,选择“运行 SQL 文件”,选中 db_petmgr.sql,编码选择 utf8mb4,执行完刷新库列表。
导入完成后执行一次检查:
mysql -uroot -p -e "USE db_petmgr; SHOW TABLES; DESC pet;"如果看到 pet 表和 id、name、category 等字段,说明数据初始化成功。这里有两个高频坑。第一,不要在 Navicat 的查询编辑器里整段复制 SQL 执行,脚本里如果有 CREATE DATABASE 语句,查询编辑器可能执行不完整,最后库建出来了,表数据是空的。第二,MySQL 8 命令行导入如果报 public key retrieval is not allowed,不是密码错误,而是认证插件导致的,检查一下连接串是否带了 allowPublicKeyRetrieval=true。
3.3 部署进 Tomcat:IDEA 与 Eclipse 的操作差异
把 petmgr 导入 IDE 后配置 Tomcat。Eclipse 用户在 Servers 视图新建 Tomcat 实例,把项目 Add 进去;IDEA 用户在 Run Configuration 里选择 Tomcat Server-Local,配置 Application context 为 /petmgr。
启动前先确认三件事。第一,web.xml 的 schema 版本和 Tomcat 版本匹配,前面说过 4.0 对应 Tomcat 9。第二,mysql 驱动 jar 已经放进 WEB-INF/lib,不是只存在于 IDE 的外部库列表里,否则部署后 jar 进不了 artifact,运行时报 ClassNotFoundException 是必然的。第三,如果 Tomcat 8080 端口被占用,改 conf/server.xml 里的 Connector 配置:
<Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />改完端口重启 Tomcat,浏览器访问 http://localhost:8081/petmgr/。看到列表页或登录页说明部署成功。如果 404,先检查访问路径里的上下文路径是不是 /petmgr,这个值要和 IDE 里配置的 Application context 一致,跟文件夹叫什么名字没关系。我在答疑时发现不少人把工程文件夹重命名为 petmgr2,但 IDE 里 Application context 仍写 /petmgr,那应该访问的还是 /petmgr,而不是 /petmgr2。
4. 增删改查实现:分类查询、添加表单、编辑回显与删除确认的关键代码
4.1 列表和分类查询:PreparedStatement 动态拼条件
列表是系统的默认页面。PetDAO 里提供一个同时支持按名称模糊查询和按分类精确查询的方法,这样无论是直接进列表还是选择分类后再查询,都走同一个方法:
public List<Pet> findByNameAndCategory(String name, String category) { List<Pet> list = new ArrayList<>(); String sql = "SELECT id, name, category, age, sex, description FROM pet WHERE 1=1"; if (name != null && !name.trim().isEmpty()) { sql += " AND name LIKE ?"; } if (category != null && !category.trim().isEmpty()) { sql += " AND category = ?"; } try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { int index = 1; if (name != null && !name.trim().isEmpty()) { ps.setString(index++, "%" + name + "%"); } if (category != null && !category.trim().isEmpty()) { ps.setString(index++, category); } try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { Pet pet = new Pet(); pet.setId(rs.getInt("id")); pet.setName(rs.getString("name")); pet.setCategory(rs.getString("category")); pet.setAge(rs.getString("age")); pet.setSex(rs.getString("sex")); pet.setDescription(rs.getString("description")); list.add(pet); } } } catch (SQLException e) { e.printStackTrace(); } return list; }代码逻辑不复杂,重点在两处。WHERE 1=1 是动态拼接条件的常用写法,后面接 AND 不需要判断当前是不是第一个条件,省去一层 if 嵌套;用 PreparedStatement 而不是字符串拼 SQL,避免了引号转义麻烦,也天然挡住了 SQL 注入。这是 JavaWeb DAO 层最经典的写法,面试时也常被问到。设置参数时 index 从 1 开始递增,顺序必须和 SQL 里 ? 的出现顺序一致,否则运行时会报参数索引错误。
分类下拉框的数据来源,可以写死几个 option,也可以用 SELECT DISTINCT category FROM pet 从数据库动态加载。我更推荐后者,新增分类后不用改 JSP 就能自动出现。但要注意动态查询时,如果数据库里没有任何数据,下拉框会是空的,所以种子数据别删光。
4.2 添加宠物:doPost 编码处理与 PRG 跳转
添加页面 addPet.jsp 的表单字段和 pet 表字段一一对应,提交方式必须用 post,避免 URL 过长和参数裸露:
<form action="pet" method="post"> <input type="hidden" name="action" value="add" /> 名称:<input type="text" name="name" /> 分类: <select name="category"> <option value="猫">猫</option> <option value="狗">狗</option> <option value="鸟">鸟</option> </select> 年龄:<input type="text" name="age" /> 性别: <select name="sex"> <option value="公">公</option> <option value="母">母</option> </select> 描述:<textarea name="description"></textarea> <button type="submit">添加</button> </form>form 的 action 写的是 pet 而不是 /petmgr/pet,这里依赖相对路径解析:如果当前页面 URL 是 http://localhost:8080/petmgr/addPet.jsp,表单会提交到 http://localhost:8080/petmgr/pet,Tomcat 按 web.xml 找到 PetServlet。如果你在 JSP 里加了 标签,提交地址以 base 为准,两种方式混用时最容易出 404。
Servlet 的 doPost 处理新增:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String action = request.getParameter("action"); if ("add".equals(action)) { String name = request.getParameter("name"); String category = request.getParameter("category"); String age = request.getParameter("age"); String sex = request.getParameter("sex"); String description = request.getParameter("description"); Pet pet = new Pet(); pet.setName(name); pet.setCategory(category); pet.setAge(age); pet.setSex(sex); pet.setDescription(description); int rows = new PetDAO().addPet(pet); if (rows > 0) { response.sendRedirect("pet?action=list"); } else { request.setAttribute("msg", "添加失败"); request.getRequestDispatcher("addPet.jsp").forward(request, response); } } }doPost 开头第一行必须是 request.setCharacterEncoding("UTF-8"),而且要放在任何 getParameter 之前。这一步漏了,表单提交的中文到 Servlet 时已经是乱码,后面数据库配置得再标准也救不回来。另一个细节是新增成功后用 sendRedirect 而不是 forward,这叫 PRG 模式,Post/Redirect/Get,目的是让浏览器地址栏变成 get 请求的列表页,用户按 F5 刷新不会重复提交表单。很多课设代码这里用 forward,结果刷新一次多一条数据,答辩时容易被老师挑出来。
4.3 编辑回显和删除:id 参数传递与 UPDATE/DELETE 语句
编辑功能比新增多一个步骤:先回显,再提交。列表页每一行生成两个操作链接,常见写法:
<a href="pet?action=toEdit&id=${pet.id}">编辑</a> <a href="pet?action=delete&id=${pet.id}" onclick="return confirm('确定删除这条记录吗?');">删除</a>这里的 ${pet.id} 是 EL 表达式,它取的是 request 域中 pet 对象的 id 属性。EL 解析的是 JavaBean 的 getter 方法,所以 Pet 类里必须有 getId() 方法,属性名拼错一个字,拿到的就是 null,后续转 Integer 直接抛 NumberFormatException。编辑回显的 Servlet 方法:
private void showEditPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int id = Integer.parseInt(request.getParameter("id")); Pet pet = new PetDAO().findById(id); request.setAttribute("pet", pet); request.getRequestDispatcher("editPet.jsp").forward(request, response); }editPet.jsp 里除了表单字段,还有两个隐藏域:一个存 id,用来定位更新哪条记录;一个存 action=update,让 doPost 的 else if 分支识别这是更新操作。更新逻辑最终落到一条 UPDATE 语句:
UPDATE pet SET name=?, category=?, age=?, sex=?, description=? WHERE id=?删除操作更简单,一条 DELETE FROM pet WHERE id=? 就能删干净。但这里我要多说一句:列表页里的删除链接是 GET 请求,这是课设项目的常规偷懒写法,对生产环境有隐患。搜索引擎预取、浏览器收藏夹都可能把 GET 请求当普通访问,一旦带上 id 就会误删数据。想做得严谨一点,可以把删除改成表单 POST 提交,或者至少保留前端的 confirm 拦截。这是源码可以升级的方向之一,下一章我会给完整的建议。
5. Servlet+JSP 项目避坑清单:连接失败、中文乱码、404 的现场还原
5.1 ClassNotFoundException: com.mysql.jdbc.Driver
现象:第一次访问列表页,Tomcat 控制台抛 java.lang.ClassNotFoundException: com.mysql.jdbc.Driver,页面 500,错误堆栈定位到 Class.forName("...") 这一行。
原因:驱动 jar 没有被打进 WEB-INF/lib。很多人把 jar 拖到 IDE 的 External Libraries 或 Tomcat 的 lib 目录下,以为这样就能用。实际上 Web 应用运行时每个应用的类加载器以 WEB-INF/lib 和 WEB-INF/classes 为主,外部库的配置不一定生效。另外 MySQL 8.0 用户如果用了旧驱动,驱动类名本身也对不上。
解决:把 mysql-connector-java 的 jar 拷到 petmgr 工程的 WEB-INF/lib 下,在 IDE 里 clean 并重新部署,确认部署后的 artifact 里包含这个 jar。如果 MySQL 是 8.0,把驱动类改成 com.mysql.cj.jdbc.Driver。改完重启 Tomcat 再访问,这个异常就消失了。这条解决路径我走过不下五次,每次都是 jar 位置或版本的问题,和业务代码无关。
5.2 数据库连接失败:Access denied 或 Public Key Retrieval is not allowed
现象:页面报 SQLException,提示 Access denied for user 'root'@'localhost'(using password: YES),或者提示 Public Key Retrieval is not allowed。
原因:前者是账号密码不匹配,后者是 MySQL 8 默认 caching_sha2_password 认证插件要求客户端在首次连接时获取公钥,旧驱动或连接串没带 allowPublicKeyRetrieval=true 时就会报这个错。不少人在这一步反复确认密码,结果不是密码问题,是认证方式不匹配。
解决:先用 Navicat 或命令行确认账号能正常登录,排除密码错误。然后在 JDBC 的 URL 上加参数:
jdbc:mysql://localhost:3306/db_petmgr?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai如果还报 SSL 或时区错误,说明驱动版本太旧,直接换 8.0.x 的 mysql-connector-java。DBUtil 里的连接参数最好统一放到常量里,改一处全局生效,不用到处找。
5.3 中文乱码:添加宠物后中文名变成“???”
现象:新增宠物成功,回到列表页,中文名称和分类显示为问号,英文正常。直接查数据库里的记录也是问号。
原因:三层出现编码断层。Servlet 的请求体没设置 UTF-8,或者设置放在了 getParameter 之后,读到就是乱码;JDBC 连接 URL 没带 characterEncoding,驱动侧又转换一次;数据库或表本身是 latin1,插入时再转一次就彻底变问号。最常见的还是前两者叠加。
解决:按三步统一编码。第一,Servlet 里 request.setCharacterEncoding("UTF-8") 写在方法第一行;第二,JDBC 连接 URL 加 characterEncoding=utf8mb4;第三,确认数据库和表的 charset 是 utf8mb4,导入 SQL 时选择 utf8mb4 编码。改完后删掉刚才的脏数据重新插一遍。已经变成问号的记录不要指望靠查数据看编码恢复,直接删除重来最快。
5.4 访问项目出现 404,Tomcat 控制台却显示部署成功
现象:Tomcat 启动正常,日志里出现 petmgr 的部署信息,但浏览器访问 http://localhost:8080/petmgr/ 返回 404。
原因:这类 404 大概率不是代码问题,而是上下文路径或映射不一致。常见有三种情况:IDE 配置的 Application context 不是 /petmgr;web.xml 里 welcome-file 写错文件名,比如写 index.html 而工程里只有 index.jsp;JSP 里 form 的 action 和 web.xml 的 url-pattern 对不上。这不是什么玄学,就是路径没对齐。
解决:先看访问地址是否带了正确的项目上下文路径,再检查 web.xml 的 welcome-file 是否指向真实存在的文件。如果是页面内链接 404,回到第 2 章的链路图,把 URL 逐段拆开:http://localhost:8080/上下文路径/Servlet映射?参数,一段一段核对。最后看 IDE 部署输出,确认实际部署的上下文路径和浏览器输入一致。
5.5 页面能显示但 CSS、图片全部失效
现象:列表页数据正常渲染,但页面没有样式、图片红叉,整体布局错乱。
原因:JSP 里 css 和图片用了相对路径,比如 href="css/style.css"。当浏览器地址是 /petmgr/pet?action=list 时,相对路径会基于 /petmgr/pet 解析,最终去找 /petmgr/pet/css/style.css,自然不存在。这是 Servlet 映射路径和 JSP 静态资源混用时的典型问题,数据没错,纯粹是浏览器解析基准变了。
解决:在 JSP 页面顶部加一行 base 标签,让所有相对路径都基于项目根目录:
<% String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/"; %> <base href="<%=basePath%>">加了这一行后,直接写 href="css/style.css" 也会被解析成 /petmgr/css/style.css,不会再跟着 Servlet 路径到处乱跑。这个修复几乎零成本,但很多课设代码里压根没写,页面数据全对就是丑得没法看。
6. 进阶升级:登录、分页、校验和参数过滤让课设更完整
到这里,系统的部署和核心增删改查已经盘清楚。如果你打算把这个包作为课程设计,或者想让它更完整一些,下面四个改动优先级最高,每个半小时内能完成,但价值很直观。
先加登录。数据库新建一张 user 表,字段 id、username、password,插入一条测试账号;建 login.jsp 做登录表单,Servlet 里查库比对成功后用 session.setAttribute("loginUser", username) 存登录态;listPet.jsp 和 addPet.jsp 顶部统一检查 session,为空就重定向回 login.jsp。这是答辩时最常被问“整个系统状态怎么控制”的地方,一套下来能讲三五分钟。
再加分页。列表页超过十几条数据后浏览体验很差。PetDAO 里加一个带 LIMIT 的查询方法:
String sql = "SELECT id, name, category, age, sex, description FROM pet LIMIT ?, ?";Servlet 里从 request 拿 pageNo 和 pageSize 两个参数,默认 pageNo=1、pageSize=10,查总记录数后算出总页数,JSP 上渲染“上一页/下一页”两个链接。分页是 Java Web 面试高频题,强烈建议手写一遍,不要直接复制别人的。
然后是前端校验。addPet.jsp 的 form 加 onsubmit 事件,名称为空时直接弹提示不发请求:
<form action="pet" method="post" onsubmit="return checkForm();"> <script> function checkForm() { var name = document.querySelector('input[name="name"]').value.trim(); if (name === '') { alert('请输入宠物名称'); return false; } return true; } </script>但记住,前端校验只负责体验,后端 Servlet 里同样要判断 name 非空,否则绕过页面直接 POST 请求照样插入空数据。前后端双重校验才是完整做法,这也是面试官喜欢追问的点。
最后是参数过滤。第 4 章提到 LIKE 查询时如果用户输入了 % 或 _ 会被当成通配符,造成意外匹配。在把关键字拼进 SQL 参数前做一次转义:
keyword = keyword.replace("%", "\\%").replace("_", "\\_");这样用户输入“100%”时,查询的是字面的百分号而不是匹配任意字符。
我拿到任何一份新源码的习惯,是先把 web.xml 和数据库脚本读一遍,确认入口 URL 和表结构之后才启动项目。这份宠物管理系统我也照样过了一遍,驱动不打包、中文乱码、相对路径 404,几个典型的坑全踩过才确认源码本身没有大问题。从那以后,每次部署老的 Servlet 项目,我都会把“驱动是否进 WEB-INF/lib、编码是否设置、上下文路径是否一致”这三项强制过一遍,再也没有在环境问题上反复翻车。你照着这篇笔记跑一遍,大概率也会遇到其中一两个问题,回第 5 章对号入座就行。希望帮到你。
本文还有配套的精品资源,点击获取