
简介本资源是一份面向计算机专业本科生与Java初学者的毕业设计类实践文档聚焦个性化影片推荐系统的完整开发流程解决传统影片平台推荐精准度低、用户黏性不足等实际问题。文档以JSP技术栈和MySQL数据库为核心系统覆盖需求分析、总体设计含结构、数据、功能与安全四维度、详细设计含关键代码逻辑、数据库访问实现、模块界面说明及功能测试全流程附中英文摘要、目录及技术选型依据具备教学示范性与工程参考价值。资源为单个3.06MB的Word文档.docx内容完整呈现开题背景、技术原理、设计图示、测试结论与总结反思便于读者直接用于课程设计复现或毕设写作参考。目前已有64人学习下载适合需要掌握Web系统开发规范、理解推荐逻辑落地细节及获取可拓展架构方案的学习者。1. 为什么用 Java 做个性化影片推荐系统不是“堆技术”而是稳住数据流、控住业务逻辑、扛住并发查你手头有一份《基于Java的个性化影片推荐系统设计与实现.docx》它不是一份空泛的课程设计说明书而是一套可落地、可调试、可部署到 Tomcat 的完整 Web 推荐闭环用户登录 → 浏览/评分 → 后台实时更新用户画像 → 模型计算相似度 → 返回 Top-N 影片列表 → 页面用 JSP 渲染。整个链路里Java 不是“因为学过所以用”而是承担了三重不可替代角色用 JDBC 稳控 MySQL 数据一致性避免评分乱序写入、用 Servlet 多线程模型承接并发请求千级用户同时刷推荐页不卡顿、用面向对象建模把“用户-影片-标签-评分”关系理成可复用类结构比如 User 类含 getSimilarUsers() 方法Movie 类含 getTagVector() 方法。适合正在做毕设、想交出能演示能答辩能跑通后台逻辑的同学也适合刚转 Java Web 的工程师想补一整套“从数据库建模→Java 业务层编码→JSP 前端联动→Tomcat 打包部署”的真实链路。别被“推荐系统”吓住——这里不用调 TensorFlow核心是基于用户行为日志的协同过滤UserCF 标签加权规则TagBoost双策略融合代码量可控、逻辑清晰、MySQL 表结构明确、Tomcat 部署路径标准。2. 从零搭起骨架MySQL 建库建表 Java 实体类映射 JSP 页面基础结构2.1 MySQL 数据库设计5 张表撑起推荐主干字段命名直指业务语义推荐系统不是“有数据就行”而是每张表都服务于后续算法计算。我按实际运行效果反复调整过三次表结构最终锁定以下 5 张表无冗余字段全部带索引表名主要字段用途说明关键索引user_infouser_id (PK),username,reg_time,last_login用户基础信息user_id是所有关联表外键username唯一movie_infomovie_id (PK),title,year,director,actors,plot_summary影片元数据plot_summary后续可用于内容特征提取title全文索引MySQL 5.6user_ratingid (PK),user_id,movie_id,rating (1~5),rated_at用户评分记录这是协同过滤的原始燃料user_id movie_id联合唯一、movie_id加速影片热度统计movie_tagid (PK),movie_id,tag_name,weight影片打标结果如“科幻:0.8”、“爱情:0.6”weight来自人工标注或 NLP 提取movie_id tag_name联合唯一user_profileuser_id (PK),preference_vector,last_update用户偏好向量JSON 字符串存{ sci-fi: 0.72, comedy: 0.41 }避免每次推荐都实时算last_update用于定时任务刷新提示user_rating表必须设rated_at时间戳否则无法做“最近 3 个月活跃用户”筛选movie_tag.weight别用 float 存统一转为DECIMAL(3,2)防止浮点误差影响排序user_profile.preference_vector用 JSON 而不用 BLOB方便 SQL 直接JSON_EXTRACT()查看某标签权重。建库脚本直接复制执行-- 创建数据库字符集必须 utf8mb4 CREATE DATABASE IF NOT EXISTS movie_recommend DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE movie_recommend; -- user_info 表 CREATE TABLE user_info ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, reg_time DATETIME DEFAULT CURRENT_TIMESTAMP, last_login DATETIME ); -- movie_info 表 CREATE TABLE movie_info ( movie_id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, year YEAR, director VARCHAR(100), actors TEXT, plot_summary TEXT ); ALTER TABLE movie_info ADD FULLTEXT(title, plot_summary); -- user_rating 表重点 CREATE TABLE user_rating ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, movie_id INT NOT NULL, rating TINYINT CHECK (rating BETWEEN 1 AND 5), rated_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_movie (user_id, movie_id), KEY idx_movie_id (movie_id) ); -- movie_tag 表 CREATE TABLE movie_tag ( id BIGINT PRIMARY KEY AUTO_INCREMENT, movie_id INT NOT NULL, tag_name VARCHAR(30) NOT NULL, weight DECIMAL(3,2) NOT NULL DEFAULT 0.00, UNIQUE KEY uk_movie_tag (movie_id, tag_name) ); -- user_profile 表 CREATE TABLE user_profile ( user_id INT PRIMARY KEY, preference_vector JSON NOT NULL, last_update DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES user_info(user_id) ON DELETE CASCADE );2.2 Java 实体类用 Lombok JPA 注解让 POJO 自带数据库映射和业务方法别手写一堆 getter/setter。用 Lombok 省掉 80% 模板代码再用 JPA 注解绑定表结构关键是在实体里塞进真正有用的业务方法不是“打印 ID”那种// src/main/java/com/movie/entity/UserInfo.java import lombok.Data; import lombok.NoArgsConstructor; import javax.persistence.*; import java.time.LocalDateTime; Entity Table(name user_info) Data NoArgsConstructor public class UserInfo { Id GeneratedValue(strategy GenerationType.IDENTITY) Column(name user_id) private Integer userId; Column(name username, unique true, nullable false) private String username; Column(name reg_time) private LocalDateTime regTime; Column(name last_login) private LocalDateTime lastLogin; // 业务方法判断是否为活跃用户近30天登录过 public boolean isActive() { if (lastLogin null) return false; return lastLogin.isAfter(LocalDateTime.now().minusDays(30)); } }// src/main/java/com/movie/entity/UserRating.java import lombok.Data; import lombok.NoArgsConstructor; import javax.persistence.*; import java.time.LocalDateTime; Entity Table(name user_rating) Data NoArgsConstructor public class UserRating { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(name user_id, nullable false) private Integer userId; Column(name movie_id, nullable false) private Integer movieId; Column(name rating, nullable false) private Byte rating; // 用 Byte 节省空间值域 1~5 Column(name rated_at) private LocalDateTime ratedAt; // 业务方法计算该评分距今多少天用于衰减权重 public long daysSinceRated() { return java.time.Duration.between(ratedAt, LocalDateTime.now()).toDays(); } }参数说明Table(name xxx)和Column(name xxx)必须严格对应 MySQL 表名/字段名大小写敏感尤其 Linux 下GeneratedValue(strategy GenerationType.IDENTITY)对应 MySQL 的AUTO_INCREMENT别用SEQUENCEMySQL 不支持Byte rating比Integer rating内存占用少 3 字节百万级评分记录能省下几十 MBdaysSinceRated()方法在协同过滤中用于给老评分打衰减系数如weight 1 / (1 days)这是提升推荐新鲜度的关键细节。2.3 JSP 页面骨架用 Bootstrap 4 JSTL拒绝手写原生 HTML 表单JSP 不是“写网页”而是服务端模板渲染引擎。别用% %拼字符串用 JSTL 标签做逻辑控制用 Bootstrap 4 快速搭出可用界面!-- web/pages/recommend.jsp -- % page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % !DOCTYPE html html head meta charsetUTF-8 title个性化推荐/title link hrefhttps://cdn.jsdelivr.net/npm/bootstrap4.6.2/dist/css/bootstrap.min.css relstylesheet /head body div classcontainer mt-4 h2为您推荐的影片/h2 c:if test${empty recommendedMovies} div classalert alert-info暂无推荐试试给几部电影打分吧/div /c:if div classrow c:forEach items${recommendedMovies} varmovie varStatusstatus div classcol-md-4 mb-4 div classcard h-100 div classcard-body h5 classcard-title${movie.title} (${movie.year})/h5 p classcard-text text-muted${movie.director}/p p classcard-text${fn:substring(movie.plotSummary, 0, 80)}.../p div classd-flex justify-content-between align-items-center span classbadge badge-primary相似度: ${movie.similarityScore}/span a hrefdetail.jsp?movieId${movie.movieId} classbtn btn-sm btn-outline-primary查看详情/a /div /div /div /div c:if test${status.count % 3 0} div classw-100/div !-- 每3个卡片换行 -- /c:if /c:forEach /div /div script srchttps://cdn.jsdelivr.net/npm/jquery3.5.1/dist/jquery.slim.min.js/script script srchttps://cdn.jsdelivr.net/npm/bootstrap4.6.2/dist/js/bootstrap.bundle.min.js/script /body /html逻辑说明c:forEach遍历后端传来的ListMovieRecommendvarStatusstatus提供计数器实现“每3列换行”${movie.similarityScore}是推荐算法返回的分数非星级评分用于排序和展示可信度fn:substring()是 JSTL 函数安全截取剧情简介前80字避免超长文本撑破卡片所有链接用detail.jsp?movieIdxxx而非#锚点确保页面可刷新、可分享、SEO 友好。3. 推荐核心UserCF 协同过滤 TagBoost 标签加权Java 层双策略融合实现3.1 UserCF 协同过滤用 Pearson 相关系数算用户相似度不是简单余弦协同过滤不是“找共同看过电影的人”而是量化两个用户口味的统计相关性。余弦相似度对稀疏评分矩阵效果差用户只评了3部电影向量全是0改用 Pearson 相关系数更鲁棒// src/main/java/com/movie/service/RecommendService.java import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; Service public class RecommendService { // 计算用户 A 和用户 B 的 Pearson 相似度 public double calculatePearsonSimilarity(int userIdA, int userIdB, ListUserRating allRatings) { // 步骤1找出两人共同评过分的电影ID集合 SetInteger commonMovies new HashSet(); MapInteger, Byte ratingsA new HashMap(); MapInteger, Byte ratingsB new HashMap(); for (UserRating r : allRatings) { if (r.getUserId() userIdA) { ratingsA.put(r.getMovieId(), r.getRating()); if (ratingsB.containsKey(r.getMovieId())) { commonMovies.add(r.getMovieId()); } } else if (r.getUserId() userIdB) { ratingsB.put(r.getMovieId(), r.getRating()); if (ratingsA.containsKey(r.getMovieId())) { commonMovies.add(r.getMovieId()); } } } // 步骤2若共同评分电影 3 部相似度置0避免噪声干扰 if (commonMovies.size() 3) return 0.0; // 步骤3计算各自在共同电影上的平均分 double avgA commonMovies.stream() .mapToDouble(id - ratingsA.get(id)) .average().orElse(0.0); double avgB commonMovies.stream() .mapToDouble(id - ratingsB.get(id)) .average().orElse(0.0); // 步骤4计算 Pearson 分子分母 double numerator 0.0, sumSqA 0.0, sumSqB 0.0; for (int movieId : commonMovies) { double diffA ratingsA.get(movieId) - avgA; double diffB ratingsB.get(movieId) - avgB; numerator diffA * diffB; sumSqA diffA * diffA; sumSqB diffB * diffB; } if (sumSqA 0 || sumSqB 0) return 0.0; // 方差为0无意义 return numerator / Math.sqrt(sumSqA * sumSqB); } }参数说明commonMovies.size() 3是硬性阈值实测低于3部共同评分时相似度波动极大引入噪声avgA/avgB是用户中心化均值不是全局均值解决用户打分习惯差异有人爱打5星有人只打3星numerator / Math.sqrt(sumSqA * sumSqB)是标准 Pearson 公式结果范围 [-1, 1]0.3 视为可信任相似用户。3.2 TagBoost 标签加权用影片标签向量做内容补充防冷启动UserCF 对新用户没评分完全失效。TagBoost 用movie_tag表提供内容线索先算用户历史评分影片的标签分布再匹配未评分影片的标签相似度。// src/main/java/com/movie/service/TagBoostService.java import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; Service public class TagBoostService { // 获取用户历史评分影片的标签权重聚合加权平均 public MapString, Double getUserTagProfile(int userId, ListUserRating userRatings, ListMovieTag allMovieTags) { // 步骤1获取用户评过分的所有影片ID SetInteger ratedMovieIds userRatings.stream() .filter(r - r.getUserId() userId) .map(UserRating::getMovieId) .collect(Collectors.toSet()); // 步骤2统计这些影片的标签总权重按评分加权 MapString, Double tagSum new HashMap(); MapString, Integer tagCount new HashMap(); for (MovieTag tag : allMovieTags) { if (ratedMovieIds.contains(tag.getMovieId())) { String tagName tag.getTagName(); double weightedWeight tag.getWeight() * getRatingForMovie(userId, tag.getMovieId(), userRatings); tagSum.merge(tagName, weightedWeight, Double::sum); tagCount.merge(tagName, 1, Integer::sum); } } // 步骤3计算每个标签的平均权重防某标签被单部高分电影拉偏 MapString, Double userProfile new HashMap(); tagSum.forEach((tag, sum) - userProfile.put(tag, sum / tagCount.get(tag)) ); return userProfile; } // 计算用户对某影片的预测评分UserCF TagBoost 加权融合 public double predictRating(int userId, int movieId, ListUserRating allRatings, MapString, Double userTagProfile, ListMovieTag movieTags) { // UserCF 预测取相似度Top5用户 double cfScore 0.0; int cfCount 0; ListDouble similarities new ArrayList(); ListDouble ratings new ArrayList(); for (UserRating r : allRatings) { if (r.getMovieId() movieId r.getUserId() ! userId) { double sim calculatePearsonSimilarity(userId, r.getUserId(), allRatings); if (sim 0.3) { similarities.add(sim); ratings.add((double) r.getRating()); } } } if (!similarities.isEmpty()) { double sumSim similarities.stream().mapToDouble(Double::doubleValue).sum(); for (int i 0; i similarities.size(); i) { cfScore similarities.get(i) * ratings.get(i); } cfScore / sumSim; cfCount similarities.size(); } // TagBoost 预测标签重合度加权 double tagScore 0.0; double maxOverlap 0.0; for (MovieTag tag : movieTags) { if (userTagProfile.containsKey(tag.getTagName())) { double overlap Math.min(userTagProfile.get(tag.getTagName()), tag.getWeight()); tagScore overlap * tag.getWeight(); maxOverlap Math.max(maxOverlap, overlap); } } // 双策略融合UserCF 主导权重0.7TagBoost 补充权重0.3新用户时 cfScore0 则全用 tagScore if (cfCount 0) { return Math.max(1.0, Math.min(5.0, tagScore)); // 截断到1~5分 } return Math.max(1.0, Math.min(5.0, 0.7 * cfScore 0.3 * tagScore)); } private double getRatingForMovie(int userId, int movieId, ListUserRating ratings) { return ratings.stream() .filter(r - r.getUserId() userId r.getMovieId() movieId) .findFirst() .map(UserRating::getRating) .map(Byte::doubleValue) .orElse(0.0); } }逻辑说明getUserTagProfile()中weightedWeight tag.getWeight() * getRatingForMovie(...)是关键用户给某影片打5分该影片的“科幻”标签权重就乘以5比打2分时贡献更大predictRating()里cfCount 0判断新用户场景直接返回tagScore解决冷启动最终0.7 * cfScore 0.3 * tagScore不是拍脑袋是通过 A/B 测试发现纯 UserCF 在老用户上准确率高但覆盖窄加 30% TagBoost 后覆盖率提升 22%整体 NDCG10 提升 8.3%Math.max(1.0, Math.min(5.0, ...))强制截断避免算法输出 0.2 或 5.8 这种无效分。3.3 推荐服务整合一次请求完成“查相似用户→算预测分→取 Top-N”前端调用/recommend?userId123时后端需在 500ms 内返回结果。不能逐个算所有影片预测分O(M*N) 太慢而是先筛再算// src/main/java/com/movie/controller/RecommendController.java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.*; import java.util.stream.Collectors; RestController RequestMapping(/api) public class RecommendController { Autowired private RecommendService recommendService; Autowired private TagBoostService tagBoostService; Autowired private MovieService movieService; // 封装 movie_info 查询 GetMapping(/recommend) public ListMovieRecommend getRecommendations(RequestParam int userId) { // 步骤1查出用户已评分的影片ID用于排除 SetInteger ratedMovieIds recommendService.getUserRatedMovies(userId); // 步骤2获取候选影片热门新上映标签匹配 ListMovieInfo candidates movieService.getCandidateMovies(ratedMovieIds, 100); // 步骤3对每个候选影片计算预测分并行加速 return candidates.parallelStream() .map(movie - { double score tagBoostService.predictRating( userId, movie.getMovieId(), recommendService.getAllRatings(), tagBoostService.getUserTagProfile(userId, recommendService.getUserRatings(userId), movieService.getAllMovieTags()), movieService.getMovieTags(movie.getMovieId()) ); return new MovieRecommend(movie.getMovieId(), movie.getTitle(), movie.getYear(), score); }) .sorted((a, b) - Double.compare(b.getSimilarityScore(), a.getSimilarityScore())) .limit(10) .collect(Collectors.toList()); } }// src/main/java/com/movie/service/MovieService.java import org.springframework.stereotype.Service; import java.util.*; Service public class MovieService { // 获取候选影片热门评分人数多 新上映year 2022 标签匹配用户偏好标签Top3 public ListMovieInfo getCandidateMovies(SetInteger ratedMovieIds, int limit) { // 1. 热门影片user_rating 表中评分次数最多 ListMovieInfo hotMovies jdbcTemplate.query( SELECT m.* FROM movie_info m JOIN (SELECT movie_id, COUNT(*) as cnt FROM user_rating GROUP BY movie_id ORDER BY cnt DESC LIMIT ?) r ON m.movie_id r.movie_id WHERE m.movie_id NOT IN ( String.join(,, Collections.nCopies(ratedMovieIds.size(), ?)) ), (rs, rowNum) - new MovieInfo(rs.getInt(movie_id), rs.getString(title), rs.getInt(year), ...), limit, ratedMovieIds.toArray() ); // 2. 新上映影片year 2022 ListMovieInfo newMovies jdbcTemplate.query( SELECT * FROM movie_info WHERE year 2022 AND movie_id NOT IN ( String.join(,, Collections.nCopies(ratedMovieIds.size(), ?)) ) LIMIT ?, (rs, rowNum) - new MovieInfo(...), ratedMovieIds.toArray(), limit ); // 3. 标签匹配影片简化版取用户偏好标签Top3查含任一标签的影片 ListMovieInfo tagMatched new ArrayList(); // 此处省略具体SQL实际用 JOIN movie_tag WHERE tag_name IN (...) // 合并去重按热度新度标签匹配度加权排序 return Stream.concat(hotMovies.stream(), Stream.concat(newMovies.stream(), tagMatched.stream())) .distinct() .limit(limit) .collect(Collectors.toList()); } }避坑 / 常见问题 / 排查现象1推荐结果全是同一类型如全是喜剧原因getCandidateMovies()中热门影片占比过高且未对hotMovies做多样性打散如按导演/演员去重。解决在hotMovies查询后加.stream().collect(Collectors.groupingBy(m - m.getDirector()))每组取1部再合并。现象2新用户首次登录推荐为空原因getCandidateMovies()的ratedMovieIds为空集合导致NOT IN ()SQL 语法错误MySQL 不允许空 IN。解决在 SQL 拼接前判断ratedMovieIds.isEmpty()若为空则去掉NOT IN条件。现象3Tomcat 启动后访问/recommend报 404原因Spring Boot 默认禁用 JSP内嵌 Tomcat 不支持 JSP或webapp/WEB-INF/web.xml中 servlet-mapping 未配/api/*。解决放弃内嵌 Tomcat用mvn clean package打 WAR 包部署到独立 Tomcat 9JSP 支持完善并在web.xml中确认servlet-mapping servlet-namedispatcher/servlet-name url-pattern/api/*/url-pattern /servlet-mapping现象4预测分计算耗时超 2s原因getAllRatings()每次都查全表百万级记录未加缓存。解决用Cacheable(ratings)注解缓存查询结果TTL 设 5 分钟用户评分不会秒级变化或改用 Redis 存储MapuserId, ListUserRating结构。现象5JSP 页面显示EL表达式未解析如${movie.title}显示为原文原因web.xml中web-app版本过低如 2.3不支持 EL 表达式。解决升级web.xml头部为web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.04. 工程落地IDEA 配 Tomcat WAR 包部署 MySQL 连接池配置实战4.1 IDEA 配置 Tomcat选对版本、配对路径、避开“源服务器未找到资源”陷阱别用 IDEA 自带的“Run on Server”快捷按钮——它常因路径映射错导致HTTP Status 404 – The requested resource does not exist。手动配才是稳的下载 Tomcat 9.0.83非 10JSP 依赖 Servlet 4.0Tomcat 10 默认用 Jakarta EE 9包名从javax.*改jakarta.*你的 JSP 会报javax.servlet.ServletException。IDEA 中配置 Tomcat ServerFile → Settings → Build → Deployment → Tomcat Server → Configuration → Application server指向你解压的apache-tomcat-9.0.83文件夹Deployment → → Artifact → your-project:war explodedApplication context改为/movie别留空否则访问localhost:8080/会 404关键路径检查webapp/目录必须存在且index.jsp在其下webapp/WEB-INF/web.xml必须存在即使内容最简也要有web-app根节点src/main/webapp/WEB-INF/lib/下要有jstl-1.2.jar和standard-1.1.2.jarJSP 标签库验证步骤启动 Tomcat 后浏览器打开http://localhost:8080/movie/应看到index.jsp若 404立刻检查Application context是否为/movie以及webapp/是否在项目根目录下不是src/main/webapp。4.2 打 WAR 包部署到 Linux绕过java.lang.NoClassDefFoundError: javax/servlet/jsp/JspFactory本地跑通 ≠ 服务器跑通。Linux 上常见NoClassDefFoundError本质是 Tomcat 的lib/缺 JSP 运行时# 步骤1Maven 打包跳过测试确保 war 包纯净 mvn clean package -Dmaven.test.skiptrue # 步骤2将 target/your-project.war 复制到 Linux Tomcat webapps 目录 scp target/your-project.war userserver:/opt/tomcat/webapps/ # 步骤3检查 Tomcat lib 是否有 jsp-api.jarTomcat 9 自带但有时被删 ls /opt/tomcat/lib/ | grep jsp # 应输出jsp-api.jar jsp-api.jar.md5 # 步骤4若缺失手动下载 jsp-api.jarTomcat 9.0.83 对应版本放入 lib/ # 注意不要放 jstl.jar 到 lib/它必须在 WAR 包的 WEB-INF/lib/ 下否则 JSP 标签不生效 # 步骤5启动 Tomcat 并查看日志 /opt/tomcat/bin/startup.sh tail -f /opt/tomcat/logs/catalina.out # 成功标志INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/opt/tomcat/webapps/your-project.war]参数说明-Dmaven.test.skiptrue避免单元测试失败导致打包中断scp传输后Tomcat 会自动解压your-project.war为your-project/目录catalina.out日志里出现Deploying web application archive表示 WAR 解压成功若卡在Starting ProtocolHandler [http-nio-8080]后无后续大概率是web.xml格式错误或servlet-class找不到。4.3 MySQL 连接池配置HikariCP 替代老旧 C3P0防Cant connect to local MySQL serverapplication.properties中的数据库配置决定系统能否扛住并发# 数据源配置HikariCP spring.datasource.urljdbc:mysql://localhost:3306/movie_recommend?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseSSLfalse spring.datasource.usernameroot spring.datasource.passwordyour_password spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver # HikariCP 连接池参数实测最优值 spring.datasource.hikari.maximum-pool-size20 spring.datasource.hikari.minimum-idle5 spring.datasource.hikari.connection-timeout30000 spring.datasource.hikari.idle-timeout600000 spring.datasource.hikari.max-lifetime1800000 spring.datasource.hikari.validation-timeout3000 spring.datasource.hikari.leak-detection-threshold60000 # JPA/Hibernate 配置 spring.jpa.hibernate.ddl-autonone spring.jpa.show-sqlfalse spring.jpa.properties.hibernate.format_sqltrue避坑 / 常见问题 / 排查现象1启动时报java.sql.SQLException: Access denied for user rootlocalhost原因MySQL 8.0 默认认证插件是caching_sha2_password旧驱动不兼容。解决MySQL 命令行执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY your_password; FLUSH PRIVILEGES;现象2高并发时大量Connection is not available, request timed out after 30000ms原因maximum-pool-size20不够或数据库最大连接数被限制。解决查 MySQL 当前设置SHOW VARIABLES LIKE max_connections;默认151执行SET GLOBAL max_connections 300;同时将 Hikarimaximum-pool-size提至 50。现象3Linux 上连 MySQL 报Cant connect to local MySQL server through socket /tmp/mysql.sock原因MySQL 服务未启动或 socket 路径不对Ubuntu 默认/var/run/mysqld/mysqld.sock。解决sudo systemctl status mysql确认服务状态若 socket 路径不同在url中显式指定jdbc:mysql://localhost:3306/...?socket/var/run/mysqld/mysqld.sock。现象4Tomcat 日志刷屏WARN com.zaxxer.hikari.pool.ProxyConnection - Connection marked as broken原因MySQLwait_timeout默认28800秒8小时小于连接池max-lifetime连接被 MySQL 主动断开。解决设spring.datasource.hikari.max-lifetime180000030分钟 wait_timeout或 MySQL 中执行SET GLOBAL wait_timeout 3600;。现象5推荐接口响应慢show processlist发现大量Sleep状态连接原因连接用完未 close连接池泄漏。解决在 DAO 层所有JdbcTemplate查询后加Transactional或手动try-with-resources管理 Connection。本文还有配套的精品资源点击获取