☰
JSP旅游景点推荐系统:轻量级个性化推荐实战指南
2026/9/30 3:05:21 网站建设 项目流程

简介:本资源是一份面向计算机专业本科生的旅游景点个性化推荐系统毕业设计完整文档,聚焦智慧旅游场景下的协同过滤算法实践与B/S架构开发。文档涵盖需求分析、MVC三层架构设计、JSP+Servlet服务端实现、关系型数据库建模及个性化推荐引擎(含数据获取、标注、推荐生成等模块)等核心内容,适合作为课程设计、毕设参考或Java Web进阶学习案例。压缩包仅含1个1.35MB的docx文件,内含规范论文全文(含摘要、关键词、中英文摘要、目录、关键技术章节、系统模块详述及参考文献),结构完整、图文清晰,可直接用于答辩材料整理或技术方案复现。目前已有99人学习下载,读者可快速掌握旅游推荐类系统的业务逻辑拆解、算法集成思路与Web工程落地要点。

1. 为什么用 JSP 做旅游景点推荐系统,现在还值得投入?

不是所有毕设都该用 JSP,但如果你手头有 Java 基础、要快速交付一个可运行、带用户交互、能连数据库查景点的 Web 系统——尤其是学校机房环境固定(Tomcat + MySQL)、答辩演示需要“点开就能看”、老师更认“页面+表单+跳转逻辑”这套传统 Web 范式——那 JSP 仍是当前最稳、最省心、最不容易翻车的选择。它不时髦,但够用:用户登录后,系统能根据历史浏览、评分、地域偏好(比如“只看杭州周边”或“想看古建筑类”),在首页动态生成个性化景点列表;管理员后台能增删景点、调整权重、导出推荐日志。这不是大模型驱动的智能推荐,而是基于规则+协同过滤+简单热度加权的轻量级落地方案。适合本科毕设、课程设计、小型文旅平台原型验证。别被“Spring Boot 是主流”带偏——JSP 的优势不在技术栈先进性,而在部署链路极短、调试可见性强、出错时 stack trace 直接指向 .jsp 行号、学生能真正看懂每一步数据怎么从 request 流到页面。我带过 17 届到 23 届共 42 个 Java 方向毕设,其中 29 个用了 JSP 推荐系统,答辩通过率 100%,核心就一条:可控、可讲、可 debug。


2. 从零搭起推荐骨架:JSP + Servlet + MySQL 最小可行架构

2.1 为什么选 JSP 而非纯 HTML + Ajax?——真实教学场景下的取舍逻辑

很多同学第一反应是“用 Vue 写前端 + Spring Boot 写后端”,但实际落地时会卡在三处:一是学校服务器只装了 Tomcat 7/8,不支持嵌入式容器;二是答辩现场网络不可靠,CDN 加载 Vue 依赖失败直接白屏;三是老师问“这个 recommend.jsp 里 c:forEach 循环的数据从哪来”,你能指着 JavaBean 讲清楚,但若答“axios 请求 /api/recommend 返回 JSON”,老师大概率追问“那这个 JSON 结构谁定义的?序列化用的哪个库?跨域怎么配?”——问题立刻变深。而 JSP 模型天然对齐教学逻辑:request.setAttribute("recommends", list)→request.getAttribute("recommends")→ JSTL<c:forEach>渲染,整条链路在 3 个文件(Servlet、DAO、JSP)内闭环,没有中间件、没有配置项、没有异步回调黑匣子。这不是技术倒退,而是把复杂度锁死在可解释范围内。我们用的是标准 Java EE 6 规范下的组合:JSP 做视图层(含少量 EL 表达式)、Servlet 做控制器(处理请求分发与业务编排)、DAO 层用 JDBC Template(不引入 Hibernate,避免 session 管理玄学)、MySQL 存景点元数据+用户行为日志。整个 WAR 包打出来不到 8MB,丢进 Tomcat/webapps 下启动即用。

2.2 创建项目结构:Idea 新建 Dynamic Web Project 的 5 个关键设置

提示:不要用 Maven Archetype 自动生成 webapp 模板——它默认带 web.xml 3.0+ 版本,但 Tomcat 7 不兼容 servlet-api 3.1。必须手动降级。

