☰
JavaEE宠物领养网站设计与实现:JSP+Servlet三层架构实战
2026/10/5 1:04:06 网站建设 项目流程

简介:面向计算机相关专业毕业设计的论文资源,系统阐述了基于JavaEE的宠物领养网站从选题、需求分析、系统设计、数据库设计到实现测试的完整过程,可作为毕设选题、系统开发与论文写作的一站式参考。资源包内仅1个docx文档,压缩包大小2.44MB,包含中英文摘要、目录及论文全文章节,目前已有167人学习下载。论文以解决流浪宠物救助困难与领养信息不对称为出发点,基于JSF表现层、EJB业务逻辑层和MySQL数据存储层的典型JavaEE分层架构,围绕用户注册登录、宠物信息管理、领养申请、论坛交流等核心模块展开需求与功能分析,并给出了数据库表结构设计、环境搭建、代码实现及测试调试的具体方案。读者可据其快速掌握JavaEE项目的整体设计思路,了解宠物领养场景下前后端分层实现与数据库建模方法,同时获得论文结构安排、章节撰写和测试结论整理方面的示例,尤其对需要完成开题、中期检查或最终答辩文档的学生有实操价值。

1. JavaEE宠物领养网站:一个需要“系统+论文”双交付的课设题

很多人拿到“基于JavaEE下宠物领养网站的设计与实现”这个题目,以为只是把代码跑起来就算完,实际上这是一手系统、一手论文的双交付。这个题目在课程设计和毕业设计里都是标准的管理信息系统题材:三层架构、角色权限、宠物资料维护、领养申请审核、回访记录归档,拆开看全是传统Web开发的底盘技术。选它的人大多看中需求边界清楚、扩展空间够大,JavaEE规范下的技术栈成熟,论文的每个章节都能落到具体代码上,答辩不会一问三不知。适合谁:Java后端刚入门、想用一个完整项目把JSP/Servlet/数据库操作串起来的人,也适合导师要求“从底层写起”不允许直接套 Spring Boot 的学校。

2. 架构与技术选型:这个题为什么走 JSP+Servlet+三层最稳

2.1 JSP/Servlet、SSM还是 Spring Boot:JavaEE 课设的选型依据

把“JavaEE”翻译成课设技术栈,其实是三套方案在竞争。

第一套是纯 JSP + Servlet + JDBC,最传统,老教材和网上的课程设计参考资料最多。优点是每一步都符合教材标准流程:Servlet 收请求、Service 写业务、DAO 查数据库,论文里的结构图和代码片段一一对应。缺点是重复代码多,写一个模块要复制十几个 import,但没有框架黑匣子,老师追问任何一行都能答。

第二套是 SSM,也就是 Spring + SpringMVC + MyBatis。这套在毕设里出现频率极高,代码量比纯 Servlet 少一半,事务和依赖注入都有现成机制。但如果学校课程只教到 JSP,论文里突然冒出一堆注解和 XML 配置,答辩时容易被认为是“网上模板改的”。

第三套是 Spring Boot + MyBatis Plus,开发最快,但严格来说已经不是 JavaEE 传统范围,有些导师不认可这个选题方向。

我一般这样建议:如果学校教材是 JSP + Servlet,就按第一套写,稳妥;如果老师默认会用框架且论文允许写框架整合,选第二套。这篇笔记按第一套展开,因为它最贴近标题里的“JavaEE”,也最容易把论文写厚——框架帮你做掉的事情越多,论文里能写的设计细节越少。

2.2 目录结构与分层:让代码包结构直接映射论文目录

论文的大纲通常是需求分析、系统设计、数据库设计、系统实现、系统测试,JavaEE 的分层结构刚好可以一一对应。我习惯建这样的包结构:

pet-adoption/ ├── pom.xml # Maven 工程描述 ├── src/main/java/ │ ├── com/example/pet/ │ │ ├── entity/ # 实体类:User, Pet, AdoptionApplication │ │ ├── dao/ # 数据访问层:PetDao, UserDao │ │ ├── service/ # 业务层:AdoptService │ │ ├── servlet/ # 控制器层:PetListServlet, AdoptApplyServlet │ │ ├── filter/ # 过滤器:EncodingFilter, LoginFilter │ │ ├── util/ # 工具类:DBUtil, PageUtil │ │ └── exception/ # 自定义异常:ServiceException ├── src/main/resources/ │ ├── c3p0-config.xml # 数据库连接池配置 │ └── db.properties └── src/main/webapp/ ├── jsp/ # 视图层 │ ├── admin/ # 后台管理页面 │ ├── user/ # 前台页面 │ └── common/ # 公共片段:页头、页脚 ├── WEB-INF/web.xml └── uploads/ # 宠物图片上传目录

