ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot + MyBatis Plus 从零复刻东营特色旅游网站全流程解析

Spring Boot + MyBatis Plus 从零复刻东营特色旅游网站全流程解析 从零复刻一个东营特色景点旅游网站还要把整个过程写成一篇能过审的论文——这是很多Java方向同学课程设计和毕业设计的真实处境。我当时选这个题目核心原因就一个东营的旅游资源足够独特黄河入海口、湿地候鸟、石油工业遗存、孙子文化园这些元素随便挑几个出来都比通用旅游网站有辨识度论文里做需求分析、写系统设计时天然有内容可写。而技术侧就是标准的Java Web开发路线Spring Boot做后端、MyBatis Plus操作数据库、前端用模板引擎渲染难度适中却能把Java基础、框架使用、数据库设计、论文写作全链路走一遍。这篇文章从选题逻辑聊起把数据库设计、核心功能实现、论文撰写的完整思路以及我实际踩过的坑全部摊开讲打算做同类Java项目或者正在为毕业设计选题发愁的同学可以直接参考着往下推。1. 选题复盘为什么是东营景点 Java旅游网站1.1 东营旅游资源的独特性与信息化痛点先说选题。很多人一听旅游网站就觉得是烂大街的CRUD演示项目但如果把范围限定到东营特色景点整个项目的立意就不一样了。东营最核心的旅游名片是黄河入海口——河海交汇的黄色分界线、秋季的红海滩、迁徙季节的候鸟都是全国独一份的生态景观。除了自然生态还有两个容易被忽视的方向一个是胜利油田衍生出来的工业旅游和石油文化资源另一个是广饶的孙子文化旅游区以兵圣文化和古典军事体验为主线。这三个方向各有明确的目标受众做门户展示、做路线串联、做文化信息整合都有实实在在的内容支撑。但我在前期调研时发现一个很矛盾的问题东营景点的线上信息极其分散。官方旅游网站的信息更新慢景点介绍停留在千篇一律的百科词条式文本社交平台上又只有零散的游客攻略缺少一个能把景区介绍、开放时间、票价、路线、游客评价串起来的统一入口。对做课程设计和毕设来说这个痛点恰好是选题的金矿既有真实的需求背景又不会因为需求过大导致做不完。论文开头的研究背景和研究意义部分把这些现状梳理清楚就是一段思路非常完整的导入。1.2 这个选题在课程设计/毕设中的角色定位从技术难度来评估这个项目非常适合作Java方向的课程设计或本科毕设。它不冷门参考资料多得是也不过分简单核心模块覆盖了Java Web开发的几个关键层次数据表设计、ORM框架使用、业务逻辑分层、前后端数据交互、权限控制。对学生而言这套流程走完Java基础、Spring Boot、MyBatis Plus、SQL这些硬技能基本都能串起来。更重要的是这个选题有充足的论文扩展点。比如景点检索可以引入排序算法和搜索策略路线推荐可以做成基于热门度和地理位置组合的简单推荐系统游客行为数据可以做简单的统计分析。这些点不用真做得多复杂只要在论文里体现出你在思考、有设计依据就已经比大部分模板化毕设高出不少了。我当时就是靠结合热门度加权排序改进景点检索体验作为创新点把系统设计章节做厚了一些答辩时老师明显更愿意往这个方向追问。1.3 核心功能边界的划定做这类系统最怕的一件事就是需求失控。旅游网站能搞的功能太多了在线购票、酒店预订、拼团、支付、地图导航、虚拟导游……如果全往系统里塞代码量先不说论文里的需求分析就能把篇幅撑爆但每个模块都浅尝辄止答辩反而容易被问住。我最终划定的功能边界是六块景点展示与分类检索、景点详情页、用户注册登录、景点评论、景点收藏、旅游路线推荐。再加一个后台管理用于维护景点数据和审核评论。这套功能集合非常标准CRUD有了关联查询有了权限控制有了一个接近真实落地的业务闭环也有了。而且实现难度都在可控范围内不会出现卡在某个技术点上一个月推进不了的窘境。给正在选型的同学一个建议如果你的毕设周期只有三个月功能模块最好控制在5到8个之间。核心功能做扎实比堆砌十几个残废模块对你更有利。2. 技术底座搭建Spring Boot MyBatis Plus的选型与踩坑2.1 框架选型逻辑技术选型就一句话稳定、资料多、上手快。我当时定的是 Spring Boot 2.7.x MyBatis Plus 3.5.x MySQL 8.0前端用 Thymeleaf 模板引擎加 Bootstrap 做样式。这套组合在Java课程设计和毕设里几乎是最不出错的选择。后端框架没选Spring Cloud那套微服务原因很简单单机部署的单体项目用微服务架构是自找麻烦服务拆分、注册中心、配置中心全加上光环境配置就能消耗掉大量时间而且答辩时老师很可能会问你这里为什么要拆这么细答不上来反而扣分。Spring Boot单体应用足够优雅自动化配置省心内嵌Tomcat让部署也变得非常简单。持久层选MyBatis Plus而不是原生MyBatis是因为通用Mapper和条件构造器真的能省掉大量重复的CRUD代码——对一个以业务逻辑为主的项目来说把时间花在景点检索、评论互动这些地方比手撸每一句SQL更有价值。前端为什么不用前后端分离主要是考虑到项目体量。旅游门户站点以服务端渲染为主Thymeleaf直接渲染页面天然对搜索引擎友好也省去了搭建前后端分离工程需要处理的跨域、接口文档、构建部署等一系列问题。Bootstrap负责快速把页面样式撑起来写几个列表页、详情页完全够用。2.2 环境准备JDK多版本共存与配置陷阱Java开发的第一步是环境而环境问题往往是最先卡住人的地方。我记得不少同学在java环境配置上载过跟头尤其是机器上已经装了多个JDK版本的情况。我的环境用的是 JDK 8。选择 JDK 8 而不是 JDK 17主要考虑是Spring Boot 2.7对JDK 8的支持最成熟学校机房、老师电脑上的环境兼容性也最好网上能找到的教程参考基本都是JDK 8为主的。如果你要用JDK 17建议直接配Spring Boot 3.x两者是配套的强搭在一起版本冲突会找得你怀疑人生。多个JDK版本共存时核心是环境变量切换。Windows机器上JAVA_HOME 指向哪个JDK命令行里的 java -version 就是哪个版本。但坑也在这里很多人改了JAVA_HOME命令行执行 java -version 结果还是旧版本这是因为 Path 环境变量里配置的路径比 JAVA_HOME 的优先级更高。我当时就是折腾了半天最后把 Path 里写死的 C:\Program Files\Java\jdk1.8.0_xxx\bin 删掉改成 %JAVA_HOME%\bin才彻底解决。还有一个容易被忽略的点IDEA 里配置的 Project SDK、Project language level、Maven 的 JDK for importer 这三处要保持一致。经常出现的情况是命令行 java -version 是8IDEA 里Project SDK却选的17编译时用旧语法倒是没报错但一旦用了高版本API就会冒出 weird error且不容易追溯到源头。2.3 工程目录结构与配置文件的坑工程结构按 Maven 的标准分层来我用的包名是 com.dongying.travel下面按 controller、service、mapper、entity、config、common 分层。这种分法本身没有技术含量但对后面写论文帮助很大——系统设计章节里画软件层次架构图直接拿包结构改一下就是一张很规范的图。配置文件用 application.yml。第一次接触这个文件的同学最容易栽在缩进和空格上YAML 对缩进敏感写错一个空格Spring Boot 启动时直接报解析错误而且报错信息往往指向内存溢出或Caused by: while scanning a directive包装得和真正的配置问题毫无关系非常劝退。我当时的建议是id 开头的前两行你都手打不要复制网页上排版乱掉的配置文件。多环境配置也值得一开始就做。我设置了 application-dev.yml 和 application-prod.yml 两个文件本地开发连本地MySQL部署到服务器时通过启动参数 --spring.profiles.activeprod 切换。这个习惯养成以后换一份答辩演示环境会从容非常非常多。2.4 数据库选型MySQL还是SQL Server数据库选型上我的答案很明确MySQL。安装简单、社区资料多、Navicat连接方便、MyBatis Plus对MySQL支持最完善。字符串需要支持中文和emoji时建库显式指定 utf8mb4 字符集就行CREATE DATABASE dongying_travel DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;但是有一个现实情况必须提很多学校机房或老师的演示环境还是老旧的 SQL Server 2008。如果毕设的部署环境被指定为SQL Server连接方式就要换成 jdbc:sqlserver://localhost:1433;DatabaseNamedongying_travel驱动依赖改用dependency groupIdcom.microsoft.sqlserver/groupId artifactIdsqlserverjdbc/artifactId version4.2/version /dependency不过SQL Server 2008的年代比较久远JDBC驱动兼容性偶尔会出幺蛾子而且它不支持比较新的分页写法MyBatis Plus的分页插件性能也会受限。我的建议是除非导师明确强制否则优先用MySQL。3. 数据库设计与实体类逆向工程3.1 核心表结构设计数据库设计是这个项目里最值得花时间的一部分。旅游网站的实体关系并不复杂核心是用户—景点之间的关联关系。我最终确定了7张核心表列一下主表的结构表名用途关键字段sys_user用户表id, username, password, nickname, rolescenic_category景点分类表id, name, sort_orderscenic景点表id, category_id, name, location, description, cover_url, ticket_price, opening_hours, hot_score, statuscomment评论表id, user_id, scenic_id, content, rating, create_time, statusfavorite收藏表id, user_id, scenic_id, create_timetravel_route旅游路线表id, title, days, scenic_ids, description, cover_urlnews旅游资讯表id, title, content, create_time景点表里我特意设计了 hot_score 这个字段。它可以由管理员在后台手动调整也可以通过一个定时任务根据浏览量、评论数、收藏数加权计算出来。这个字段的价值在于排序功能不用每条请求实时去聚合统计直接按hot_score倒序查就行对一个小型网站来说性能完全够代码也简洁。另一个用心的地方是 favorite 表。如果只做用户收藏功能一张表就够了但我额外在 favorite 上加了联合唯一约束 UNIQUE KEY uk_user_scenic(user_id, scenic_id)防止因重复点击或接口被重复调用而插入两行相同记录。这个设计在论文测试章节里是一个非常好的异常数据处理案例老师问到重复提交问题时可以直接拿它来答。3.2 MyBatis Plus实体类生成SQL的技巧关于根据Java实体类生成建表SQL这件事网上讨论非常多。实际项目里我建议分两个方向来理解一个是正向的一个是逆向的。正向先用SQL写DDL建表然后使用MyBatis Plus的代码生成器AutoGenerator反推出实体类、Mapper接口、Service、Controller。这是最稳妥的做法因为数据库字段类型、长度、索引、注释都在你手里生成出来的实体类符合预期。代码生成器的核心配置大概是# 生成器配置里指定要生成的表和包名 url jdbc:mysql://localhost:3306/dongying_travel username root password 123456 strategy: include: - scenic - sys_user - comment逆向如果你已经写好了实体类希望自动生成CREATE TABLE语句。说实话MyBatis Plus并没有一个开箱即用的官方组件做这件事但实现原理很简单就是利用JDBC的 DatabaseMetaData 或者直接反射实体类的注解拼DDL——解析 TableName 拿到表名解析 TableField 拿到列名和类型再拼上主键、长度、注释输出SQL文本。如果你的论文需要一个亮点这个自制工具类可以作为一个独立章节展示你对注解机制和反射的掌握比业务CRUD更能体现基础功底。实际开发中我强烈建议采用正向流程建表先行。实体类只是数据库的映射让数据库来主导设计表里数据多了以后你才会明白一个有注释、有索引、有统一命名规范的表结构有多重要。3.3 常见实体设计误区实体类设计有几个容易翻车的地方我一个个说。第一个是字段类型映射。Java和MySQL类型不是一一对应的尤其注意 LocalDateTime 对应 datetime 类型BigDecimal 对应 decimalText 类型在MyBatis Plus里默认映射为 String 没问题但5000字以上的大文本字段如果用 varchar(255)插入时会直接报Data too long for column。我当时在景点简介字段上就吃过这个亏后来统一改成 TEXT才解决了长文本存不进去的问题。第二个是保留字冲突。表字段如果叫 order、desc 这类SQL保留字写SQL的时候必须加反引号如order。MyBatis Plus如果配置了驼峰映射默认生成的SQL也有概率踩到。最好的规避方式就是设计表时避开保留字例如订单日期字段直接叫 create_time不要叫 order_date。第三个是逻辑删除。MyBatis Plus默认逻辑删除需要给表加一个 deleted 字段并在实体类上用 TableLogic 注解标注然后在配置里写mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0配置好之后业务层执行 deleteById 时真正跑的是 UPDATE 语句把 deleted 置为1。这样评论表、收藏表的删除操作都能保留痕迹论文里谈数据安全性与可追溯性时这是很值得写的一笔。但注意别在关联查询里忘记加deleted0的条件如果配置没有问题MyBatis Plus会自动帮你带上的。4. 核心功能实现拆解4.1 景点展示与检索排序算法与分页落地景点列表页是整个系统的门面也是实现最密集的模块之一。我做的是首页推荐热门景点、分类页按分类筛选景点、搜索页按关键字模糊匹配景点名称和简介三个入口最终都汇聚到同一条分页查询逻辑上。分页直接用MyBatis Plus的分页插件配置一个拦截器Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }然后查询时传入 Page 对象和 LambdaQueryWrapperPageScenic page new Page(current, 10); LambdaQueryWrapperScenic wrapper new LambdaQueryWrapper(); wrapper.eq(Scenic::getCategoryId, categoryId) .eq(Scenic::getStatus, 1) .orderByDesc(Scenic::getHotScore) .orderByDesc(Scenic::getCreateTime); scenicService.page(page, wrapper);这里有个细节值得展开讲讲就是排序。很多人在论文里写实现了热门景点排序实现方式却是 Java 代码里把所有景点查出来然后用 Collections.sort 或者手写冒泡排序手动排一把。这种做法在小数据集上能跑但一旦数据量上来了全量加载 手动排序的效率很差。正确的思路是把排序下推到数据库用 SQL 的 ORDER BY 完成。数据在数据库侧就排好序再按页码取10条返回性能完全不是一个量级。那 Java 的排序算法到底用不用得上其实要用的但不是用在这个地方。比如旅游路线推荐模块里我需要把某个分类下面的所有景点按 hot_score 和 rating 做综合加权再筛选出前3个作为推荐路线。这个加权计算如果全部写在 SQL 里会很别扭我就是在 Service 层查出候选景点列表后写一个基于 Comparable 接口的排序方法用 Collections.sort 或 stream().sorted() 按综合分值排序取前三名。论文里可以理直气壮地写排序算法在路线推荐中的应用代码简单逻辑清楚还能引申到 Comparator 自定义排序规则。模糊搜索也有坑。直接用 MySQL 的 LIKE %关键字%由于关键字出现在开头走不了索引数据量一大就会扫全表。对于这个毕设级别的项目来说数据量撑死几千条直接 LIKE 问题不大。但如果要答好怎么优化搜索可以从三个方向展开为 name、location 字段建联合索引搜索词做前后缀修剪用 Redis 缓存高频搜索词结果。论文里把其中任意一个方案实现并展示测试对比都是加分项。4.2 用户注册登录与权限控制用户模块我实现了注册、登录、退出、修改密码、个人中心以及管理员后台的景点数据维护和评论审核。权限控制简化为两个角色ROLE_ADMIN 和 ROLE_USER。密码存储方面有一个很多教程还在误导人的做法用 MD5 加盐。MD5 本质上是摘要算法暴力破解成本极低现在随便一个彩虹表网站就能反查弱密码。我当时用的是 Spring Security 自带的 BCryptPasswordEncoder加盐是随机的、蕴含在结果里的每次加密的结果都不一样安全性比 MD5 高一个档次。如果你不想把完整的 Spring Security 引入项目只为了用密码加密完全可以单独引入 spring-security-crypto 这个轻量级依赖只使用 BCryptPasswordEncoder 类。登录状态我这里选的是传统 Session 方案。为什么不用 JWT因为我的系统是 Thymeleaf 服务端渲染Session 天然契合服务端可以直接控制会话生命周期也不需要处理 token 过期刷新问题。JWT 的优势主要在于无状态、跨域、前后端分离放在我这套架构里反而是多余的复杂度。这个选型逻辑答辩时常被问回答好它就是展示你比较过、权衡过的证据。登录校验我用的是拦截器实现。写一个 LoginInterceptor重写 preHandle 方法从 Session 里取登录用户没有就重定向到登录页public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); Object user session.getAttribute(loginUser); if (user null) { response.sendRedirect(/login); return false; } return true; } }再在 WebMvcConfigurer 里注册拦截器把需要登录才能访问的路径收藏、评论、个人中心、后台管理通过 addPathPatterns 和 excludePathPatterns 精确控制好。这套方案代码量很少但能清晰展示 Spring MVC 的拦截器机制论文里花半页配一张拦截器处理流程图整个系统设计章节就活了。4.3 评论、收藏与路线推荐关联查询的实战训练评论模块是典型的关联查询场景评论表保存 user_id 和 scenic_id前端展示时需要把用户名和头像联出来。MyBatis Plus 的简单 CRUD 做不到自动联表我有两种写法可供选择一是先查评论列表再批量查用户信息填回去二是直接在 Mapper 里写自定义 SQL 做 join。我选了第二种因为评论列表需要同时按时间排序和按热度排序join 写法在 SQL 层直接完成排序逻辑上更好维护。收藏模块要解决的核心问题是重复收藏和取消收藏。我在 favorite 表上设计过联合唯一索引所以业务代码里用一条规则就够了用户点击收藏时先查一下记录是否存在存在则执行删除取消收藏不存在则执行插入。配合唯一索引这个操作的幂等性就有了双重保障。收藏数显示也是常见坑直接在列表里每个景点子查询一次count(favorite)N1问题就来了。我采用的方法是给 scenic 表加一个 favorite_count 字段收藏成功时 UPDATE favorite_count 1取消时 -1。虽然引入了数据一致性维护成本但对小项目来说收益远大于风险。旅游路线推荐是系统里最有想法的功能。我没有做复杂的协同过滤而是采用一套基于规则的热门组合策略用户选择一个景区分类后系统取出该分类下 hot_score 前三的景点按上午下午晚上的节奏拼成一个一日游路线如果用户选择了多个分类就按分类权重取各分类前两名组成两日游路线。这个实现非常轻量但完整模拟了推荐系统的核心流程——候选集构建、打分排序、TopN截取。论文里可以在这块画一个路线生成流程图并把热度计算公式展开成表格技术含量足够支撑一个章节的篇幅。事件收藏数扣减时别忘了先判断当前值是否大于0防止并发场景下 favorite_count 被减成负数。加一个 SQL 层的约束UPDATE scenic SET favorite_count favorite_count - 1 WHERE id ? AND favorite_count 0这样一个语句就能兜住边界。5. 从项目代码到论文论文章节安排与写作技巧5.1 需求分析章节怎么写得有深度论文和代码是两套体系。代码讲究实现论文讲究证明你想清楚了。需求分析章节如果只写用户可以注册登录、浏览景点、发表评论三行就没了整章塌掉。要写出深度关键是和东营场景做深度绑定。我当时的需求分析分三层来写。第一层写用户角色分析游客、注册用户、管理员每种角色能干什么用表格列清楚。第二层写功能性需求每个模块从功能描述、优先级、使用场景、前置条件四个维度拆解。比如景点检索这个功能使用场景是游客想找东营境内适合亲子游的景区前置条件是景区分类数据和热度数据正确维护这一拆下来一个模块就能写小半页。第三层写非功能需求从性能页面响应时间不超过3秒、安全密码加密存储、后台接口防越权、可用性浏览器兼容、移动端适配三个角度配数据目标。用例图是需求分析章节的标配。可以用 StarUML 或 ProcessOn 画角色就三个用例控制在15个以内不要画得密密麻麻。图下面一定要配一段文字描述核心用例流程拿景点收藏举例用户登录→进入景点详情页→点击收藏按钮→系统校验登录态→校验是否已收藏→插入收藏记录→页面反馈成功。这样一条流程写下来老师一眼能看出你是真的做了系统而不是套模板。5.2 系统设计章节的图与表系统设计章节的关键是图比字管用。我建议至少准备四类图按顺序排列即是系统架构图表现浏览器、应用服务器、数据库的分层关系、功能模块图树状图展示前台后台各模块组成、数据库ER图画清楚7张表之间的关系、核心业务时序图如用户发表评论的完整时序。画架构图时我给个建议不要放出来一大坨技术术语就完事。每个层级都要配一行文字说明比如Controller层参数校验、路由映射、统一异常处理这样。图本身说明结构文字补充职责答辩时老师看图提问时你回答的逻辑就是现成的。数据库表结构说明部分我在正文中放了一张主表的字段说明总表然后把每张表的建表 SQL 放进附录正文里用一页左右讲解表间关系和几处关键设计联合索引、逻辑删除、冗余字段。把前面提到的 favorite 联合唯一索引、scenic 表冗余热度字段拿来做专门讲解这一章节就不愁没内容了。5.3 测试章节与答辩准备系统测试章节很多人随便写几句就过了其实这里是论文性价比最高的地方。我做了两类测试功能测试和性能测试。功能测试我列了一个测试用例表格包含用例编号、测试步骤、预期结果、实际结果、是否通过覆盖了登录、注册、景点检索、评论、收藏、后台管理这些核心流程大概20条左右。这个表格可以显得非常规范实际操作时我也是真的照着用例表一项项点过去的截图附上效果图这一章几乎不需要额外加工。性能测试我对两个场景做了简单压测首页景点列表接口和搜索接口。用 JMeter 模拟50个并发用户循环请求10次记录平均响应时间、吞吐量、错误率。压测结果和优化前对比——比如搜索接口优化前平均响应850ms加上索引和查询优化后降到210ms——这种数据放在论文里比一百句系统性能良好都有说服力。答辩准备则要兼顾项目和理论两块。项目方面要能讲清每个模块用了什么、为什么这么选、有没有替代方案理论方面就是积累常问的基础题Spring 容器的生命周期、MyBatis 的缓存机制、HashMap 底层结构、Java 内存区域划分、线程池参数含义、索引为什么能加快查询——这些代码里不直接体现但必须答得上的问题实际就是各类 Java 面试题集里反复出现的那些知识点可以按八股文清单系统过一遍。6. 实测中遇到的几个典型问题6.1 启动失败端口占用与配置缩进项目做到后期做的功能越来越多启动 Spring Boot 报错的频率也上来了。最常见的一种是端口占用。我本地开发用8080端口有一次项目启动直接报 Web server failed to start. Port 8080 was already in use。排查方法是命令行执行 netstat -ano | findstr 8080找到占用进程的 PID然后在任务管理器结束进程。后来我学聪明了在 application.yml 里把端口改成 8081同时配置了 server.port${PORT:8081} 这种环境变量优先的写法部署到服务器时可以通过环境变量随时调整。另一种高频启动失败原因是 yml 文件格式。Spring Boot 对 YAML 缩进非常敏感多一个空格或少一个空格启动时直接报解析异常。这个报错信息经常被包装成其他样子比如提示某个配置项类型不匹配。我的排查经验是先从最后几行错误日志往上看找到 Caused by 那一行基本就是配置问题的根因然后检查 yml 里所有缩进是否一致统一用两个空格缩进不要混用 Tab。6.2 IDEA编译OOM的解决路径有同学问过一个很典型的报错IDEA 编译时进程堆大小调整到8000还是报 java.lang.OutOfMemoryError。这个问题不是单纯调堆就能解决的。IDEA 里编译走的是独立的编译器进程它的堆内存设置入口在 Settings → Build, Execution, Deployment → Compiler → Build process heap size (Mbytes)默认只有700M。如果你只改了 IDEA 自身的 vmoptions 里的 -Xmx 参数编译进程根本不受影响。我把编译进程堆大小改成2048后大项目编译基本不再报 OOM。还有一种情况是 Maven 构建时 OOM那就需要调 MAVEN_OPTSexport MAVEN_OPTS-Xmx1024m -XX:MaxMetaspaceSize512m注意 Metaspace 参数Java 8 之后方法的元数据放在 Metaspace默认没有上限或上限设得很高但物理内存有限时同样会撑爆。调参的方向最好是给编译进程足够内存但不盲目给满留下给系统本身和数据库的内存才会更稳定。6.3 数据库连接的类型与驱动坑MySQL 8 的 JDBC 连接串里最坑的参数是时区。我第一次配置 application.yml 时spring: datasource: url: jdbc:mysql://localhost:3306/dongying_travel?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8漏了 serverTimezone启动直接报 The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。这个报错里的乱码就是因为字符集和时区没配对。用完好的连接串后如果还是中文乱码检查数据库连接参数是否带了 characterEncodingutf8以及建表语句是否显式用了 utf8mb4。如果学校强制用 SQL Server 2008还有一个隐藏坑JDBC 驱动版本不能乱选。sqljdbc4.jar 只支持到 SQL Server 2008 是比较稳妥的新版本驱动反而可能出现 TLS 协议不兼容导致连接失败。部署到低版本 SQL Server 时尽量用项目仓库里已有的驱动不要想当然下载最新版。6.4 数据一致性与事务的坑最后讲一个很多同学容易忽视的隐蔽问题事务失效。Spring 的事务是基于 AOP 代理实现的有一个著名的坑——同类内部方法调用 if (this.addFavorite(...)) 这种结构时如果 addFavorite 方法是 Transactional它的 this 调用绕过了代理对象事务注解完全不生效。解决办法是把需要事务的代码抽到独立的 Service Bean 里或者用 AspectJ 编译期织入。这个知识点在很多 Java 面试里也会问属于代码跑得好好的但逻辑可能不对的经典案例。我实际遇到的事务问题是收藏数统计不一致。前面设计过 favorite_count 冗余字段如果收藏插入成功但 UPDATE favorite_count 失败两者就不同步了。我的处理方案是给这两步操作包在一个事务里Transactional(rollbackFor Exception.class) public void addFavorite(Long userId, Long scenicId) { favoriteMapper.insert(new Favorite(userId, scenicId)); scenicMapper.increaseFavoriteCount(scenicId); }rollbackFor 要显式指定 Exception.class 而不是默认的 RuntimeException因为业务代码里可能抛出受检异常不写的话事务不会回滚数据照样对不上。这个是实践里总结出的经验常规教程里很少会提醒但踩上一次就够你查三天。最后再分享一点个人的感触。这类 Java 旅游网站项目代码本身并不算高难度真正拉开差距的是把每一步都当成可解释的设计决策来对待。为什么用 MyBatis Plus、为什么排序下推数据库、为什么收藏计数用冗余字段每一个为什么都清楚论文写起来就是水到渠成答辩也不会心虚。做完这个项目之后我最大的收获反而不是 Spring Boot 的 API 用得更熟了而是学会了一种思维方式先想清楚边界和理由再动手写代码。这套方法论在之后做任何系统、写任何技术方案时都一直在用。
返回列表