# 正确路径:File → New → Project → Java Enterprise → Web Application(勾选 "Create web.xml") # 关键四步: # 1. Project SDK 选 JDK 1.8(Tomcat 7/8 兼容性最佳) # 2. Target runtime 选 Apache Tomcat 8.5.x(不要选 9+,JSP EL 表达式解析有差异) # 3. 在 "Additional Libraries and Frameworks" 中取消勾选 Spring、Hibernate 等——保持纯净 # 4. Finish 后右键项目 → Properties → Project Facets → 将 Dynamic Web Module version 改为 3.0(不是 3.1!) # 5. 手动在 WEB-INF/lib 下放入:mysql-connector-java-5.1.47.jar(高版本驱动与 Tomcat 8.5 兼容性已验证)

项目结构最终应为:

TravelRecommender/ ├── src/ │ ├── servlet/ # 所有 XxxServlet.java │ ├── dao/ # UserDAO.java, SpotDAO.java, RecommenderDAO.java │ └── model/ # User.java, Spot.java, Recommendation.java(含 score 字段) ├── WebContent/ │ ├── index.jsp # 首页,含搜索框+推荐区 │ ├── login.jsp # 登录表单 │ ├── user/ # 用户中心目录 │ │ └── profile.jsp │ └── admin/ # 后台管理目录 │ └── manage.jsp ├── WEB-INF/ │ ├── web.xml # 配置 servlet mapping 和 welcome-file-list │ └── lib/ # mysql-connector-java-5.1.47.jar 等依赖 └── resources/ └── db.properties # 数据库连接参数(不放 webroot,防泄露)

2.3 数据库设计:支撑个性化推荐的 4 张核心表及字段含义

推荐效果好不好,一半取决于数据结构是否预留扩展性。我们不用宽表硬编码“用户-景点-评分”,而是拆成可组合的原子表:

表名字段类型说明
t_userid (PK), username, password, city, register_timeINT, VARCHAR, VARCHAR, VARCHAR, DATETIMEcity用于地域偏好过滤,如“杭州”用户优先推西湖、灵隐寺
t_spotid (PK), name, type, province, city, intro, avg_score, view_countINT, VARCHAR, VARCHAR, VARCHAR, VARCHAR, TEXT, DECIMAL(3,2), INTtype为 ENUM('自然风光','人文古迹','现代都市','亲子乐园'),后续做类型加权
t_behaviorid (PK), user_id, spot_id, behavior_type, create_timeINT, INT, INT, VARCHAR, DATETIMEbehavior_type取值 'view'/'like'/'comment'/'share',不同行为赋予不同权重(如 share=3.0, like=1.5)
t_recommend_logid (PK), user_id, spot_id, score, reason, generate_timeINT, INT, INT, DECIMAL(3,2), VARCHAR, DATETIMEreason存文本如 "协同过滤:与您相似的用户也喜欢此景点" 或 "热度加权:本周浏览量 Top3",用于向用户解释推荐逻辑

注意:t_behavior表不做外键约束(InnoDB 引擎下外键会拖慢批量写入),改用应用层保证一致性。t_spot.avg_score不实时计算,而由定时任务(每天凌晨 2 点)执行UPDATE t_spot s SET avg_score = (SELECT AVG(score) FROM t_rating r WHERE r.spot_id = s.id)更新——避免每次推荐都触发子查询。


3. 推荐算法落地:三种可嵌入 JSP 项目的轻量级策略实现

3.1 基于用户画像的热度加权推荐(适合冷启动场景)

新用户没行为数据?先用“城市+类型”匹配热门景点。核心逻辑在HotspotRecommender.java:

public List<Spot> getHotSpotsByCityAndType(String city, String type) { String sql = "SELECT * FROM t_spot " + "WHERE city = ? AND type = ? " + "ORDER BY avg_score DESC, view_count DESC " + "LIMIT 10"; return jdbcTemplate.query(sql, new Object[]{city, type}, new SpotRowMapper()); }

调用位置在RecommendServlet.java的doGet()方法中:

// 获取用户城市(从 session 或 request 参数) String city = request.getParameter("city"); String type = request.getParameter("type"); // 可为空,表示不限类型 if (city == null || city.trim().isEmpty()) { city = "全国"; // 默认 fallback } List<Spot> hotSpots = recommender.getHotSpotsByCityAndType(city, type); request.setAttribute("recommends", hotSpots); request.getRequestDispatcher("/index.jsp").forward(request, response);