包结构定成这样以后,论文目录基本就跟着出来了:entity 对应“系统实体设计”,dao 对应“数据持久层设计”,servlet 对应“Web 控制层实现”,页面目录对应“系统功能演示”。写论文时不需要另起炉灶,代码里每一个包都能找到对应小节。

2.3 搭建最小环境:版本配对表与一条能启动的命令

这个题目技术上并不新,但版本坑非常多。最常见的翻车点是教材用 Tomcat 8 写的代码,结果电脑装了 Tomcat 10,启动后直接 ClassNotFoundException。原因后面专门讲,这里先给一张稳定的版本配对表:

组件推荐版本备注
JDK1.8 或 111.8 兼容性最好,老教材代码直接编译
Tomcat8.5.100注意避开 Tomcat 10,jakarta 命名空间是分水岭
MySQL5.7 或 8.0驱动类名不同,8.0 用 com.mysql.cj.jdbc.Driver
Maven3.6.x镜像建议配阿里云,否则依赖下载慢到怀疑人生
IDEIDEA Community / EclipseVSCode 也能配 JavaEE 语言环境,但调试 Tomcat 不如前两者顺手

用 Maven 建工程后,启动只需要一条命令:

mvn clean package -DskipTests

然后把生成的 war 包复制到 Tomcat 的 webapps 目录,启动 Tomcat,浏览器访问http://localhost:8080/pet。这一步能通,说明环境链路没问题,后面才值得继续。

3. 数据模型与权限设计:三张核心表撑起整个领养业务

3.1 领养流程拆解:从游客浏览到回访归档的状态流转

宠物领养网站和普通商品商城最大的区别在于,商品下单后状态是“已支付”,而宠物领养要经过申请、审核、线下接触、决定领养、回访确认,它是一个带人工审核的长流程。这个流程直接决定了数据库表的设计,也决定了论文中业务时序图怎么画。

完整链路可以抽象成这样一个状态流:管理员上架宠物(状态为“待领养”)→ 用户提交领养申请 → 管理员审核申请 → 审核通过后用户线下接触宠物 → 确认领养后宠物状态变为“已领养” → 管理员登记回访记录 → 流程结束。任何一个环节被拒绝,宠物回到“待领养”,申请状态变为“已拒绝”。

设计阶段最容易犯的错误是只给宠物表设计一个状态字段,领养申请也只有一个状态字段,两个表互相之间没有约束关系。实际上这两张表的状态必须联动:宠物是“待领养”时,才能新增申请;申请被批准时,宠物状态必须同步更新。这个约束要在业务层代码里保证,不能只在界面上拦。

3.2 数据库表设计:从需求分析到建表 SQL

整个系统最核心的其实只有三张表:用户表、宠物表、领养申请表,辅助表可以加公告表、回访记录表。字段设计时我尽量控制数量,课设系统的表字段太多反而难写论文,老师问每个字段的理由会非常累。

用户表的设计思路:

字段类型说明
idint主键自增
usernamevarchar(32)登录名,唯一索引
passwordvarchar(64)存 MD5 加盐后的值
phonevarchar(16)领养联系用
rolevarchar(16)枚举:USER / ADMIN
create_timedatetime注册时间

宠物表的字段要注意一下状态字段的语义。状态字段在代码里我建议用英文枚举值而不是中文。数据库里存AVAILABLE、APPLIED、ADOPTED、OFFLINE,页面上再映射成中文“待领养”“申请中”“已领养”“已下架”。这样做的好处是代码里比较状态时不会出现中文比较的玄学问题。

领养申请表是业务核心,字段设计如下:

CREATE TABLE adoption_application ( id INT PRIMARY KEY AUTO_INCREMENT, pet_id INT NOT NULL, user_id INT NOT NULL, reason VARCHAR(500) COMMENT '领养理由', status VARCHAR(16) DEFAULT 'PENDING', apply_time DATETIME, review_time DATETIME, review_remark VARCHAR(255), KEY idx_pet_id (pet_id), KEY idx_user_id (user_id), KEY idx_status (status) );

