简介:基于Servlet、JSP与MySQL的影视管理系统Java Web课程设计项目,完整覆盖从需求分析、MVC分层设计到前后端实现与测试的开发流程。系统包含用户注册登录、电影信息查询、评论管理与排行榜等典型功能,适合Java Web初学者、课程设计学生及需要项目参考的开发者。压缩包共112个文件,除核心Java源码与JSP页面外,还提供SQL建表脚本、CSS样式、JS交互脚本、Jar依赖库及XML配置,总大小10.03MB,目录结构清晰,可直接导入IDE运行。已有132人学习下载。通过该项目可深入理解Servlet处理机制、JSP渲染逻辑与JDBC数据库操作的完整链路,并在现有代码基础上快速二次改造,是巩固Java Web实践能力的优质案例。
1. 基于servlet+jsp+mysql的影视管理系统,结课答辩前你只需要盯住这三层
课程设计里“基于servlet+jsp+mysql的影视管理系统.zip”这类题目出现频率极高,它不追求技术新,而是有意把 Java Web 最原始的链路——浏览器发请求、Servlet 接参数、JSP 渲染页面、MySQL 落库——全部暴露在明面上。正因为没有框架替你托管,真正理解 Tomcat 什么时候编译 JSP、连接串里每个参数为什么存在,这个项目才能顺利跑通,而不是靠复制一份现成代码蒙混过关。
这个系统适合两类人:一类是拿到课程设计题目后需要几天内交付可演示版本的学生,另一类是想借小型业务把 Servlet 生命周期、JDBC、会话管理串起来讲的初级开发者。实现思路并不复杂,难的是把 Tomcat 面对 JSP 时的编译机制、MySQL 8.0 的驱动与认证差异、请求转发时参数作用域这几个课堂不讲的部分补齐。这篇内容就按“理论 → 实现 → 部署排错 → 答辩加分”的顺序,把你手工搭这套系统时最常见的动作和报错一次性讲完。
2. 课程设计里的 MVC 分层:servlet 管流程,jsp 只渲染,mysql 承接持久化
2.1 为什么是三件套而不是 Spring Boot
很多人在动手前会问:课程设计为什么还要用 servlet+jsp+mysql?Spring Boot 用三行注解就能起一个 Web 服务,谁还愿意手写 HttpServlet?但框架把细节藏起来之后,报错的解释成本反而更高。你配一个 DispatcherServlet,就要面对 DispatcherServlet、webApplicationContext 和 rootWebApplicationContext 这类父子容器概念,而在三件套里,Servlet 从哪里来、走到哪里去,代码里写得明明白白,课堂上一个请求的生命周期演示就能完整覆盖。
影视管理系统本质是增删改查:核心业务一个 MovieServlet 就能吃下,JSP 承担页面展示,MySQL 只需三张表左右。这个技术栈的代码量远小于引入 Spring Boot,而且它对 IDE 版本、JDK 版本、网络环境都不挑,能减少答辩现场的环境偶发问题。从学习维度看,做完这个项目你能回答“JSP 第一次请求为什么慢”“PreparedStatement 和 Statement 区别”“session 为什么依赖 Cookie”这类问题,它们比框架注解更常出现在面试里。
2.2 项目目录结构与 WEB-INF 的分工
课程设计项目的目录结构,直接决定了部署时能不能被 Tomcat 正常识别。Eclipse 里 Dynamic Web Project 的源码目录叫 WebContent,IDEA 项目则叫 webapp,不管哪种,内部都要遵守同一个约定:WEB-INF 目录下保存 web.xml、lib、classes,外部浏览器访问不到,只有服务器内部能读取。一个典型的影视管理系统可以这样组织:
webapp/ ├── index.jsp ├── WEB-INF/ │ ├── web.xml │ ├── lib/ │ │ ├── mysql-connector-java-8.0.33.jar │ │ └── jstl-1.2.jar │ └── jsp/ │ ├── login.jsp │ ├── movie_list.jsp │ └── movie_edit.jsp ├── static/ │ ├── css/ │ └── js/ └── upload/ # 海报等上传文件的落盘目录 src/main/java/ └── com/example/movie/ ├── entity/Movie.java ├── dao/MovieDao.java ├── servlet/ │ ├── MovieListServlet.java │ ├── MovieSaveServlet.java │ └── MovieDeleteServlet.java └── util/DBUtil.java把 JSP 放到 WEB-INF/jsp 而不是根目录,是为了不让用户直接输入 URL 访问页面,所有页面都通过 Servlet 跳转进入,这样登录拦截才能生效。lib 目录放 mysql 驱动、JSTL 等 jar 包,其中 mysql-connector-java 要选和数据库版本匹配的:MySQL 8.0 对应驱动类 com.mysql.cj.jdbc.Driver,5.7 及更旧版本用 com.mysql.jdbc.Driver。classes 目录放着 Servlet 编译后的 class 文件,由 IDE 自动输出,不要手动改。upload 目录用于封面图落盘,需要让 Tomcat 进程对它拥有读写权限,否则保存图片时会直接抛 IOException。
2.3 web.xml 注册 Servlet,还是直接用 @WebServlet
Servlet 3.0(Tomcat 7 及以上)开始支持 @WebServlet 注解,可以完全不写 web.xml。课程设计我建议保留 web.xml,理由很直接:老师打开文件就能看到哪些路径对应哪个 Servlet,不用逐个类去翻注解,而且老版本 IDE 和 Tomcat 的兼容性更好。如果项目里还要加 Filter 和 Listener,集中写在 web.xml 里也更容易交代。下面是 web.xml 最简配置:
<?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"> <display-name>movie-admin</display-name> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <servlet> <servlet-name>MovieListServlet</servlet-name> <servlet-class>com.example.movie.servlet.MovieListServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>MovieListServlet</servlet-name> <url-pattern>/movie/list</url-pattern> </servlet-mapping> <session-config> <session-timeout>30</session-timeout> </session-config> </web-app>上面这段里,servlet-name 只是内部标识,真正决定访问路径的是 servlet-mapping 中的 url-pattern。这里配置为 /movie/list,浏览器访问地址就是 http://localhost:8080/项目上下文/movie/list。有人把 url-pattern 写成 /movie/list/,导致带子路径的请求也进入同一个 Servlet,做权限校验时容易出现 uri 判断失误;课程设计只写精确匹配就够了。url-pattern 一共有三类写法:精确匹配(/movie/list)、路径匹配(/movie/)、扩展匹配(*.do),它们之间有优先级,但小型项目不需要全部掌握。
两种注册方式对比如下:
| 对比点 | web.xml 注册 | @WebServlet 注解 |
|---|---|---|
| 适用 Servlet 版本 | 2.5 及以前必须 | 3.0+ |
| url-pattern 可见性 | 集中管理,一眼看全 | 分散在各实体类上 |
| 老 IDE 兼容性 | 更好 | 需要项目 facets 正确 |
| 课程设计演示 | 方便现场改路径 | 改完必须重新编译 |
2.4 一个 Servlet 要具备的最小结构
Servlet 的生命周期由 Tomcat 管理:实例化、init、service、destroy。平时你只需要重写 doGet 或 doPost,service 方法会在内部根据 HTTP 方法自动转发到对应的方法。以影视列表为例,最简单的 List Servlet 长这样:
@WebServlet("/movie/list") // 注解式映射,与 web.xml 二选一 public class MovieListServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 必须在第一次读取参数之前调用,否则POST请求体已按默认编码解码 req.setCharacterEncoding("UTF-8"); // 指定响应类型和字符集,缺了charset中文会乱码 resp.setContentType("text/html;charset=UTF-8"); String keyword = req.getParameter("keyword"); // 从 ?keyword=关键字 取值 MovieDao dao = new MovieDao(); List<Movie> movies = dao.searchByName(keyword); // DAO层负责拼SQL req.setAttribute("movies", movies); // 塞进request作用域 req.getRequestDispatcher("/WEB-INF/jsp/movie_list.jsp") .forward(req, resp); // 服务器内部转发 } }这里有两个关键动作。req.setAttribute 把查询结果放进 request 作用域,JSP 能用 ${movies} 直接取到;getRequestDispatcher().forward() 是服务器内部转发,地址栏不变,request 里的属性在 JSP 中依然有效。如果这里误用 sendRedirect,浏览器会发起第二次全新请求,request 属性全部丢失,JSP 里拿到的就是 null。WEB-INF 目录是受保护的,浏览器直接访问 http://localhost:8080/项目/WEB-INF/jsp/movie_list.jsp 会得到 404,只能通过 Servlet 转发进入,这本身就构成了一层页面访问控制。
3. 影视管理系统的核心链路:movie 表、JDBC 查询与 jsp 列表页的串接
3.1 建库建表:字段长度和索引是答辩里能细讲的地方
影视管理系统最好拆成两张表:movie 与 category。category 存电影分类,movie 通过外键关联它,避免在 movie 表里重复存“动作”“科幻”这类字符串,这是课程设计评审时最容易看到的规范点之一。建表 SQL 可以直接在 MySQL Workbench 或 Navicat 里执行:
CREATE DATABASE IF NOT EXISTS movie_db DEFAULT CHARACTER SET utf8mb4; USE movie_db; CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL UNIQUE COMMENT '分类名: 动作/科幻/爱情' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE movie ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL COMMENT '片名', director VARCHAR(50) COMMENT '导演', category_id INT, release_year YEAR, rating DECIMAL(2,1) COMMENT '评分, 精确到0.1', poster VARCHAR(255) COMMENT '海报图片URL或相对路径', description TEXT, created_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_movie_category FOREIGN KEY (category_id) REFERENCES category(id) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ALTER TABLE movie ADD INDEX idx_title (title); ALTER TABLE movie ADD INDEX idx_rating (rating);字段说明是答辩时可以直接念出来的内容:
| 字段 | 类型 | 说明 |
|---|---|---|
| title | VARCHAR(100) | 片名,模糊查询的主字段 |
| rating | DECIMAL(2,1) | 评分范围 0.0~10.0,精确到一位小数 |
| release_year | YEAR | 用 YEAR 类型比 VARCHAR 省空间,排序也正确 |
| category_id | INT | 外键,关联 category 表的 id |
| poster | VARCHAR(255) | 封面图存相对路径,方便整个项目迁移目录 |
字符集选择 utf8mb4 而不是 utf8,是因为 utf8mb4 能完整存储 emoji 和生僻字,MySQL 8.0 默认就是 utf8mb4。外键 ON DELETE SET NULL 的含义是:删除某个分类后,属于该分类的电影保留,只是 category_id 被置空,这比 ON DELETE CASCADE 更符合“误删分类不应该连电影一起删”的直觉。两个 ALTER TABLE 语句给 title 和 rating 建了索引,虽然课程设计数据量很小、索引效果不明显,但答辩时可以说“搜索片名和按评分排序的语句会走二级索引,避免全表扫描”,这句话比单纯展示堆代码有用得多。
3.2 JDBC 连接串参数:少了 serverTimezone 页面就会白等
JDBC 连接串是课程设计里翻车率最高的位置,报错常见的有三类:ClassNotFoundException、Public Key Retrieval is not allowed、Server returns invalid timezone。这些问题都能通过一个正确的连接串和驱动类提前规避。写一个专门的 DBUtil 来管理连接,是最简洁的做法:
public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/movie_db" + "?useUnicode=true&characterEncoding=UTF-8" + "&serverTimezone=Asia/Shanghai&useSSL=false" + "&allowPublicKeyRetrieval=true"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { // 注意: 驱动8.0+用com.mysql.cj.jdbc.Driver Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError("没找到mysql驱动, 检查WEB-INF/lib"); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }连接串里每个参数都有明确作用:
| 参数 | 作用 | 缺失时的典型表现 |
|---|---|---|
| useUnicode=true&characterEncoding=UTF-8 | 客户端与数据库之间按 UTF-8 传输字符 | 中文乱码 |
| serverTimezone=Asia/Shanghai | 指定服务器时区 | 报 serverTimezone 相关连接异常 |
| useSSL=false | 本地开发不启用 TLS 握手 | 连接卡顿或 SSL 握手告警 |
| allowPublicKeyRetrieval=true | 允许客户端获取 RSA 公钥 | Public Key Retrieval is not allowed |
allowPublicKeyRetrieval 是 MySQL 8.0 后才常见的问题:8.0 默认认证插件是 caching_sha2_password,首次连接需要服务端公钥做加密认证,驱动默认不允许自动获取公钥,于是直接拒绝。如果你的数据库是 5.7,把驱动换成 mysql-connector-java 5.1.x,连接串里去掉这个参数、驱动类改回 com.mysql.jdbc.Driver 就够用。本机如果不想一步步安装 MySQL,也可以直接用 docker 拉起一个 8.0 实例,映射 3306 端口后连接串不变,能省掉一些环境变量配置上的时间。
3.3 模糊查询与 PreparedStatement 参数:LIKE 拼错才最让人头疼
搜索是影视管理系统的核心交互,用户输入片名或导演名,后台做模糊匹配。这里推荐用 PreparedStatement 而不是直接拼字符串,一方面防止 SQL 注入,另一方面参数用问号占位,代码更干净:
public List<Movie> searchByName(String keyword) { String sql = "SELECT m.id, m.title, m.director, m.release_year, " + "m.rating, m.description, c.name AS category_name " + "FROM movie m LEFT JOIN category c ON m.category_id = c.id " + "WHERE m.title LIKE ? OR m.director LIKE ? " + "ORDER BY m.rating DESC"; List<Movie> list = new ArrayList<>(); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { String like = "%" + (keyword == null ? "" : keyword.trim()) + "%"; ps.setString(1, like); ps.setString(2, like); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { Movie m = new Movie(); m.setId(rs.getInt("id")); m.setTitle(rs.getString("title")); m.setRating(rs.getDouble("rating")); list.add(m); } } } catch (SQLException e) { e.printStackTrace(); // 课程设计阶段先打印, 交付前换成日志方式 } return list; }这段代码里有两个需要留神的细节。第一个是 LIKE 参数的位置:SQL 里有两个问号,分别对应 title 和 director,所以 ps.setString 要按顺序调用两次,顺序颠倒或漏掉一个,会直接抛 Parameter index out of range。第二个是 keyword 为 null 的情况,req.getParameter 在参数不存在时返回 null,String.trim() 会抛 NullPointerException,所以先判空再拼接 %%。LEFT JOIN 的选择也有讲究:如果某部电影还没有分类,INNER JOIN 会把整行过滤掉,LEFT JOIN 则保留电影行,category_name 显示 null,页面里用 JSTL 判断空值即可。try-with-resources 写法保证 Connection、PreparedStatement、ResultSet 在方法结束时自动关闭,避免连接泄漏,这个细节在答辩时可以直接说“所有 JDBC 资源都在 try-with-resources 内关闭,防止数据库连接耗尽”。
3.4 jsp 列表页:EL 表达式与 JSTL 的配合
查询结果通过 request.setAttribute("movies", movies) 传到 JSP 后,页面用 EL 表达式和 JSTL 标签来渲染表格。一个完整的影视列表页可以这样写:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <body> <h2>影视列表</h2> <form action="${pageContext.request.contextPath}/movie/list" method="get"> 片名: <input type="text" name="keyword" value="${param.keyword}"> <input type="submit" value="搜索"> </form> <table border="1"> <tr><th>ID</th><th>片名</th><th>导演</th><th>评分</th><th>操作</th></tr> <c:forEach items="${movies}" var="m"> <tr> <td>${m.id}</td> <td>${m.title}</td> <td>${m.director}</td> <td>${m.rating}</td> <td> <a href="${pageContext.request.contextPath}/movie/edit?id=${m.id}">编辑</a> <a href="${pageContext.request.contextPath}/movie/delete?id=${m.id}">删除</a> </td> </tr> </c:forEach> </table> </body> </html>这里最容易踩坑的是 ${m.title} 到底取什么:EL 表达式会调用 Movie 类的 getTitle() 方法,也就是必须写标准的 getter/setter,如果属性叫 title 却没有 getTitle(),页面会直接报 PropertyNotFoundException。${param.keyword} 是从查询串里取参数的专用写法,相当于 request.getParameter("keyword"),用来在刷新后回显搜索关键字。action 和链接里都写了 ${pageContext.request.contextPath},这是获取当前应用上下文路径的动态方式,如果项目部署到 Tomcat 时上下文名改成别的,链接仍能正确拼接,写死 /movie 会导致部署路径一变所有链接全 404。JSTL 的 <c:forEach> 是循环标签,items 是要遍历的集合,var 是每次循环的临时变量名,循环体里就是一行表格数据。
4. Tomcat 部署时 jsp 编译 class、mysql 连接与 404 的排查路径
4.1 配置 Tomcat 后 jsp 编译成的 java 类和 class 文件在哪
Tomcat 部署后第一次访问某个 JSP 页面,会明显卡一下,之后再用就变快,原因是 JSP 要由 Jasper 引擎编译:JSP 文件先被翻译成 Servlet 源码 .java,再编译成 .class,最后才由容器调用。这正好解释了配置 Tomcat 后查看 JSP 编译后的 java 类这个高频问题。中间产物默认放在 Tomcat 的 work 目录下,路径结构是:
work/Catalina/localhost/应用上下文名/org/apache/jsp/ ├── movie_list_jsp.java └── movie_list_jsp.class在命令行里也可以直接查找:
# Linux / macOS find /usr/local/tomcat/work -name "movie_list_jsp.java" 2>/dev/null # Windows PowerShell Get-ChildItem -Recurse -Path "$env:CATALINA_HOME\work" -Filter "movie_list_jsp.java"打开生成的 movie_list_jsp.java,能看到整个 JSP 页面被翻译进 _jspService(HttpServletRequest, HttpServletResponse) 方法里,静态 HTML 变成了 out.write() 调用,<c:forEach> 标签则被展开成真实运行的 Java 循环代码。这就是为什么 JSP 里杂糅大量 <% %> 脚本片断会拖慢页面:每个脚本片断都会原样嵌入 Java 方法体,请求越多执行负担越重。如果修改 JSP 后刷新页面还是旧内容,先看浏览器缓存,再看 work 目录对应 .class 文件的修改时间是否真的更新了;手动删除整个 work 目录再重启 Tomcat,会让所有 JSP 重新编译,这是一种强制清理手段。修改 JSP 后的“离开页面提示”那种 onbeforeunload 客户端行为属于浏览器事件,跟 Jasper 编译没有关系,别混在一起排查。
4.2 MySQL 从安装到连接:五个高频报错的定位顺序
MySQL 环境没就绪时,项目启动后所有请求都会卡在数据库操作处。开发机上的 MySQL 可以用免安装版 zip 快速搭建:解压后新建 my.ini 指定 basedir 和 datadir,管理员命令行执行 mysqld --initialize-insecure 做初始化,再用 net start mysql 启动服务,最后用 mysql -uroot 连接给 root 设置密码。整个流程里,验证服务是否在监听比反复改 Java 代码更有效:
# 1) 检查 MySQL 进程 tasklist | findstr mysql # 2) 确认 3306 端口在监听 netstat -ano | findstr 3306 # 3) 本机登录并查看目标库 mysql -uroot -p123456 -h127.0.0.1 -P3306 movie_db -e "show tables;"下面是课程设计里最高频的 MySQL 连接报错,按出现频率排好了定位顺序:
| 报错现象 | 常见原因 | 定位方式 |
|---|---|---|
| ClassNotFoundException: com.mysql.cj.jdbc.Driver | WEB-INF/lib 下缺驱动 jar | 检查 jar 是否在 lib 目录,重启 Tomcat |
| Access denied for user 'root'@'localhost' | 密码错误或认证插件不匹配 | mysql -uroot -p 本机登录验证 |
| Unknown database 'movie_db' | 库没创建或连接串库名拼错 | show databases; |
| Communications link failure | MySQL 服务没启动 / 端口被改 | netstat 确认 3306 监听 |
| Public Key Retrieval is not allowed | MySQL 8.0 默认认证插件问题 | 连接串加 allowPublicKeyRetrieval=true |
提示:数据库连接问题不要先怀疑 Java 代码。先用 MySQL 客户端工具(Workbench 或 Navicat)在外部连一次同一地址和账号,能连上就说明问题在项目侧,连不上就把精力放在 MySQL 服务、端口和密码本身。
4.3 404、500 与“找不到主类”的排查顺序
Servlet 项目部署后最常见的错误并不是代码崩溃,而是 404。排查顺序应该是:先确认应用上下文,也就是 URL 里的第一段路径。项目部署在 Tomcat webapps 下产生一个名为 movie 的目录时,访问地址是 http://localhost:8080/movie/movie/list;如果打成 ROOT.war 部署,地址才是 http://localhost:8080/movie/list。上下文不一致,所有的链接都会 404。
第一层级看 URL 与 web.xml 或 @WebServlet 里的 url-pattern 是否一一对应。把浏览器地址换成直接访问 Servlet 本身,例如 /movie/list,如果这个地址也 404,说明映射有问题;如果 Servlet 能访问但页面报 404,说明转发目标 JSP 的路径不对,重点检查是不是写成直接访问 WEB-INF 下文件。第二层级是 500:Tomcat 错误页会给出 ServletException 和完整堆栈,注意最底部的 Caused by,NullPointerException 通常是没查数据库就取对象属性,SQLException 就回到 4.2 的表格里对号入座。第三类是 IDE 里常见的“错误: 找不到或无法加载主类”,它发生在没有 main 方法的 Servlet 工程里,原因是把 Web 项目当作普通 Java Application 运行了。Eclipse 里右键选择 Run As -> Run on Server,IDEA 里先配置 Tomcat Server 再以 war exploded 方式部署,让容器自己加载类和 lib,而不是让 JVM 找一个不存在的入口类。
5. 让影视管理系统从“能跑”到“能讲”:过滤器、连接池与答辩加分点
5.1 用 Filter 统一登录拦截与编码设置
系统一旦加入后台管理,就需要登录校验。在每个 Servlet 里都写一遍 session 判断是课程设计最常见的冗余代码,正确做法是用 Filter 统一拦截后台路径。Filter 在请求到达 Servlet 之前执行,多个 Filter 按照 web.xml 里的注册顺序组成过滤链。下面这个过滤器统一处理了编码和登录判断:
@WebFilter(urlPatterns = {"/movie/*", "/admin/*"}) public class AuthFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; req.setCharacterEncoding("UTF-8"); // 全局编码, 替代每个Servlet中的重复写法 HttpSession session = req.getSession(false); boolean needAuth = !req.getRequestURI().endsWith("/movie/list"); if (needAuth && (session == null || session.getAttribute("loginUser") == null)) { ((HttpServletResponse) response) .sendRedirect(req.getContextPath() + "/login.jsp"); return; } chain.doFilter(request, response); // 放行到目标Servlet } }这里有两个易错点。getSession(false) 表示不主动创建 session,如果用户未登录就访问后台,这个方法返回 null,配合后面的空判断才能正确跳转;如果写成 getSession(true),每个未登录请求都会被创建一个新的 HttpSession,白白占用内存。过滤链的最后必须执行 chain.doFilter,否则请求会在过滤器中断,页面表现为一直空白。拦截路径用注解里的 urlPatterns 声明,注意别把 login.jsp 和静态资源(css/js)也拦进来,否则登录页自己都会被重定向到登录页,形成循环跳转。
5.2 连接池与事务:老师问“性能”时怎么答
课程设计阶段用 DriverManager 每次 getConnection 完全够用,但答辩时如果被问到“并发请求多了怎么办”,连接池就是最好的回答素材。常见做法是用 Druid 或 HikariCP 替换裸 JDBC,把 DBUtil 里的 DriverManager.getConnection 换成从池中获取连接:
DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl(URL); dataSource.setUsername("root"); dataSource.setPassword("123456"); dataSource.setInitialSize(5); // 启动时建立5个连接 dataSource.setMaxActive(20); // 连接池最大连接数 dataSource.setMaxWait(60000); // 拿不到连接的等待时间, 单位毫秒 Connection conn = dataSource.getConnection();注意使用连接池后,代码里的 conn.close() 不再是真正的关闭连接,而是把连接归还给池子,这一点可以直接讲给老师听。下面对比两种方式:
| 对比项 | DriverManager.getConnection | Druid/HikariCP 连接池 |
|---|---|---|
| 连接获取 | 每次请求新建一条 TCP 连接 | 从池中复用空闲连接 |
| 并发 100 次请求 | 连接数线性增长,延迟明显 | 稳定在 initialSize 到 maxActive |
| 额外依赖 | 只有 mysql 驱动 | druid jar + slf4j-api jar |
| 课程设计工作量 | 零配置 | 多 3-5 行配置 |
事务方面,如果影视管理里有“删除分类时同步更新电影列表”“登记评价后刷新评分”这类跨表操作,就值得在 Save Servlet 里用 Connection.setAutoCommit(false),业务逻辑结束后 commit,任一步异常就 rollback。MySQL 的存储过程在这个项目里通常不是必须,但如果老师追问数据库能力,可以顺手提“也可以用存储过程封装统计电影数量的逻辑,用 CallableStatement 调用”。
5.3 三个能写进答辩稿的具体细节
最后整理三个成本低、效果好的加分项,可以直接写进演示稿或答辩陈述里:
| 加分点 | 实现位置 | 一句话讲解口径 |
|---|---|---|
| 索引 | movie 表的 title、rating 列 | 搜索和评分排行的 SQL 走二级索引,避免全表扫描 |
| 事务 | MovieSaveServlet 里操作多张表 | setAutoCommit(false) 保证数据一致性,异常时 rollback |
| 连接池 | DBUtil 改为 Druid 数据源 | 连接复用替代每次新建,支撑更高并发 |
评审老师最在意的是“这个项目是不是你自己写的、能不能讲清楚”。有一个很有效的准备动作:在答辩前打开 Tomcat 的 work 目录,用 javap -c 反编译访问过的 movie_list_jsp.class,在字节码里找到页面中 JSTL 循环对应的位置。当你能够指着反编译结果说“JSP 在第一次访问时被 Jasper 编译成一个 Servlet,我的页面循环就在这里展开”时,这门课程设计的验收标准就已经完全落在你这一侧了。
本文还有配套的精品资源,点击获取