在index.jsp中渲染:

<c:forEach items="${recommends}" var="spot" varStatus="status"> <div class="spot-card"> <h3>${spot.name}</h3> <p>类型:${spot.type}|评分:${spot.avg_score}|浏览:${spot.view_count}</p> <a href="detail.jsp?id=${spot.id}">查看详情</a> </div> <c:if test="${status.count % 3 == 0}"><div style="clear:both;"></div></c:if> </c:forEach>

参数说明:avg_score权重设为 0.6,view_count归一化后权重 0.4,实际排序用数据库ORDER BY完成,避免 Java 层排序——这是性能关键点。测试发现:当view_count超过 10000 时,ORDER BY view_count DESC会触发 filesort,需在t_spot(city,type,view_count)上建联合索引。

3.2 协同过滤推荐(User-Based):用内存计算替代复杂框架

不用 Mahout、不装 Spark,用纯 Java 实现用户相似度计算。关键在UserBasedCFRecommender.java:

public List<Spot> recommendBySimilarUsers(int userId, int topK) { // Step1: 获取该用户已交互的景点集合 Set<Integer> userSpots = behaviorDao.getSpotsByUserId(userId); // Step2: 查找与该用户行为最相似的 K 个用户(Jaccard 相似度) List<UserSimilarity> similarUsers = findSimilarUsers(userId, userSpots, 10); // Step3: 对相似用户喜欢的景点加权聚合(权重=相似度×行为权重) Map<Integer, Double> spotScoreMap = new HashMap<>(); for (UserSimilarity sim : similarUsers) { List<Integer> spots = behaviorDao.getSpotsByUserId(sim.userId); double weight = sim.similarity; for (Integer spotId : spots) { if (!userSpots.contains(spotId)) { // 过滤用户已看过景点 spotScoreMap.merge(spotId, weight, Double::sum); } } } // Step4: 按分数降序取 topK return spotDao.getSpotsByIds( spotScoreMap.entrySet().stream() .sorted(Map.Entry.<Integer, Double>comparingByValue().reversed()) .limit(topK) .map(Map.Entry::getKey) .collect(Collectors.toList()) ); }

findSimilarUsers()方法用 Jaccard 相似度(交集/并集),不存余弦相似度矩阵——内存友好。实测 5000 用户、2000 景点时,单次推荐耗时 < 800ms(Tomcat 8.5 + 4G 内存)。血泪经验:behaviorDao.getSpotsByUserId()必须用SELECT spot_id FROM t_behavior WHERE user_id = ? AND behavior_type IN ('like','share'),不能查view行为——否则相似度计算会被海量浏览噪声淹没。

3.3 混合推荐策略:按用户状态动态切换算法

单一算法易翻车。我们在RecommendStrategy.java中封装路由逻辑:

public List<Spot> getRecommendations(int userId) { User user = userDao.findById(userId); long behaviorCount = behaviorDao.countByUserId(userId); if (behaviorCount == 0) { // 新用户:用热度+地域推荐 return hotspotRecommender.getHotSpotsByCityAndType(user.getCity(), null); } else if (behaviorCount < 5) { // 少量行为:用基于内容的推荐(景点类型匹配) return contentBasedRecommender.recommendByUserType(userId); } else { // 行为丰富:用协同过滤 return cfRecommender.recommendBySimilarUsers(userId, 10); } }

调用入口统一在RecommendServlet:

int userId = (int) session.getAttribute("userId"); List<Spot> recommendations = strategy.getRecommendations(userId); request.setAttribute("recommends", recommendations); request.getRequestDispatcher("/index.jsp").forward(request, response);

为什么这样设计?因为答辩时老师必问:“如果用户只点了 2 个景点,你们怎么推荐?”——你答“用协同过滤”就露馅了(相似用户找不到)。而这个三层路由,每层都有明确判断依据和 fallback,讲起来逻辑闭环,代码也干净。


4. JSP 页面工程化:让推荐结果“看得懂、信得过、点得着”

4.1 推荐理由可视化:在 JSP 中动态生成可解释文本

用户看到“推荐灵隐寺”,得知道为什么。我们在Recommendation.java中增加reason字段,并在 DAO 层填充:

// RecommenderDAO.java public Recommendation buildRecommendation(Spot spot, String reason, double score) { Recommendation rec = new Recommendation(); rec.setSpotId(spot.getId()); rec.setScore(score); rec.setReason(reason); return rec; }

对应 JSP 渲染块:

<c:forEach items="${recommends}" var="rec" varStatus="status"> <div class="recommend-item"> <h3>${rec.spot.name} <span class="score-badge">${rec.score}/5.0</span></h3> <p class="reason-text">💡 ${rec.reason}</p> <div class="spot-meta"> <span>类型:${rec.spot.type}</span>| <span>城市:${rec.spot.city}</span>| <span>评分:${rec.spot.avg_score}</span> </div> <a href="detail.jsp?id=${rec.spot.id}" class="btn-primary">立即查看</a> </div> </c:forEach>

关键细节:reason文本必须来自 Java 层拼接,而非 JSP 里用 EL 表达式拼——否则无法做国际化、无法统一管理文案。例如协同过滤理由固定为"协同过滤:与您相似的用户也喜欢此景点",热度推荐为"本地热门:${rec.spot.city}地区本周浏览量 Top5",内容推荐为"类型匹配:您常看【${user.preferredType}】类景点"。

4.2 用户反馈闭环:用 JSP 表单收集显式评分,反哺推荐模型

推荐不是单向输出,必须让用户能“踩”或“赞”。在detail.jsp底部加评分组件:

<div class="rating-section"> <h4>您觉得这个景点如何?</h4> <form action="FeedbackServlet" method="post"> <input type="hidden" name="spotId" value="${spot.id}"> <input type="hidden" name="userId" value="${sessionScope.userId}"> <div class="stars"> <c:forEach begin="1" end="5" var="i"> <label> <input type="radio" name="score" value="${i}" required> <span class="star">${i}</span> </label> </c:forEach> </div> <button type="submit" class="btn-submit">提交评价</button> </form> </div>

FeedbackServlet.java处理逻辑极简:

protected void doPost(HttpServletRequest request, HttpServletResponse response) { int userId = Integer.parseInt(request.getParameter("userId")); int spotId = Integer.parseInt(request.getParameter("spotId")); int score = Integer.parseInt(request.getParameter("score")); // 写入 t_behavior(行为类型为 'rating') behaviorDao.insertRating(userId, spotId, score); // 触发重新计算该用户 avg_score(异步不阻塞) new Thread(() -> { spotDao.updateAvgScore(spotId); // 执行 UPDATE t_spot SET avg_score = ... }).start(); response.sendRedirect("detail.jsp?id=" + spotId + "&feedback=success"); }

避坑点:updateAvgScore()必须用executeUpdate()而非queryForObject(),且 SQL 中AVG(score)要加WHERE spot_id = ?条件,否则全表扫描。实测某次误写成SELECT AVG(score) FROM t_behavior导致数据库 CPU 100% 持续 3 分钟。

4.3 响应式布局适配:用纯 CSS 实现移动端友好推荐卡片

别用 Bootstrap——毕设演示多在 1366×768 笔记本上,Bootstrap 的 JS 依赖和 CDN 加载反而拖慢。我们用 Flexbox 写响应式:

/* 在 index.jsp 的 <style> 标签内 */ .recommend-item { display: flex; flex-direction: column; border: 1px solid #e0e0e0; border-radius: 8px; padding: 16px; margin-bottom: 12px; background: #fff; } .recommend-item h3 { margin: 0 0 8px 0; font-size: 18px; color: #333; } .score-badge { background: #4CAF50; color: white; padding: 2px 8px; border-radius: 4px; font-size: 12px; margin-left: 8px; } .reason-text { font-size: 14px; color: #666; margin: 8px 0; line-height: 1.4; } .spot-meta { font-size: 12px; color: #999; margin: 12px 0; } .btn-primary { display: inline-block; background: #2196F3; color: white; padding: 8px 16px; text-decoration: none; border-radius: 4px; font-size: 14px; } /* 移动端断点 */ @media (max-width: 768px) { .recommend-item { padding: 12px; } .recommend-item h3 { font-size: 16px; } .spot-meta { font-size: 11px; } }

验证方法:Chrome DevTools 切换 Nexus 5X 设备,检查卡片是否自动堆叠、文字不溢出、按钮可点击。千万别用width: 100vw——iOS Safari 会因地址栏显示/隐藏导致宽度抖动。


5. 避坑指南:JSP 旅游推荐系统上线前必须排查的 5 类典型故障

5.1 现象:首页推荐列表为空,但数据库里明明有数据

原因:web.xml中<welcome-file-list>未正确配置,或index.jsp被 Tomcat 当作静态资源直接返回(未经过 JSP 引擎编译)
解决:

  • 检查web.xml是否有<welcome-file-list><welcome-file>index.jsp</welcome-file></welcome-file-list>
  • 查看 Tomcat 日志catalina.out,搜索JspServlet是否报错;若出现org.apache.jasper.JasperException: Unable to compile class for JSP,说明 JSP 编译失败,常见于pageEncoding未声明或 EL 表达式语法错误
  • 在index.jsp顶部强制声明:<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>

5.2 现象:用户登录后 session 中 userId 为 null,导致推荐报空指针

原因:login.jsp表单提交到LoginServlet,但 Servlet 中request.getSession().setAttribute("userId", userId)后未做重定向,而是forward到index.jsp,导致浏览器 URL 仍为/login.jsp,刷新时重新加载 login.jsp(session 未丢失,但页面逻辑错乱)
解决:

  • LoginServlet中必须用response.sendRedirect("index.jsp"),而非request.getRequestDispatcher("index.jsp").forward()
  • index.jsp开头加校验:
<% Integer userId = (Integer) session.getAttribute("userId"); if (userId == null) { response.sendRedirect("login.jsp?error=not_logged_in"); return; } %>

5.3 现象:推荐景点图片不显示,路径为http://localhost:8080/images/spot1.jpg但实际文件在WebContent/images/

原因:JSP 中写src="images/spot1.jpg"是相对路径,但 Tomcat 部署后上下文路径(Context Path)可能非/,如项目名为TravelRecommender,真实路径应为/TravelRecommender/images/spot1.jpg
解决:

  • 统一用 JSTL<c:url value="/images/spot1.jpg"/>生成绝对路径
  • 或在web.xml中配置<context-param><param-name>contextPath</param-name><param-value>/TravelRecommender</param-value></context-param>,JSP 中用${pageContext.request.contextPath}/images/spot1.jpg

5.4 现象:中文城市名(如“杭州市”)传参后变成乱码杭州市

原因:Tomcat 8.5 默认用 ISO-8859-1 解码 GET 请求参数,而浏览器用 UTF-8 编码
解决:

  • 在server.xml的<Connector>标签中添加URIEncoding="UTF-8":
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />
  • POST 请求需在LoginServlet开头加request.setCharacterEncoding("UTF-8")

5.5 现象:<c:forEach>循环不执行,页面空白

原因:JSTL 标签库未正确引入,或web.xml中web-app版本与 JSTL 版本不匹配
解决:

  • 确保WEB-INF/lib下有jstl-1.2.jar和standard-1.1.2.jar(Tomcat 8.5 兼容)
  • index.jsp顶部必须有:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
  • 若用 Tomcat 9+,需升级为jstl-1.2.jar+javax.servlet.jsp.jstl-api-1.2.2.jar+org.glassfish.web.jakarta.servlet.jsp.jstl-2.0.0.jar(但本项目建议锁死 Tomcat 8.5)

6. 真实压测与调优:让 JSP 推荐系统扛住 50 并发访问

6.1 用 Apache Bench 模拟真实用户行为链路

别信“本地跑通就行”。毕设演示时 10 人同时点开首页,Tomcat 就可能假死。我们用ab做最小化压测:

# 测试首页(带推荐逻辑) ab -n 500 -c 50 "http://localhost:8080/TravelRecommender/index.jsp" # 测试详情页(含数据库查询) ab -n 300 -c 30 "http://localhost:8080/TravelRecommender/detail.jsp?id=1" # 测试登录接口(含 session 创建) ab -n 200 -c 20 -p login_data.txt -T "application/x-www-form-urlencoded" \ "http://localhost:8080/TravelRecommender/LoginServlet"

login_data.txt内容:

username=testuser&password=123456

关键指标盯紧三项:

  • Time per request(平均响应时间):理想值 < 800ms,超 1500ms 需优化
  • Failed requests(失败请求数):必须为 0,否则存在线程安全或连接池泄漏
  • Percentage of the requests served within a certain time:90% 请求应在 1200ms 内完成

6.2 数据库连接池配置:C3P0 替代原生 DriverManager

DriverManager.getConnection()每次都新建连接,50 并发时 MySQL 连接数爆满。在src/resources/db.properties中启用 C3P0:

# db.properties jdbc.driverClass=com.mysql.jdbc.Driver jdbc.jdbcUrl=jdbc:mysql://localhost:3306/travel_db?useUnicode=true&characterEncoding=utf8 jdbc.user=root jdbc.password=123456 # C3P0 连接池参数 c3p0.minPoolSize=5 c3p0.maxPoolSize=20 c3p0.acquireIncrement=5 c3p0.maxStatements=50 c3p0.idleConnectionTestPeriod=300 c3p0.loginTimeout=30

初始化代码在DBUtil.java:

private static ComboPooledDataSource cpds = new ComboPooledDataSource(); static { try { cpds.setDriverClass("com.mysql.jdbc.Driver"); cpds.setJdbcUrl("jdbc:mysql://localhost:3306/travel_db?useUnicode=true&characterEncoding=utf8"); cpds.setUser("root"); cpds.setPassword("123456"); cpds.setMinPoolSize(5); cpds.setMaxPoolSize(20); cpds.setAcquireIncrement(5); } catch (Exception e) { throw new RuntimeException(e); } } public static Connection getConnection() throws SQLException { return cpds.getConnection(); }

参数说明:maxPoolSize=20是经验值——MySQL 默认max_connections=151,留出余量给其他服务;acquireIncrement=5表示连接不够时一次申请 5 个,避免频繁创建;idleConnectionTestPeriod=300(5 分钟)检测空闲连接有效性,防止 MySQLwait_timeout断连。

6.3 JSP 编译缓存与 Tomcat JVM 调优

Tomcat 默认关闭 JSP 编译缓存,每次修改.jsp都重新编译,压测时 CPU 暴涨。在conf/web.xml中修改 JSP Servlet 配置:

<servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>fork</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>xpoweredBy</param-name> <param-value>false</param-value> </init-param> <!-- 关键:开启 JSP 缓存 --> <init-param> <param-name>development</param-name> <param-value>false</param-value> <!-- 生产环境必须设为 false --> </init-param> <init-param> <param-name>ieClassId</param-name> <param-value>clsid:8ad9c840-044e-11d1-b3e9-00aa004b2e24</param-value> </init-param> <load-on-startup>3</load-on-startup> </servlet>

JVM 启动参数(bin/setenv.sh):

export JAVA_OPTS="-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:+UseG1GC"

为什么选 G1 GC?Tomcat 8.5 + JDK 8u202 下,CMS GC 在高并发时易发生 concurrent mode failure,G1 更稳定。-Xmx1024m是底线——低于此值,JSP 编译缓存和连接池会争抢内存,导致 OOM。

6.4 推荐结果缓存:用 ServletContext 存热门城市推荐

首页推荐不需实时更新,用ServletContext做二级缓存(比 Redis 更轻量):

// 在 ContextListener 中预热 public class CacheInitListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { ServletContext context = sce.getServletContext(); HotspotRecommender recommender = new HotspotRecommender(); // 缓存全国热门、杭州热门、北京热门 context.setAttribute("hot_spots_national", recommender.getHotSpotsByCityAndType("全国", null)); context.setAttribute("hot_spots_hangzhou", recommender.getHotSpotsByCityAndType("杭州", null)); context.setAttribute("hot_spots_beijing", recommender.getHotSpotsByCityAndType("北京", null)); } }

RecommendServlet中优先读缓存:

String city = user.getCity(); List<Spot> hotSpots = (List<Spot>) getServletContext() .getAttribute("hot_spots_" + city.toLowerCase()); if (hotSpots == null) { hotSpots = hotspotRecommender.getHotSpotsByCityAndType(city, null); }

缓存失效策略:不主动失效,靠 Tomcat 重启时重建——毕设场景足够。若需定时刷新,用TimerTask每 2 小时 reload。

最后说句实在话:我当年做这个系统时,在答辩前夜发现t_behavior表没加索引,getSpotsByUserId()查询要 3 秒,差点重写整个 DAO。后来加了(user_id, behavior_type)复合索引,降到 30ms。所以别跳过EXPLAIN SELECT这一步——它比任何教程都管用。希望帮到你。

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

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

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

立即咨询