建表时有两个容易被忽略的点。第一,status 字段要加索引,因为后台列表页最常见的操作就是按状态刷申请列表,没有索引到后期数据量稍微上来一点就会明显变慢。第二,要在 pet_id 上加唯一约束吗?不要加。因为业务允许一只宠物被多个用户申请,只是最终只批准其中一个,如果加了唯一索引,第二个人提交时数据库就会报错,而不是进入“待审核”状态。

3.3 角色与权限模型:三种角色怎么在后端落地

这个网站的角色就三种:游客、普通用户、管理员。游客能看宠物列表和详情,提交领养申请前必须登录;普通用户在登录后可以提交申请、查看自己的申请记录;管理员负责宠物上架下架、审核申请、登记回访记录。

权限落地不能只靠隐藏按钮,要在后端做两层校验。第一层是 Filter 拦截,只允许登录用户访问/user/*路径,只允许管理员访问/admin/*路径:

public class AdminFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpSession session = request.getSession(); User user = (User) session.getAttribute("loginUser"); // 注意:这里不能只判断 user 是否为空,还要判断 role if (user != null && "ADMIN".equals(user.getRole())) { chain.doFilter(req, resp); } else { request.setAttribute("msg", "需要管理员权限"); request.getRequestDispatcher("/jsp/common/error.jsp").forward(request, resp); } } }

逻辑说明:这段代码从 Session 里取登录用户,然后判断角色。这里有一个很多课设都会踩的坑——Session 存的是这个用户的完整对象,如果用户更新了手机号,你必须重新 setAttribute 更新 Session,否则 Session 里的 user 还是旧数据,权限判断也会出问题。

第二层是 Servlet 内部的业务判断。比如管理员审核申请时,必须再查一次该宠物当前状态是不是“待审核的申请对应的状态”,不能因为 Filter 放行了就无条件执行。Filter 管“能不能进”,业务代码管“该不该做”,两层缺一不可。

4. 核心功能落地:宠物展示与领养申请的可复现代码

4.1 连接池配置:c3p0 的关键参数与 MySQL 8.0 驱动差异

技术选型定了纯 Servlet,数据库连接就不建议用 DriverManager 每次手动创建,要引入连接池。选 c3p0 是因为老教材配套资料最多,出问题一搜就有答案。配置写在src/main/resources/c3p0-config.xml:

<c3p0-config> <default-config> <property name="jdbcUrl"> jdbc:mysql://localhost:3306/pet_adoption?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai </property> <property name="driverClass">com.mysql.cj.jdbc.Driver</property> <property name="user">root</property> <property name="password">你的密码</property> <property name="initialPoolSize">5</property> <property name="maxPoolSize">20</property> <property name="minPoolSize">5</property> <property name="idleConnectionTestPeriod">60</property> <property name="testConnectionOnCheckIn">true</property> </default-config> </c3p0-config>

参数说明:initialPoolSize 是启动时预创建的连接数,课设并发量低,5 就够了;maxPoolSize 建议设 20,太小高峰期会阻塞,太大会把 MySQL 的连接数吃满。idleConnectionTestPeriod 设为 60 秒,是让连接池定期检测空闲连接是否有效,防止 MySQL 端主动断开后应用还在用死连接。

注意 MySQL 8.0 的驱动类名是com.mysql.cj.jdbc.Driver,MySQL 5.7 用com.mysql.jdbc.Driver,中间多了一个cj。这个差异在本地联调时最容易翻车,报错信息是 ClassNotFoundException,很多人排查半天才发现是环境版本的问题。

4.2 BaseDao 封装:把 JDBC 样板代码收敛到一个工具类里

纯 JDBC 的代码重复度很高,写 20 个 DAO 方法就有 20 段 getConnection、PreparedStatement、ResultSet。我习惯先写一个 BaseDao,把查询和更新封装成两个泛型方法,子类只传 SQL 和参数,返回结果直接映射成对象:

public abstract class BaseDao { private static ComboPooledDataSource dataSource = new ComboPooledDataSource(); protected <T> List<T> queryList(String sql, BeanRowMapper<T> mapper, Object... params) { List<T> list = new ArrayList<>(); try (Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { for (int i = 0; i < params.length; i++) { ps.setObject(i + 1, params[i]); } try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { list.add(mapper.map(rs)); } } } catch (SQLException e) { throw new ServiceException("查询失败:" + sql, e); } return list; } protected int executeUpdate(String sql, Object... params) { try (Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { for (int i = 0; i < params.length; i++) { ps.setObject(i + 1, params[i]); } return ps.executeUpdate(); } catch (SQLException e) { throw new ServiceException("更新失败:" + sql, e); } } @FunctionalInterface protected interface BeanRowMapper<T> { T map(ResultSet rs) throws SQLException; } }

逻辑说明:这里用了 try-with-resources 语法,Connection、PreparedStatement、ResultSet 在方法结束时自动关闭,不用在 finally 里手工释放,从源头杜绝连接泄漏。参数用可变长 Object 数组,配合 PreparedStatement 的占位符,避免字符串拼接 SQL 带来的注入风险。

接口BeanRowMapper是一个函数式接口,子类在调用时可以用 Lambda 简化。比如查询宠物列表,只需要写三行代码,三行里就能看清楚这条 SQL 查了哪些字段、参数是什么。这种封装方式是论文里“数据访问层设计”一节的直接素材。

4.3 领养申请 Servlet:事务边界的正确姿势

领养申请是整个系统里最有代表性的核心功能,因为它涉及两张表的状态变更,是事务最好的切入点,也是答辩时老师必问的场景。用户提交申请时,要插入一条申请记录,同时把宠物状态从“待领养”改成“申请中”,这两个操作必须在一个事务里,否则就会出现申请记录建了但宠物状态没变的情况。

@WebServlet("/user/adopt/apply") public class AdoptApplyServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); HttpSession session = request.getSession(); User user = (User) session.getAttribute("loginUser"); if (user == null) { response.sendRedirect(request.getContextPath() + "/jsp/common/login.jsp"); return; } int petId = Integer.parseInt(request.getParameter("petId")); String reason = request.getParameter("reason"); try { new AdoptService().applyAdoption(user.getId(), petId, reason); request.setAttribute("msg", "申请已提交,等待管理员审核"); } catch (ServiceException e) { request.setAttribute("msg", e.getMessage()); } request.getRequestDispatcher("/jsp/user/applyResult.jsp").forward(request, response); } }

Service 层的方法是事务的主体:

public class AdoptService { private PetDao petDao = new PetDao(); private ApplicationDao applicationDao = new ApplicationDao(); public void applyAdoption(int userId, int petId, String reason) { // 第一步:用带状态条件的 UPDATE 抢占宠物 int updated = petDao.updateStatusIfAvailable(petId); if (updated == 0) { throw new ServiceException("该宠物已被申请或已下架"); } // 第二步:插入申请记录 applicationDao.insert(userId, petId, reason); } }

这里的关键设计是第一步的 UPDATE 语句。我在宠物表设计时留了 status 字段,这条 SQL 是:

UPDATE pet SET status = 'APPLIED' WHERE id = ? AND status = 'AVAILABLE'

affected rows返回 0 说明宠物已经不处于“待领养”状态,直接拒绝本次申请。这样做比先 SELECT 再 UPDATE 更安全,因为两个用户同时提交时,SELECT 查到的状态都是“待领养”,两个 UPDATE 都可能执行成功,但加上AND status = 'AVAILABLE'这个条件后,数据库的行锁会让后一个 UPDATE 等前一个提交完再执行,第二个的 affected rows 就是 0,并发问题在数据库层就被挡住了。

这段代码还需要再补一点:事务的提交和回滚。上面 Service 的写法简化了事务控制,真正要写到课设代码里,需要把连接绑定到当前线程上。常见做法是在 DAO 里提供一个getConnection()方法,Service 拿到连接后setAutoCommit(false),执行完两个操作后 commit,任何一步抛异常就 rollback:

Connection conn = DBUtil.getConnection(); try { conn.setAutoCommit(false); petDao.updateStatusIfAvailable(conn, petId); applicationDao.insert(conn, userId, petId, reason); conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { DBUtil.closeQuietly(conn); }

事务边界的代码,在论文里要单独给一节,配图和文字把“为什么这两个操作必须在同一事务”讲清楚。

4.4 列表页分页与条件筛选:必考功能的普通但标准写法

宠物列表页是系统的门面,几乎所有管理系统都要做分页和筛选。课设里用 JSP + JSTL 做前端,分页参数由 Servlet 组装后放到 request 里。分页的核心是两段 SQL:

SELECT COUNT(*) FROM pet WHERE status = 'AVAILABLE'; SELECT * FROM pet WHERE status = 'AVAILABLE' ORDER BY create_time DESC LIMIT ?, ?;

LIMIT 的第一个参数是偏移量,计算方式是(currentPage - 1) * pageSize,第二个参数是每页条数。这里有个常见错误:偏移量直接写currentPage * pageSize,这样第二页会重复第一页的最后一条,第三页会重复第二页的最后一条。

Servlet 里把 pageSize 固定为 8,currentPage 从请求参数读,默认值是 1。PageUtil 这个工具类负责封装 totalCount、totalPages、currentPage、pageSize,页面上只负责展示。JSP 里用 JSTL 循环输出宠物卡片,分页条用 c:if 判断上一页下一页是否可用。这一段在论文里的截图价值很高,页面效果图加上 SQL 说明,能撑起“系统实现”一个整节的内容。

5. 常见问题与排查:七个让系统跑不起来的经典翻车场景

5.1 JSP 中文乱码:三条编码链路必须同时一致

现象:页面显示的汉字全是问号,或者表单提交后存入数据库变成乱码。

原因:JavaEE 的编码是链式传递,页面响应编码、请求解码编码、数据库连接 URL 编码,三处只要有一处不一致就乱码。最常见的情况是 JSP 页面设置了 UTF-8,但数据库连接 URL 没带 characterEncoding 参数,或者 Tomcat 收到的 POST 请求没有被正确解码。

解决:第一,JSP 文件顶部加pageEncoding指令;第二,写一个 EncodingFilter 拦截所有请求,统一设置request.setCharacterEncoding("UTF-8");第三,数据库连接 URL 里加characterEncoding=utf8。三条链对齐后乱码基本绝迹,剩下如果还有问题,检查数据库表的字符集是不是 utf8mb4,而不是默认的 latin1。

5.2 Tomcat 10 的包名分水岭:javax 与 jakarta 的暗坑

现象:本地 Tomcat 9 跑得好好的代码,换到 Tomcat 10 后启动报ClassNotFoundException: javax.servlet.ServletException,代码一行没动就挂了。

原因:Tomcat 10 开始把 Servlet API 的包名从javax.servlet改成了jakarta.servlet,这是整个 JavaEE 生态从旧规范迁移到 Jakarta EE 的结果。老教材和大多数网上课设代码都基于 javax,直接扔到 Tomcat 10 当然跑不起来。

解决:工具版本表里已经写了推荐用 Tomcat 8.5 或 9.0,这是最快的路。如果非要用 Tomcat 10,要把所有 import 从javax.servlet全局替换成jakarta.servlet,替换后还要检查第三方库是否兼容。VSCode 配置 JavaEE 语言环境时也要注意这个坑,装了扩展后编译通过了,启动到 Tomcat 依旧挂,多半是版本匹配的问题,不推荐在这个环节浪费时间。课设老老实实用 IDEA Community + Tomcat 8.5 是最少踩坑的组合。

5.3 数据库连接耗尽:Too many connections 的幕后元凶

现象:系统本地跑没几次,第二次打开页面就报Too many connections,重启 MySQL 后恢复正常,过一会儿又不行。

原因:代码里某个查询路径上,连接没有关闭。最典型的是 ResultSet 在循环里提前 return,把 finally 块里的 conn 关闭跳过了。没有连接池时,每次操作新建连接用完即弃,反而不容易累积;用了连接池后,连接没有被归还到池里,池被越掏越空。

解决:检查所有 DAO 方法,确保连接关闭在 finally 块,或者直接使用 4.2 里 try-with-resources 的写法。还有一种隐蔽情况是代码里手动 new 了连接而没走连接池,排查思路是全局搜索DriverManager.getConnection、conn.close,看数量是否匹配。连接池配置里把maxPoolSize临时调到 50 只能缓解症状,治本还要靠代码检查。

5.4 图片上传后的 404:宠物图片为什么重启就丢

现象:管理员在后台给宠物上传图片,当时能正常显示,重启 Tomcat 后图片全部 404。

原因:上传代码把文件保存到了项目部署目录的绝对路径里,比如webapps/ROOT/uploads下。IDEA 或 Eclipse 里重新部署项目时,旧文件被清掉,新部署的目录是空的,图片当然就没了。

解决:把上传目录改到 Tomcat 安装目录之外,比如D:/data/pet/uploads,同时给 Tomcat 配置一个虚拟路径映射,让/uploads/**指向这个外部目录。配置方式是在 Tomcat 的 context.xml 里添加<Context>参数,开发时也可以用 IDEA 的部署设置直接指定外部资源映射。这是做任何带文件上传功能的 Web 项目都必须想清楚的第一件事。

5.5 并发领养导致状态覆盖:两个用户同时申请同一只宠物

现象:两个浏览器同时打开同一只宠物的详情页,同时点申请,最终数据库里出现了两条申请记录,宠物状态被第二次更新覆盖。

原因:没有做状态前置校验,或者校验用 SELECT 但没加锁。两个请求同时读到宠物状态是“待领养”,各自执行 UPDATE,后执行的把前执行的覆盖了。

解决:用 4.3 里写的带状态条件的 UPDATE 解决。核心是让数据库的行锁替应用层把关:UPDATE pet SET status='APPLIED' WHERE id=? AND status='AVAILABLE'这个 SQL 在并发场景下,第二个事务的等待结果影响行数是 0,应用层收到 0 就返回“已被申请”。这个方案写进论文,是一个很自然的并发控制讨论点,比写一段复杂的同步代码更有说服力。

5.6 ResultSet 取值时的低级错误:列名拼写与索引混淆

现象:查询宠物列表一直正常,换了一个表就报Column 'xxx' not found。

原因:getString 方法里写的列名和大写的数据库字段不一致。MySQL 在 Windows 平台大小写不敏感,到了 Linux 就严格区分,本地跑得好好的,部署到服务器上就挂。还有人是按字段顺序用getString(1)、getString(2),一旦 SQL 字段顺序调整,数据全部串位。

解决:养成用列名取值、避免数字索引的习惯。查询时如果用了别名,比如SELECT create_time AS createTime,代码里就要用getTimestamp("createTime"),保持 SQL 别名和 Java 代码的驼峰命名一致,少一处暗伤。

6. 验证与进阶:让系统在演示和答辩时经得起追问

课设系统做得再好,最终要在导师面前演示和答辩。我自己的经验是,提前准备一套“演示脚本”比临时点开页面高效得多。

准备演示数据时,不要只准备“待领养”状态的宠物,要让每个状态都有一条:一只待领养、一只申请中、一只已领养、一只已下架。演示领养流程时,从注册账号开始,登录后筛选宠物,提交申请,切换到管理员账号审核通过,再回到前台看宠物状态变为“申请中”,整个闭环一气呵成,比单独演示每个页面更直观。

验证事务是否正确,有一个非常有效的演示方法:在代码里临时加入一个异常,或者直接在 PetDao 的第二步插入操作前抛一个 RuntimeException,然后走一次申请流程。如果数据库里没有残留半截数据——申请记录没有插入成功,宠物状态也没有变——就证明事务边界是有效的。这个过程完全可以写进测试章节,作为“功能测试”的实例。

进阶方向按投入产出比排,我会做这几件事:用户密码从 MD5 改成 MD5 加盐;给管理员操作加一个简单的操作日志表;宠物列表按时间倒序改成推荐排序加时间倒序;照片上传加一个文件大小和扩展名校验。这四个改动每个只需要不到半天,但功能上和论文深度上都明显上了一个台阶。更高阶的比如接入 Redis 做列表缓存、用搜索引擎替代 SQL 模糊查询,课设阶段不建议碰,复杂度上升却不带来演示效果上的明显提升。

答辩时老师最常追问的三个问题,答案都能在上面的代码里找到:为什么选 JavaEE 而不选框架,因为要体现三层架构和底层理解;领养申请的事务边界在哪里,Service 层里两个 DAO 操作之间;并发问题怎么防,带状态条件的 UPDATE 是数据库层的最后防线。这三个问题能答顺,答辩基本就稳了。

最后说一条我自己交课设时踩过的坑。当年做上传功能,图片存在 Tomcat 的 webapps 部署目录里,以为万事大吉,结果第二次部署全部图片变 404。后来我养成了一个习惯,凡是做上传类功能,第一件事就是问自己“重启之后文件还在不在”,这个问题值得每个做 Web 项目的人多想一遍。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询