基于JavaEE的宠物领养网站课设实战:从Servlet/JSP到JDBC全流程 简介这份毕业设计资料为基于JavaEE的宠物领养网站设计与实现论文面向高校计算机相关专业学生及需要完成JavaWeb方向毕设的开发者。论文围绕“宠物之家”实际项目从绪论、需求分析开始依次说明系统功能框架、数据库结构、基于JSF表现层、EJB业务逻辑层及MySQL数据存储层的设计并给出环境搭建、代码编写、测试与结论等完整章节其中用户注册登录、宠物信息管理、领养申请、论坛交流等核心模块论述清晰可作为选题论证、论文结构搭建和系统开发落地的参考范本。资源包仅含1个docx文档大小2.44MB为可编辑Word格式便于直接查阅摘要、中英文目录、正文与参考文献。该资源已有167人学习尤其适合正在撰写JavaEE宠物领养类毕业设计论文或需要扩充设计思路、梳理开发流程的学习者。1. 基于JavaEE下宠物领养网站这门课设到底在做什么如果你是被“基于JavaEE下宠物领养网站的设计与实现”这个标题吸引进来的大概率是正在做Java方向课程设计或毕业设计的学生。这个题目看起来平平无奇但把它做扎实了其实是理解Java后端Web开发整套链路的一条捷径它要求你从前端页面写起穿过Servlet/JSP这一层经典的JavaEE组件落到JDBC操作MySQL数据库再回到HTTP响应把结果渲染到浏览器。和那些直接上SpringBoot的课设相比用JavaEE做宠物领养网站会更“啰嗦”——每一层代码都得自己写但正是这种不偷懒的过程帮你把JavaWeb开发的骨架完整地摸了一遍。宠物领养网站的核心诉求并不复杂用户能浏览待领养宠物、查看详情、提交领养申请管理员能发布宠物信息、审核领养申请、管理用户和公告。难的点在于把领养这件事做成一条可信的业务流——申请被提交后要经过审核、拒绝或通过宠物状态要同步变化不能出现“一只猫被两个人同时领走”这种数据一致性事故。这篇文章我会从选题逻辑、数据库设计、核心代码实现、部署方式一直讲到验收自查清单最后用一整章列出我在做这类项目时真实踩过的坑。你可以照着这个顺序复现也可以只挑其中某一块作为自己论文里的核心章节。2. 技术选型与架构设计为什么用“老一套”反而好写论文2.1 不跟风SpringBootJavaEE这道题选型要先看论文要求现在很多学生拿到题目后第一反应是“能不能用SpringBoot实现”。我的建议是先沉住气看清两份材料你的课程大纲或毕业设计任务书里对技术栈是怎么写的。如果题目明确写的是“JavaEE”那正统做法是走JavaEE分层架构——表现层用JSPServlet业务层用JavaBean数据层用JDBC。这里插一句确实有学校允许用SpringBoot然后答辩时只提“基于Java生态”但绝大多数任务书既然单独点名JavaEE就是在要求你展示Servlet生命周期、请求转发与重定向、会话跟踪、过滤器Filter这些底层机制。这些恰恰是SpringBoot封装掉的东西也是评委老师最容易追问的考点。从写论文的角度看JavaEE技术栈还有一层隐性优势它方便画架构图。你可以在论文里给出一个非常经典的“四层模型”——浏览器、Web容器Tomcat、业务对象Service/DAO、数据库每一层职责清晰、依赖方向明确数据如何从请求参数蜕变为数据库记录再变成HTML页面整个过程肉眼可见。SpringBoot虽然是当前工业界主流但它在论文里能写的“原理”大多是框架封装机制比如自动配置、starter依赖、内嵌容器写不好容易变成抄官方文档。两相比较JavaEE更适合作为一篇本科课设论文的技术主线。2.2 包结构与分层规范让代码一眼能看出MVC的影子选定JavaEE后下一步就是建项目结构。我强烈建议你只用一个动态Web项目Dynamic Web Project不要一开始就引入Maven原因是课设规模通常只有几千行代码Maven带来的依赖管理收益不大反而会让部署环节多出不少变数。但你可以在论文里提一句“项目结构遵循MVC思想进行手工分层便于在不依赖构建工具的情况下直观理解各层职责”这样既不回避问题又解释了选择。项目包结构我一般这样拆分你可以直接套用src/ ├── com.petadopt.filter // 编码过滤器、登录过滤器 ├── com.petadopt.servlet // 控制器层接收请求并调用业务层 ├── com.petadopt.service // 业务接口 ├── com.petadopt.service.impl // 业务实现 ├── com.petadopt.dao // 数据访问接口 ├── com.petadopt.dao.impl // 数据访问实现使用JDBC ├── com.petadopt.entity // 实体类对应数据库表 ├── com.petadopt.util // 数据库连接工具、字符串工具 └── com.petadopt.vo // 视图对象比如分页结果、领养申请组合信息WebContent目录下放JSP、CSS、JS和图片资源WEB-INF/web.xml配置Servlet映射如果用Servlet 3.0也可以改用WebServlet注解但我建议还是保留web.xml论文里更好讲解。看起来繁琐但这种分层对论文最大的价值是当你写“系统架构设计”那一章时可以非常具体地把每个类的名字写进架构图里评审老师一眼就能看出你确实自己写过代码。我在答辩时就被问到过“你的请求从JSP到数据库经历了哪些类”照着分层结构回答这个流程说得清清楚楚。2.3 一个容易出彩的设计把数据库工具类写到“够用且不臃肿”JavaEE项目的数据库访问没有MyBatis帮你自动映射也没有Hibernate的Session管理你只能在DAO里用JDBC写SQL。所以第一件事是封装一个DBUtil工具类负责获取连接和释放资源。这里有个分寸问题封装得太简陋每个DAO里都写一遍getConnection显得代码冗余封装得太重比如自己写一个迷你ORM框架又偏离了课设的训练目标。常见做法是准备一个静态方法返回连接再提供一个静默关闭连接和释放资源的方法public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/pet_adopt ?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 你的密码; private static Connection conn; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement st, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (st ! null) st.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }参数说明URL里的useUnicode和characterEncodingutf8决定了写入数据库的中文能不能正常存储useSSLfalse是为了避免MySQL 8.x与旧版驱动之间的SSL握手告警serverTimezone指定时区不设置的话高版本MySQL会报时区错误。Class.forName在JDBC 4.0之后其实可以省略但保留它有一个好处——在论文里可以写“通过Class.forName显式加载数据库驱动”算一个知识点。这里有一个细节值得注意把数据库连接信息直接写死在Java代码里只适合课设场景。但如果你写的论文里被老师追问“生产环境这样做有什么问题”你可以大方承认“硬编码不便于维护生产环境应使用连接池技术如Druid或C3P0并通过配置文件管理连接参数。”论文里把这一句话写上可以在“系统优化与展望”部分加分。3. 宠物领养网站数据库设计表结构、状态机与权限模型3.1 五张核心表从用户到领养记录缺哪张都会卡壳数据库设计是这篇论文的“信息枢纽”表设计好坏直接决定后面代码能写得多顺。我给这个网站设计的核心表一共五张用户表t_user、宠物表t_pet、领养申请表t_adopt_apply、公告表t_notice、宠物类别表t_category。很多新手会把宠物类别做成一个字符串字段放进宠物表里这省事但后面做“按猫/狗筛选”时SQL写起来会很别扭而且不利于扩展。独立成一张表宠物表通过category_id关联它这是比较标准的做法。用户表的设计要特别留意“角色区分”。你可以在表里加一个role字段用int区分管理员和普通用户比如1代表管理员0代表普通用户也可以单独建一张角色表做关联。我的建议是课设阶段用int字段就够了单独建角色表虽然更“正规”但会显著增加编码量而且你是自己项目的唯一用户和管理员多表关联的意义不大。以领养申请表为例字段可以这样设计CREATE TABLE t_adopt_apply ( id INT PRIMARY KEY AUTO_INCREMENT, pet_id INT NOT NULL, user_id INT NOT NULL, applicant_name VARCHAR(20) NOT NULL, applicant_phone VARCHAR(20) NOT NULL, applicant_address VARCHAR(100) NOT NULL, reason VARCHAR(500), status TINYINT DEFAULT 0 COMMENT 0-待审核, 1-已通过, 2-已拒绝, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, handle_time DATETIME, handler_id INT, review_note VARCHAR(200), FOREIGN KEY (pet_id) REFERENCES t_pet(id), FOREIGN KEY (user_id) REFERENCES t_user(id) );这段SQL里有几个关键点。status字段我用TINYINT而不是VARCHAR存“待审核”“已通过”这种中文理由是状态值在Java里用枚举或常量类表示更安全不会出现“已通过”和“通过”这两个字符串不一致导致的查询失败。handle_time和review_note是给审核环节准备的有些课设不做这两个字段导致管理员审核后完全没留下痕迹答辩时被问到“审核依据是什么”会很难受。外键约束在课设里可以建但你要有心理准备后面做宠物删除功能时外键会限制你先删关联记录这个在DAO层的代码顺序里要处理好。3.2 宠物状态机把“待领养/已领养/已下架”设计成闭环宠物表是网站的核心展示数据它不只是“名字、照片、描述”那么简单。我在表里会加一个status字段和owner_id字段分别表示宠物当前状态和领养人ID。CREATE TABLE t_pet ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL, name VARCHAR(30) NOT NULL, breed VARCHAR(30), age VARCHAR(10), gender TINYINT COMMENT 1-公, 0-母, health_note VARCHAR(200), photos VARCHAR(500), status TINYINT DEFAULT 1 COMMENT 1-待领养, 2-审核中, 3-已领养, 4-已下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, owner_id INT, FOREIGN KEY (category_id) REFERENCES t_category(id) );status字段的取值逻辑是这样的管理员添加宠物后默认为“待领养”用户提交领养申请后宠物不立刻改变状态而是等申请被审核通过后改为“已领养”管理员可以让宠物“下架”通常是因为宠物被取回、离世或不再适合展示下架后前台列表里不再显示它。听众可能会问既然申请审核通过后宠物才变状态那提交申请时为什么不改因为可能同时有两个人提交申请如果提交时就锁状态第二个人就无法申请了业务上不合理。正确的做法是宠物处于“待领养”时允许申请提交管理员审核通过某条申请后先把宠物的状态改为“已领养”再原子性地把其他同宠物的待审核申请全部置为“已拒绝”。这个逻辑是整个系统的核心业务规则建议在论文的“业务流程图”里专门画一张状态机图答辩时几乎必问。front-end页面展示的数据来自t_pet和t_category的联表查询图片路径用一个photos字段存逗号分隔的多个URL。这么做的好处是避免另建一张宠物图片表减少前后端联调工作量缺点是处理多图时需要按逗号拆分字符串。课设阶段这完全够用你只要在论文里写“为简化设计图片采用逗号分隔存储后续若图片数量庞大可独立建表”作为扩展即可。3.3 一个查询性能问题领养记录列表的联表与列表分页当后台管理员要查看领养申请记录时光靠t_adopt_apply表的数据是不够展示的——页面上通常要同时显示宠物名、申请人姓名、申请人电话、当前状态、申请时间。这就需要在DAO里做一次多表联查。我提供的SQL语句可以协助你完成这个查询功能SELECT a.id, a.pet_id, p.name AS pet_name, a.applicant_name, a.applicant_phone, a.status, a.create_time FROM t_adopt_apply a LEFT JOIN t_pet p ON a.pet_id p.id ORDER BY a.create_time DESC LIMIT ?, ?LIMIT是分页参数。写这一句时有两个位置的坑需要特别留意第一个是MySQL参数占位符不能用在LIMIT后面JDBC的PreparedStatement对LIMIT后的?支持实际上很弱不同版本MySQL驱动表现不一致有的会报错“near ? at line 1”。解决方法是先把limit和offset用Integer.toString拼进SQL里再设置其他参数。第二个是LEFT JOIN不能省略因为有可能出现宠物已从库中删除但申请表还保留的情况LEFT JOIN会让申请记录仍然显示出来宠物名为NULL。释放查询结果的资源时需要按ResultSet、Statement、Connection的顺序一一关闭。4. 核心功能实现从注册登录到领养主流程的代码闭环4.1 注册登录与会话管理用Filter统一处理登录状态宠物领养网站作为Web应用最关键的技术基础设施是会话保持和登录态校验。注册功能本身并不复杂用户提交用户名、密码、手机号Servlet层接收参数后调用UserService完成重复用户名检查并插入数据库。需要警惕的坑是密码存库前必须做哈希处理不能明文保存。课设阶段推荐使用JDK自带的MessageDigest配合盐值做MD5或SHA-256public static String md5WithSalt(String password, String salt) { String input password salt; try { MessageDigest md MessageDigest.getInstance(MD5); byte[] digest md.digest(input.getBytes(StandardCharsets.UTF_8)); StringBuilder sb new StringBuilder(); for (byte b : digest) { String hex Integer.toHexString(0xff b); if (hex.length() 1) sb.append(0); sb.append(hex); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } }说明一下加盐的目的是防止彩虹表攻击。盐值可以取用户名或者注册时间我一般取用户名因为它是固定的登录时不需要额外存储。在论文里要写明这个逻辑不然评委看到你在代码里做了一次字符串拼接却不说原因会觉得你是“抄的”。登录成功后把用户对象塞进session然后设置一个LoginFilter在过滤器里做一个“未登录用户不能访问后台”的拦截public class LoginFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); String uri request.getRequestURI(); if (uri.endsWith(/login.jsp) || uri.endsWith(/register.jsp) || uri.contains(/login) || uri.contains(/register) || uri.contains(/petList) || uri.contains(/petDetail)) { chain.doFilter(req, resp); return; } if (session ! null session.getAttribute(user) ! null) { chain.doFilter(req, resp); } else { response.sendRedirect(request.getContextPath() /login.jsp); } } }代码逻辑需要解释一下放行的条件有两个一是公开页面和接口包括登录注册页面、宠物列表和宠物详情二是session中存在登录用户。getSession(false)是关键写法它的含义是“拿不到就返回null”而不是“没有就创建一个新的”。如果写成getSession(true)那么每个未登录请求都会创建一个空session既浪费内存又会让过滤器逻辑失效。一个容易漏掉的点AJAX请求被过滤器拦截后如果直接重定向前端拿到的可能是一段HTML而不是JSON导致前端解析失败处理方式是在重定向之前判断请求头X-Requested-With如果是AJAX则返回401状态码由前端代码统一跳转登录页。4.2 宠物上架与列表查询文件上传的几个必备参数管理后台的宠物管理模块是另一个重点。宠物上架必然涉及图片上传JavaEE里上传文件用Servlet 3.0提供MultipartConfig配合Part接口这是最标准的做法。WebServlet(/admin/pet/add) MultipartConfig(maxFileSize 3 * 1024 * 1024, maxRequestSize 10 * 1024 * 1024) public class PetAddServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String name request.getParameter(name); int categoryId Integer.parseInt(request.getParameter(categoryId)); String breed request.getParameter(breed); String age request.getParameter(age); int gender Integer.parseInt(1.equals(request.getParameter(gender)) ? 1 : 0); String healthNote request.getParameter(healthNote); String saveDir getServletContext().getRealPath(/) upload/pet; File dir new File(saveDir); if (!dir.exists()) dir.mkdirs(); StringBuilder photoPaths new StringBuilder(); for (Part part : request.getParts()) { if (part.getContentType() ! null part.getSize() 0) { String fileName System.currentTimeMillis() _ part.getSubmittedFileName(); part.write(saveDir File.separator fileName); if (photoPaths.length() 0) photoPaths.append(,); photoPaths.append(upload/pet/).append(fileName); } } // 调用 PetService.addPet(...) 写入数据库 } }需要你留意的几个关键点保存路径里我用了getServletContext().getRealPath(/)这表示工程实际部署后在Tomcat里的物理路径。这样做的坑在于当你重新部署或清理Tomcat工作目录上传的图片可能被一起擦掉。一个更稳妥的做法是把图片保存到Tomcat之外的固定目录比如D:/pet_upload/同时把Tomcat的虚拟路径映射配置到server.xml或独立的context.xml中但这部分配置讲解起来篇幅较长适合放在论文的“部署与配置”一节用一幅简单路径图辅助说明。图片文件名我用时间戳加原始文件名是因为同一个浏览器上传的图片有可能重名限制上传大小方面MultipartConfig里的maxFileSize限制了单文件3MBmaxRequestSize限制了整个请求10MB这个参数在论文中要写明选择了什么值以及为什么限定这个值——太大挤占磁盘太小用户传不了清晰照片。前台宠物列表的查询可以做成支持按类别筛选分页形式用request.getParameter读取categoryId并在SQL里判断该参数是否为空来决定是否追加WHERE条件。这是动态条件拼接使用JDBC时需要保证SQL关键字顺序和空格位置正确一个常见错误是直接拼字符串时丢了一个空格导致SQL语法上相邻关键字连在一起比如s...和 WHERE之间没有空格最终拼出“id?WHERE”。排查这类问题时打开日志中的打印SQL就能立即发现。4.3 领养申请与审核闭环事务控制在“一个方法”里完成领养模块是整个系统里技术含金量最高的地方。用户在前台点击“申请领养”时前端弹出一个带petId的表单输入姓名、电话、地址、申请理由后提交。Servlet层的处理逻辑很简单取session里的当前用户ID和表单字段调用AdoptApplyService.submit方法。但如果你以为这就完了就忽略了数据库层的约束——同一用户是否可以对同一只宠物重复申请做法是在t_adopt_apply表里联合唯一约束(uid, pet_id)或者先查一次未结束的申请记录如果存在status为待审核的申请则拒绝再次提交。在论文里写清楚这个幂等性控制是很重要的亮点。管理员在后台处理申请时代码需要在一个数据库事务里完成“更新申请状态、更新宠物状态、拒绝其他待审核申请”这三件事public void audit(int applyId, int petId, int newStatus, int handlerId, String note) { Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); AdoptApplyDao dao new AdoptApplyDaoImpl(); // 1. 更新当前申请状态 dao.updateStatus(applyId, newStatus, handlerId, note, conn); if (newStatus 1) { // 2. 申请通过 - 宠物置为已领养 PetDaoImpl petDao new PetDaoImpl(); petDao.updateStatus(petId, 3, conn); // 3. 同一宠物的其他待审核申请置为已拒绝 dao.rejectOtherPending(applyId, petId, conn); } conn.commit(); } catch (SQLException e) { conn.rollback(); throw new RuntimeException(审核领养申请失败, e); } finally { conn.setAutoCommit(true); conn.close(); } }这段代码是回答“你为什么给t_pet表加owner_id字段”的最佳证据只有通过审核的申请才能更新宠物状态而这个更新和申请表状态、其他申请的状态改变处于同一个事务里谁也不会漏执行。事务的代价是连接会一直被占用直到commit或rollback才释放这个阶段如果忘了close连接会直接卡死整个Tomcat的连接池。使用AutoCommit自动模式时要格外注意close顺序许多初学者在finally块里先关了conn而此时事务已经提交或回滚完成后续无需再操作但如果你把结果集的数据读取放在commit之后则可能因为连接已关闭而出错。整体原则是事务方法里所有数据库操作必须在commit/rollback之后结束再进入finally。5. 系统部署与论文配套图表让项目和文档一起长出来5.1 从Eclipse到Tomcat运行完整发布顺序与常见报错整个项目写完后你需要把它跑起来。常见做法是在Eclipse里右键项目Export成WAR包放入Tomcat的webapps目录启动Tomcat后自动解压。步骤很简单但很多新手在这一步翻车。让我列出我在部署时一定会检查的三项第一JDK版本与Tomcat兼容性。Tomcat 9要求JDK 8以上Tomcat 10则默认使用Jakarta命名空间javax.servlet被替换为jakarta.servlet这就意味着如果你的项目里写的还是javax.servlet.http.HttpServlet直接部署到Tomcat 10会包报错ClassNotFound并从404到500表现可能形形色色。课设环境建议用Tomcat 9 JDK 8/11的稳定组合整篇文章也统一使用javax包。第二MySQL驱动版本。MySQL 5.x需要连接器版本5.1.xMySQL 8.x需要连接器版本8.0.x两者主要区别是驱动类名不同5.x是com.mysql.jdbc.Driver8.x是com.mysql.cj.jdbc.Driver写错会出现“ClassNotFoundException”。第三端口占用Tomcat默认8080端口如果之前启动过一次没有关闭Tomcat再次启动会因为端口被占而报错进入Tomcat目录执行catalina.sh run带日志启动更方便查看错误详情。部署运行信息在论文的“系统测试”章节要写清楚把JDK、Tomcat、MySQL、MySQL Connector/J的版本号都列表出来正文段落里简单写“系统在Windows 10环境下部署于Tomcat 9.0.xx”这是评审老师判断项目可复现性的重要依据。5.2 论文的图与表结构图、流程图、用例图怎么画不扣分论文写作阶段除了代码更重要的是几张图。根据我答辩和帮朋友看论文的经验与JavaEE宠物领养网站相关的最有价值的图依次是系统功能模块图用树状图列出用户端和管理端的功能领养业务流程图从提交申请到审核完成的状态流转系统架构图浏览器→JSP→Servlet→Service→DAO→MySQL标出每一层的技术组件数据库ER图至少包含五张表及其关系。关于画法工具你用Visio、draw.io还是StarUML都可以关键是ER图一定要能看出表与表关联——宠物表和类别表用外键关联申请表和宠物表、用户表分别关联这种关系在画图时要用连线加标注明确出来。功能测试这一节我一般用A4表格列出测试用例每行是“功能模块、测试步骤、预期结果、实际结果、是否通过”。比如领养审核功能的用例是用户A与用户B同时申请宠物P管理员通过A的申请确认B的申请被自动拒绝、P状态变为已领养。这同时是对数据库事务设计的验证。这样的表多放几个论文的“系统测试”一章会很扎实。6. 避坑指南与部署后的自查清单这一章是真正能让你少熬夜的部分。我把做这类项目时反复遇到的坑列成清单每条按现象、原因、解决的顺序写清楚。现象一注册时明明是中文用户名存进MySQL后变成问号“”。原因是数据库连接URL没有设置characterEncodingutf8或数据库表本身不是utf8字符集。解决方法是双管齐下第一在JDBC URL里加上useUnicodetruecharacterEncodingutf8确保驱动发给MySQL的请求以UTF-8编码第二在MySQL里建库时指定字符集即CREATE DATABASE pet_adopt DEFAULT CHARACTER SET utf8mb4。注意utf8mb4有别与utf8更完整地支持生僻字和Emoji表情。现象二本地测试能登录部署到另一台电脑后告诉我用户不存在。原因是另一台机器上的数据库没有初始化表结构和测试数据最常见的是只把WAR包拷贝过去了忘记了导入SQL。解决方法是把建表脚本和必要的初始数据比如管理员账号admin/123456保存为一个pet_adopt.sql文件并在论文的附录中完整附上。部署文档里也要写清楚“先导入数据库再部署WAR包”的顺序。现象三上传宠物图片后页面能显示重启Tomcat后图片全没了。原因是图片保存到了WebContent的upload目录下而IDE重新发布项目时会清空原目录或者你手动删除Tomcat的work目录图片也一并被清理。解决方法是把上传路径改为项目外部固定目录并在代码里配置一个常量UPLOAD_DIR D:/pet_upload/。Tomcat默认不支持访问外部目录需要在server.xml的Host节点下添加一段Context配置把外部目录映射成URL路径/petupload。SQLite或文件系统方式均可但你应该在论文里说明本项目选用的是本地文件系统方案原因是实现简单、不引入额外服务。现象四管理员审核通过了一个申请宠物显示“已领养”但申请列表里其他用户的待审核状态没变红。原因是审核方法里只更新了当前申请状态更新宠物状态和批量拒绝其他申请的SQL根本没在同一事务里。解决方法是按照前文audit方法的写法setAutoCommit(false)后执行三类更新再统一commit。如果你在Service方法里使用了多个DAO那么必须让它们共享同一个Connection而不是每个DAO自己getConnection不然事务就失控了。现象五前端JSP页面点击“申请领养”按钮没反应控制台报500错误并提示有空指针。原因非常常见用户未登录时session.getAttribute(user)返回null而Servlet或JSP里直接调用了user.getId()。这里暴露了一个设计缺陷——前端页面用链接或按钮直接访问受保护的Servlet而不是在JSP中先判断登录状态。习惯做法是给这类需要登录的功能单独加一个登录检查过滤器即前文LoginFilter的部分逻辑或者在JSP页面的按钮附近用c:if标签控制显示如果用户未登录则提示“请先登录”。把你的代码写成用户未登录时的处理是答辩中容易获得认可的细节。7. 验证思路与进阶技巧没条件真养猫也要有条件真测试项目的编码和部署完成之后不要急着写“系统测试完毕”然后交给老师。验证这件事要有具体路径。我的习惯是先准备好三组测试数据一个管理员账号、两个普通用户账号、五只不同类别的宠物三只猫两只狗其中一只宠物的status为已下架用来验证列表查询是否真的过滤掉了它。然后按用户故事走一遍完整流程——注册用户A、登录、申请领养宠物1注册用户B、申请同一只宠物1管理员登录、通过A的申请回到用户B的视角确认申请状态已是“已拒绝”且不能再重复申请。这一条流程能同时验证Servlet过滤器的登录拦截、表单提交的数据绑定、Service层事务控制、JSP页面的数据显示这四项核心能力。你可以把这个测试过程写成论文的验收标准。如果你想给自己留一个“后悔药”请在动手写代码之前先把数据库表结构定下来尽量把字段含义和状态枚举在表注释里写清楚。我最初做这个项目时以为后面可以随便加字段结果中途为了区分宠物是否被领养又加了一个owner_id字段结果DAO、JSP、查询SQL改了一串非常被动。把状态值和表结构用注释变成课堂答辩时的重要文档等代码全部完成后你就能直接拿这个表结构图去描述你的“数据模型设计”。对于胜任有余的高效解法也可以在项目尾声引入一个JavaEE的连接池比如Druid它会替换掉DriverManager.getConnection的写法但部署配置会多两步这会成为一个不错的性能优化叙述章节。对于课设规模我的建议是不引入也没问题在论文里简单说明“本项目只服务小规模业务为简化实现未使用连接池生产级部署则推荐使用Druid”即可。最后叮嘱一句如果你是真的把宠物领养网站作为一个“设计”而不是“拼凑”来做尽量亲自把JDBC里PreparedStatement的预编译机制、Servlet从init到destroy的生命周期、JSP编译成Servlet的过程这三个知识点搞清楚。它们就像整个JavaEE这条技术线的三根承重柱答辩时几乎所有的追问最终都会回到它们身上。我当时就是因为理解了JSP本质上会被Tomcat编译成一个Servlet类才在排查JSP页面取不到值时想到去Tomcat的work目录看生成的Java源码一眼发现了变量名写错。这个习惯到今天我还在用。希望今天这篇笔记能帮你少踩几个坑也希望你最后交付的不只是一份能跑起来的代码而是一份真正能讲清楚的设计与实现。本文还有配套的精品资源点击获取