
简介基于Java SpringBoot与MySQL实现的古诗词学习网站完整项目源码适合Java课程设计、毕业设计以及前后端分离开发练习。系统功能覆盖用户端与管理端用户可按朝代、类别浏览诗词进入详情页查看全文、注释与翻译支持收藏、评论、分享上传以及头部搜索栏检索诗词还能修改个人资料并查看管理员通知管理员则能对用户、诗词、诗人、朝代、类别、收藏、评论、通知和用户分享内容进行增删改查与审核。资源包共596个文件压缩后约39.9MB主要包含Java后端Controller源码、SQL数据库初始化脚本、前端HTML/CSS/JS页面、XML配置文件及项目运行素材结构组织清晰便于直接导入集成开发环境运行也方便按业务需求扩展改造。目前已有951人学习适合以此为基础快速完成古诗词主题的课程设计演示或二次开发。1. 古诗词学习网站SpringBootMySQL这个组合能解决什么问题很多刚过完Java基础、想找一个完整课程设计的同学最后都会落到“Java课程设计案例源码”这个搜索词上。古诗词学习网站是个很典型的题目数据量不大但关系清楚功能覆盖搜索、分类、详情、收藏刚好能把SpringBoot的Controller、Service、Mapper三层和MySQL的表设计、索引、分页全部练一遍。这个标题里的“基于JavaSpringBootMySQL”不是花架子它意味着你交付的是一个能启动、能录入数据、能对外提供诗词检索接口的Web项目而不是一个只跑通控制台的练习。适合三类人做毕设或课设的学生、想快速搭一个垂直内容站点的开发者、以及准备SpringBoot面试时需要一个完整项目来描述的求职者。下文我会把从建表到部署的路径完整拆开新手能照着复现熟手可以直接抄其中几个关键配置。2. 先定表结构古诗词数据模型、索引和数据库连接参数2.1 为什么古诗词网站选SpringBootMySQL而不是其他栈先回答一个很多人犹豫过的问题古诗词网站用SpringBootMySQL是不是“过时”了换成NodeMongoDB或者直接搞一个静态站点生成器不是更轻量吗这个质疑有一定道理但换到学习项目和中小型内容站场景SpringBootMySQL反而是最稳的。古诗词数据有明确的关系结构一首诗必然属于一位作者一个作者属于一个朝代一首诗可以被打上多个标签一个用户可以收藏多首诗。这种数据天然适合关系型数据库。MySQL对关系查询、事务、索引、全文检索的支持都很成熟而且社区资料多踩到什么坑都能搜到答案。SpringBoot这边最大的价值不是性能而是“约定大于配置”带来的开发节奏你只需要写好实体类、Mapper接口和Service剩下的自动装配、依赖注入、内嵌Tomcat、健康检查都帮你处理好了。对一个需要在一两周内出成果的课设或毕设来说这个组合能把时间花在业务功能上而不是折腾环境。也有人说那用Spring Boot Vue 前后端分离不是更时髦吗时髦不等于省事。前后端分离意味着你要同时维护两套工程、处理跨域、考虑接口鉴权这些对学习项目是额外负担。我见过很多同学在分离架构上花了一周联调结果诗词查询接口还没写完。所以我一般建议除非你的课题明确要求前后端分离否则用Thymeleaf模板做页面一套工程搞定先把业务跑通。2.2 建表SQL作者表、诗词表、收藏表和用户表数据模型是整个项目的底盘。我给你的建议是最多五张表用户表、作者表、诗词表、收藏表、分类标签表。不要一开始就设计十几张表诗词数据的关系没那么复杂表多了只会让联表查询越来越难维护。先看作者表和诗词表这是核心。下面的SQL可以直接跑在MySQL 5.7或8.0上-- 作者表存放姓名、朝代、简介 CREATE TABLE author ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT 作者ID, name VARCHAR(50) NOT NULL COMMENT 作者姓名, dynasty VARCHAR(20) NOT NULL COMMENT 朝代, birth_year SMALLINT DEFAULT NULL COMMENT 出生年份存四位数如 701, death_year SMALLINT DEFAULT NULL COMMENT 去世年份, intro TEXT COMMENT 作者简介, created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci COMMENT作者表; -- 诗词表核心业务表 CREATE TABLE poem ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT 诗词ID, title VARCHAR(100) NOT NULL COMMENT 标题, author_id INT UNSIGNED NOT NULL COMMENT 作者ID关联author.id, content TEXT NOT NULL COMMENT 正文, translation TEXT COMMENT 译文, appreciation TEXT COMMENT 赏析, tags VARCHAR(255) DEFAULT COMMENT 标签逗号分隔如思乡,送别, view_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 浏览量, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态1显示0隐藏, created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, KEY idx_author_id (author_id), KEY idx_view_count (view_count), FULLTEXT KEY ft_content (title, content) WITH PARSER ngram ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci COMMENT诗词表;这段SQL里有几个值得注意的参数。作者表的年代字段我用SMALLINT而不是VARCHAR是为了以后能做“检索某朝代的诗人”这种范围查询LATIN1或者utf8在建表时千万别用古诗词里大量生僻字、注音符号必须用utf8mb4配utf8mb4_unicode_ci这种排序规则。诗词表里我显式加了idx_author_id因为列表页按作者查诗是很高频的操作不加索引的话数据量过万之后就会明显变慢。收藏表往往是课设里被忽略的一张表但它关系到多对多关系能不能讲清楚。一个用户可以收藏很多首诗一首诗可以被很多用户收藏这就是典型的多对多需要一张中间表来拆CREATE TABLE favorite ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id INT UNSIGNED NOT NULL COMMENT 用户ID, poem_id INT UNSIGNED NOT NULL COMMENT 诗词ID, created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 收藏时间, UNIQUE KEY uk_user_poem (user_id, poem_id), KEY idx_poem_id (poem_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT收藏表;UNIQUE KEY uk_user_poem是必要的它能防止同一个用户对同一首诗产生重复收藏记录同时这个唯一索引也会作为查询用户收藏列表的底层加速。如果你还想要“当前用户是否已收藏”这种标记只要查这张表就行不需要额外字段。2.3 检索字段设计全文索引、LIKE模糊查询和分词古诗词网站最常用的功能就是搜索按标题搜、按内容搜、按作者搜。多数课设代码会直接写LIKE %关键词%这样写没毛病数据只有几百条时感受不到差异但一旦导入几千首全唐诗每次搜索都要全表扫描MySQL的CPU占用会明显升高。更好的方案是MySQL 5.7开始支持的ngram全文索引专门解决中文分词问题。我上面建表SQL里写的FULLTEXT KEY ft_content (title, content) WITH PARSER ngram就是为标题和正文建立中文全文索引。查询时这样写SELECT id, title, author_id, view_count FROM poem WHERE MATCH(title, content) AGAINST(春江花月夜 IN NATURAL LANGUAGE MODE) LIMIT 20;ngram解析器默认分词粒度是2也就是会把“春江花月夜”切成“春江”“江花”“花月”“月夜”这些二元组。对古诗词这种短文本来说2元切分基本够用。如果你发现长词匹配效果不好可以调整全局参数ngram_token_size但要注意这需要重启MySQL并重建索引我建议先用默认值。那是不是说LIKE就没用了不是。LIKE模糊查询适合“作者名搜索”这种场景因为作者名字大多是两个字全文索引对单字和双字匹配反而没有优势。我实际项目里的策略是搜作者名走LIKE因为走的是author.name LIKE CONCAT(%,#{name},%)配合数据量小性能没问题搜诗词标题和正文走全文索引。你甚至可以两条路都保留界面上做一个下拉选择搜索范围。这里要提醒一句全文索引和LIKE的结果集排序规则完全不同全文索引会按相关度排序LIKE只能按你指定的字段排序这个差异在需求分析时要提前确认。2.4 数据源配置连接池参数和utf8mb4缺一不可表结构定了接下来是SpringBoot连MySQL的配置。这块出问题最多的地方不是代码逻辑而是配置文件里那些“玄学参数”。下面这个application.yml片段是我在课设项目里常用的基础配置spring: datasource: url: jdbc:mysql://localhost:3306/poetry?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver hikari: minimum-idle: 5 maximum-pool-size: 15 idle-timeout: 300000 connection-timeout: 30000先说字符集。URL里的characterEncodingutf8mb4要和数据库表、客户端连接三方保持一致。注意MySQL驱动8.0版本里你写成characterEncodingutf8mb4没问题但老驱动只认utf8如果你用的驱动是5.x这里写成characterEncodingutf8即可否则会启动报错。再说allowPublicKeyRetrievaltrue。这个参数是给MySQL 8.0的caching_sha2_password认证插件准备的。MySQL 8.0默认认证方式改了有些版本下JDBC第一次连接会报Public Key Retrieval is not allowed加上这个参数就是允许客户端向服务器取公钥加密密码。测试环境加它没问题生产环境建议用sha256_password或把参数关掉这里不过度展开。连接池我用的HikariCP它是SpringBoot默认的不需要额外引入依赖。最大连接数15、最小空闲5对一个诗词学习网站足够。经常有人问“MySQL的数据库连接池到底设多大”回答是不要照抄网上的200、300你这种项目扛的并发量不大连接池开太大反而浪费MySQL的资源一般最大不超过20。最后idle-timeout和connection-timeout这两个超时参数直接关系到一个经典故障晚上挂机第二天早上打开网站第一次请求特别慢甚至报连接超时。原因就是池里的空闲连接被MySQL踢断了但连接池不知道。解决方法是把idle-timeout设得比MySQL的wait_timeout短或者加一个连接存活检查HikariCP会自动做但你至少要把参数配置对。3. 后端实现搜索诗词、作者详情和收藏功能的SpringBoot代码3.1 工程结构、Maven依赖和MyBatis分页插件拿到这个项目标题你第一件事是建一个标准SpringBoot工程。包名我习惯用com.poetry内部结构分成controller、service、mapper、entity、common五层。这个分层不是硬性规定但课设答辩时老师问到“为什么分层”你能说出“Controller负责参数校验Service负责业务逻辑Mapper负责SQL操作”就及格了。pom.xml里的关键依赖下面这个够用parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.7/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency /dependencies这里有个版本搭配的坑我专门在第5章展开但现在先给你结论如果你的JDK是8SpringBoot老老实实用2.7.x不要追新上3.x。MyBatis官方启动器mybatis-spring-boot-starter的2.3.x版本对SpringBoot 2.x支持最好。PageHelper的分页插件在这个组合里是顺滑的等下看代码就知道。3.2 诗词搜索接口从Mapper到Service再到Controller搜索接口是网站的门面。我先写Mapper层的XMLSpringBoot里既可以用注解写SQL也可以用XML我建议用XML因为SQL一长注解里拼接字符串很难维护。!-- PoemMapper.xml -- mapper namespacecom.poetry.mapper.PoemMapper !-- 搜索结果包含作者名 -- select idsearchPoems resultTypecom.poetry.entity.PoemVO SELECT p.id, p.title, p.content, p.view_count, a.name AS authorName, a.dynasty FROM poem p LEFT JOIN author a ON p.author_id a.id where if testkeyword ! null and keyword ! AND (p.title LIKE CONCAT(%, #{keyword}, %) OR p.content LIKE CONCAT(%, #{keyword}, %) OR a.name LIKE CONCAT(%, #{keyword}, %)) /if AND p.status 1 /where ORDER BY p.view_count DESC, p.id DESC /select /mapper这段SQL我先说逻辑用LEFT JOIN把作者表的名和朝代带出来这样前端列表页不用再查一次作者表。where标签是MyBatis的动态SQL只有keyword非空时才拼接LIKE条件。排序按浏览量倒序这能让高人气诗词排在前面体验上比按ID排序舒服。接着是Service层。Service不做SQL拼接它只负责补充分页和组装结果Service public class PoemService { Autowired private PoemMapper poemMapper; public PageInfoPoemVO search(String keyword, int pageNum, int pageSize) { // PageHelper.startPage 必须在 Mapper 查询之前调用 PageHelper.startPage(pageNum, pageSize); ListPoemVO list poemMapper.searchPoems(keyword); return new PageInfo(list); } }PageHelper的使用要点就一句话PageHelper.startPage(pageNum, pageSize)必须放在Mapper方法调用之前而且中间不能夹其他的SQL操作否则分页会作用到错误的查询上。很多人第一次用MyBatis的分页插件把startPage写在Service方法第一行结果前面还有一个查询用户信息的SQL分页就加到那个SQL上去了。新版PageHelper会通过PageInterceptor拦截但拦截的是它后遇到的第一个查询顺序不能乱。Controller层负责参数接收RestController RequestMapping(/api/poem) public class PoemController { Autowired private PoemService poemService; GetMapping(/search) public ResultPageInfoPoemVO search( RequestParam(defaultValue ) String keyword, RequestParam(defaultValue 1) int page, RequestParam(defaultValue 10) int size) { return Result.success(poemService.search(keyword, page, size)); } }Result是一个统一响应体的包装类里面包含code、message、data三个字段。这个类在课设里几乎是标配别嫌它多余——没有统一返回结构前端处理异常时每个接口都要写一遍判空逻辑有它之后至少能保证“接口挂了也能返回一个JSON对象”而不是直接抛字符串。RequestParam(defaultValue 1)这行是给页码设默认值的用户不传page也能正常访问避免怪异的空指针问题。3.3 诗词详情和作者详情列表联表一次详情再拆两次搜索列表我们已经联表了那点击某首诗进详情页要不要继续用JOIN我的答案是不用。详情页除了诗本身还要展示作者的完整生平、同作者的其它作品甚至相关推荐。这些数据如果全塞进一个大JOINSQL会变得很长而且会查出很多重复字段。我一般这样做先按poem_id查出诗词本身带上作者ID和作者名GetMapping(/detail/{id}) public String detail(PathVariable Integer id, Model model) { PoemVO poem poemMapper.getPoemById(id); Author author authorMapper.getAuthorById(poem.getAuthorId()); ListPoem sameAuthorPoems poemMapper.listByAuthorId(poem.getAuthorId(), 5); model.addAttribute(poem, poem); model.addAttribute(author, author); model.addAttribute(sameAuthorPoems, sameAuthorPoems); return poem/detail; }三次查询各干各的事。getPoemById只查诗词主表顺便把浏览量加一可以用一条UPDATE poem SET view_count view_count 1 WHERE id #{id}完成避免读出再写回的竞态问题。listByAuthorId用来做“本作者更多作品”推荐LIMIT 5就够了。这样做的理由很实际SQL可读性强而且缓存好做。如果将来想去掉数据库压力你可以在Service层给getPoemById加本地缓存一次查询一个键方便命中。3.4 收藏功能先判重再插入用Transactional避免脏数据收藏功能关系到MySQL事务是面试时能拿出来讲的点。接口逻辑很直白用户点击收藏后端检查该用户是否已收藏过没有就插入一条记录并给一个收藏成功的标志。Service public class FavoriteService { Autowired private FavoriteMapper favoriteMapper; Transactional(rollbackFor Exception.class) public boolean favorite(Integer userId, Integer poemId) { int count favoriteMapper.countByUserAndPoem(userId, poemId); if (count 0) { return false; // 已收藏直接返回 } int rows favoriteMapper.insert(userId, poemId); return rows 0; } }Transactional在这里作用很大假设以后你还要维护一个“用户的收藏数量”字段那么插入收藏表和更新用户收藏计数这两个操作必须是一个原子操作要么全成功要么全回滚。rollbackFor Exception.class的意思是任何异常都回滚包括自定义的业务异常和RuntimeException不加这个参数的话只有RuntimeException才回滚检查异常不会触发回滚这是新手最容易误解的一点。注意我上面这种“先查count再insert”的写法在并发下有可能重复插入稳妥的做法是直接用数据库层的唯一索引兜底也就是第2章建表时加的UNIQUE KEY uk_user_poem配合INSERT IGNORE INTO或ON DUPLICATE KEY UPDATE从源头杜绝重复。4. 把网站跑起来页面模板、JDK环境变量和MySQL启动步骤4.1 前后端一体还是分离学习项目选Thymeleaf更省事标题里只写了JavaSpringBootMySQL没有提前端框架说明这个项目的前端可以很自由。我的建议是如果你不是为了练Vue就老老实实用Thymeleaf做服务端渲染。理由有三个。第一工程简单。Thymeleaf模板直接放在src/main/resources/templates下面不需要额外启动一个Node服务不需要解决跨域也没有axios配置这一层。启动SpringBoot就是启动整个网站这对新手来说少了一整条故障链路。第二数据传递自然。Controller里往Model塞什么页面上就能直接用${poem.title}取出来不会有JSON序列化和字段名对不上的问题。第三SEO友好。古诗词网站的流量主要靠搜索引擎服务端渲染的页面文章内容直接在HTML源码里爬虫抓得到。当然如果你课题要求是前后端分离那就用REST接口配合Vue或React这个不冲突。第3章我写的是RestController已经把接口层做好了第4章的Thymeleaf是为一体化部署准备的两者可以共存只改一个注解的事。4.2 首页、列表页、知识卡片用模板引擎怎么写Thymeleaf模板的核心是“在HTML标签里写表达式”。以列表页为例搜索结果的每一行诗词用th:each循环渲染!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title搜索结果/title /head body div classpoem-list div th:eachpoem : ${pageInfo.list} classpoem-card h3 th:text${poem.title}标题/h3 p classauthor th:text${poem.authorName} · ${poem.dynasty}作者/p p classcontent th:text${poem.content}正文/p a th:href{/api/poem/detail/{id}(id${poem.id})}查看赏析/a /div /div /body /htmlth:text会自动转义HTML字符对内容安全是有好处的th:href和{...}结合可以拼出带路径参数的URL比字符串拼接优雅得多。这里接的是第3章里/api/poem/detail/{id}这个路径。注意如果你用的是RestController返回的是JSON页面跳转要用Controller或单独加一个页面Controller。很多新手栽在这两个注解只能返回到Thymeleaf模板RestController返回的是字符串本身不是页面。页面里还有一个隐藏技巧用th:if${pageInfo.hasPreviousPage}来控制“上一页”“下一页”按钮是否显示。PageInfo对象已经封装了这些状态直接复用即可没必要自己在Controller里算当前页是不是第一页。4.3 本地从零启动装JDK、装MySQL、导入SQL、跑SpringBoot这一步我按顺序给你中间任何一步失败都按这个顺序排错。# 1. 检查Java环境java环境变量配置不对时下面命令会报 command not found java -version # 2. 配置JAVA_HOMEmacOS/Linux示例Windows在系统环境变量里设置 export JAVA_HOME/Library/Java/JavaVirtualMachines/jdk1.8.0_202.jdk/Contents/Home export PATH$JAVA_HOME/bin:$PATH # 3. 启动本机MySQLmacOS通过brew安装的服务 brew services start mysql # 或者直接执行mysqld_safeLinux则用 systemctl start mysqld # 4. 连接MySQL并建立数据库 mysql -uroot -p CREATE DATABASE poetry DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE poetry; SOURCE /path/to/init.sql; # 5. 回到SpringBoot项目根目录执行Maven启动 mvn spring-boot:run每条命令背后都有坑。第1步如果提示java not found十有八九是JAVA_HOME环境变量没配或者配了没生效重启终端试试。第3步在macOS上最容易报Cant connect to local MySQL server through socket /tmp/mysql.sock这是MySQL服务没起来而不是密码错别急着改配置。第4步导入SQL文件时文件路径不要有中文和空格否则SOURCE命令会读不到。第5步用mvn spring-boot:run会占用当前终端想后台运行就打包成Jarmvn clean package -DskipTests java -jar target/poetry-0.0.1-SNAPSHOT.jar如果打包慢先确认Maven镜像用的是国内仓库不然下载依赖能卡到你怀疑人生。5. 避坑记录MySQL连接、中文乱码、版本冲突和分页失效5.1 MySQL连接报错error 2002服务没起来不是密码问题现象启动SpringBoot时控制台报error 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock。原因这个报错的意思是客户端尝试通过Unix套接字连接MySQL但套接字文件不存在。最常见原因是MySQL服务根本没启动其次是MySQL装在Docker容器里或自定义了socket路径和客户端默认的/tmp/mysql.sock不一致。解决先去服务管理里确认MySQL运行状态macOS用brew services listLinux用systemctl status mysqld。如果服务确实没起来启动它如果起来了还报错在JDBC URL里把localhost改成127.0.0.1强制走TCP连接而不是Unix套接字这招能绕开大部分socket路径问题。5.2 古诗词中文乱码数据库字符集、JDBC参数、页面编码三层一起查现象页面出现“鍝ュ攱”“????”之类的乱码或者数据库中诗文内容正确、查询出来却是问号。原因三层字符集不一致。第一层是数据库表没设成utf8mb4默认用了latin1第二层是JDBC URL没带characterEncodingutf8mb4第三层是HTML页面没声明UTF-8浏览器按系统默认编码解析。解决一条路走通。建库时就用CREATE DATABASE poetry DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci建表时也显式写上字符集JDBC URL里加上useUnicodetruecharacterEncodingutf8mb4Thymeleaf模板head里加meta charsetUTF-8。如果导入已有SQL文件后数据乱码别在代码里折腾先把库里数据清掉重建再导入因为错误字符集写入的数据已经是坏数据了。顺便提一句MySQL 8.0客户端默认字符集是utf8mb45.7老版本可能需要你在mysql命令行里先执行SET NAMES utf8mb4;。5.3 SpringBoot版本太高引发的连锁不兼容现象用SpringBoot 3.2.x创建项目启动时报ClassNotFound: javax.servlet.Filter或者mybatis-spring-boot-starter和pagehelper-spring-boot-starter大量冲突。原因SpringBoot 3.x从javax包迁移到了jakarta包并且最低要求JDK 17。如果你本机还装着JDK 8跑3.x项目编译阶段就会报错。很多农历课设教程是2020年左右写的依赖版本是基于SpringBoot 2.x你直接套到3.x上自然翻车。解决做课设首选SpringBoot 2.7.x配JDK 8这个组合最成熟。如果非要上3.x那把JDK换成17及以上同时MyBatis启动器换成mybatis-spring-boot-starter的3.0.x版本PageHelper换成pagehelper-spring-boot-starter的2.0.x版本。注意升级版本不是只改parent版本号就完事所有用到javax.servlet的代码都要改成jakarta.servlet。我建议别在这上面浪费时间2.7已经足够你答辩了。5.4 PageHelper分页失效startPage作用到了错误的查询上现象点击搜索第二页返回的数据还是第一页的内容或者LIMIT加到了author表查询上导致数据错乱。原因PageHelper拦截的是它后面第一条SQL。如果你在同一个Service方法里先调用了authorMapper.selectById(userId)再调用poemMapper.searchPoems()分页插件会作用在第一个查询上。还有一种情况是Mapper方法返回类型不是List而是PageInfo插件会报类型错误。解决把startPage紧贴在目标查询前面中间不要插入任何其他Mapper调用。如果用了Transactional要注意分页查询在事务内执行和事务外执行结果是一样的但查询顺序不要被绕过。还有一个低频坑MyBatis的二级缓存开启后分页查询如果命中了缓存LIMIT不会生效这就需要关闭该Mapper的二级缓存或者在Cache配置上加上readOnly属性。5.5 MySQL 8.0的认证插件导致连接失败现象本地用Navicat能连SpringBoot启动却报Unable to load authentication plugin caching_sha2_password或Public Key Retrieval is not allowed。原因MySQL 8.0默认新用户的认证插件是caching_sha2_password一些老驱动或非标准客户端不支持该插件。解决在JDBC URL加allowPublicKeyRetrievaltrueuseSSLfalse这是最省事的办法。更底层一点的方案是登录MySQL执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码;把该用户的认证方式改回mysql_native_password。如果你是在生产环境我不建议改认证方式而是升级驱动版本到8.0以上。6. 进阶给诗词搜索加分词、缓存和一组验收用例6.1 把LIKE搜索升级成HanLP分词搜索当诗词库到了几万首用户输入“春江花月夜”这种长句LIKE模糊匹配其实也能搜出来但搜索“月”这种单字结果集大到没法看。这时候可以在Service层接入HanLP分词把一句话拆成多个词再分别匹配。做法不复杂引入HanLP的依赖后写一个分词工具类public ListString segment(String keyword) { // 使用HanLP标准分词停用词过滤 ListTerm termList HanLP.segment(keyword); return termList.stream() .map(term - term.word) .filter(word - word.length() 1) .collect(Collectors.toList()); }前端搜索“思乡明月夜”后端先分词得到“思乡”“明月”几个词然后组装成动态SQL的多个LIKE条件排序时给完全命中的结果加权。这比单条LIKE更合理也让你的项目在答辩时多一个技术亮点。注意HanLP的词典默认是面向现代汉语的对“浔阳江头夜送客”这种句子可能切得不够准测试阶段要人工核对Top结果。6.2 热点诗词缓存先上本地缓存再考虑Redis每次刷新首页都查一次数据库是不划算的。我先用Spring Cache搭配一个本地ConcurrentHashMap做缓存Cacheable(value poemDetail, key #id) public PoemVO getPoemDetail(Integer id) { return poemMapper.getPoemById(id); }加好之后同一个ID的详情查询在缓存有效期内不会再穿透到数据库。对这个项目来说Redis不是必需品等真出现并发瓶颈再加。很多课设最大的毛病不是没用Redis而是为了写Redis而写Redis最后缓存和数据库数据对不上反而成了扣分项。本地缓存不加额外依赖切Redis也只需要改一下缓存管理器这个演进思路要讲清楚。6.3 验收用例一台干净电脑上的自检清单交付项目前我习惯跑一组固定用例确认“是不是真能干活”。你可以把下面这个表格当成你的验收清单测试场景操作预期结果首次启动mvn spring-boot:run控制台无异常端口8080被监听首页浏览器访问 /index展示推荐诗词无乱码关键词搜索输入“春江花月夜”能返回该诗作者、朝代显示正常作者搜索输入“李白”返回李白名下多首诗无重复数据详情页点击一首诗浏览量1同作者作品列表能加载收藏同一用户对同一首诗点两次第二次提示已收藏数据库无重复记录分页第2页每页5条返回5条且与第1页不重复异常输入搜索空字符串、特殊字符script页面不报错无XSS注入风险最后一列关于XSS的用例你在模板渲染时用th:text就已经自动转义了这是一体化模板的一大优势。如果走了前后端分离全局过滤器还要单独处理这也是我推荐Thymeleaf的附加理由。这个项目做完之后我自己的习惯是把init.sql、启动说明、JDK版本要求写在一个README里因为两个月后你自己都会忘掉密码和版本教材上不会教你这个但实际交付时这是最值钱的一部分。希望你做完之后不只是跑通了代码还能把每个表为什么这么建、每个参数为什么这么设讲给别人听那我这篇文章的目的就达到了。希望帮到你。本文还有配套的精品资源点击获取