ARTICLE DETAIL

资讯详情

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

SSM+MySQL古诗词项目实战:从架构拆解到排错避坑指南

SSM+MySQL古诗词项目实战:从架构拆解到排错避坑指南 简介这是面向Java毕业设计/课程设计的古诗词数字化平台完整源码包基于SSMSpringSpringMVCMyBatis框架与MySQL 5.7开发使用JDK1.8与Maven构建适合需要快速搭建Web管理系统、学习SSM整合实战的开发者。平台涵盖主页、个人中心、用户管理、诗词信息管理、分类管理、诗人信息管理、个人分享管理、系统管理等核心模块另附说明文档与LW资料可系统了解从需求分析到代码实现的完整流程也可直接用于毕设参考或二次开发。资源共1288个文件以Java源码120个、JSP页面109个、JS脚本364个、CSS样式146个为主辅以SQL数据库脚本、XML配置及PNG/JPG图片素材整体约22.59MB目录结构清晰便于按模块检索。目前已有72人学习浏览对于想掌握SSM框架落地、理解前后台交互与权限管理逻辑的读者来说这套资源提供了可直接运行的项目和详细文档能帮助深入学习代码组织、数据库设计及功能模块实现方法是综合提升Java Web开发能力的实用案例。1. 拿到一个SSM的古诗词毕业设计项目先学会看骨架再动手每年毕业季都会有人给我发来一个几十兆的zip文件名字基本长这样古诗词数字化平台源码ssmmysql说明文档LW.zip。拆开之后里面是SSM框架项目、SQL脚本、一份说明文档和一篇带格式的论文。这类项目在外人眼里是“代码”在做过几年后端的人眼里只有三件事Spring怎么把对象串起来MyBatis怎么把SQL和Java方法绑定以及数据库里那几张表怎么设计。如果这三个点能讲清楚跑起来只是时间问题讲不清楚就算项目能启动答辩时也容易被问穿。下面这套流程就是按我平时接手这类SSMMySQL项目时的实际顺序来讲先看架构和表再改配置跑起来然后顺着查询链路改功能最后把报错和坑逐个填上。适用对象很明确做Java毕业设计或课程设计的学生、想拿SSM练手的初级开发者以及领导丢来一个老项目需要快速接手的后端从业者。2. 拆开SSM三层架构这个项目里谁管对象、谁管请求、谁管SQL2.1 为什么SSM在课程设计里这么多年没消失古诗词数字化平台本质上是一个“检索展示后台管理”的典型Web应用业务逻辑并不复杂。真正值得花时间理解的是它背后的SSM组合Spring负责管理一切Java对象包括Service、DAO还包括事务SpringMVC负责接收HTTP请求把URL映射到Controller方法上MyBatis负责把Mapper接口里声明的方法翻译成SQL再把查询结果映射回Java对象。很多新手问过我同一个问题毕业论文里可以换成Spring Boot吗可以但我不建议在这个项目上临时换。原因有两个一是LW和说明文档是按SSM结构写的换成Spring Boot文档、配置截图、答辩PPT都要重做二是面试时SSM依然是高频考点Spring的IoC容器、AOP代理、MyBatis的Mapper代理原理在SSM项目里都是看得见摸得着的。Spring Boot把这些机制藏起来了基础不扎实反而讲不深校招面试时被追问两句就容易露馅。我接手这类项目时做的第一件事不是打开Controller而是先看pom.xml和数据库脚本。pom决定了启动时会踩多少依赖坑数据库脚本决定了功能能不能对上文档。把这两样看完再谈改代码。2.2 表结构设计六张表能撑起整个平台古诗词数字化平台的需求通常包含前台展示诗词、按朝代和诗人浏览、关键词搜索、用户注册登录、诗词收藏后台管理诗词和用户。按这套需求数据库里最常见的表是这六张dynasty朝代表id、name、description、sort_orderpoet诗人表id、name、dynasty_id、intro、image_urlpoem诗词表id、title、poet_id、dynasty_id、content、source、create_timeuser用户表id、username、password、nickname、email、status、create_timefavorite收藏表id、user_id、poem_id、create_timeadmin管理员表id、username、password、last_login_time这六张表里poem表是核心。注意我把朝代id和诗人id都冗余进了poem表而不是只存一个poet_id再通过join去查朝代。原因很简单列表页按朝代筛选时直接where dynasty_id ?就好少一层joinSQL写起来也直观。课程设计的数据量不会超过几万行用一点点冗余空间换查询的简单和性能非常划算。关于外键我的习惯是不建物理外键只在逻辑上维护关系。物理外键在删除诗人时容易被约束卡住导入大批量数据时还要先关掉外键检查徒增麻烦。毕业设计答辩时评委更关心你能不能解释清楚表之间的关联关系而不是数据库里有没有真的写FOREIGN KEY关键字。2.3 能直接执行的建库建表MySQL脚本数据库字符集、字段类型、排序规则是这类项目启动时翻车的高发区。建库时字符集建议直接指定utf8mb4别用默认的latin1否则中文存进去就是问号。下面这个SQL脚本按上面六张表设计可以直接在MySQL命令行或MySQL Workbench里执行-- 创建数据库字符集用utf8mb4排序规则用utf8mb4_general_ci CREATE DATABASE IF NOT EXISTS poetry_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE poetry_db; -- 朝代表 CREATE TABLE dynasty ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, name VARCHAR(50) NOT NULL COMMENT 朝代名称, description TEXT COMMENT 朝代简介, sort_order INT DEFAULT 0 COMMENT 排序字段越小越靠前 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT朝代表; -- 诗人表 CREATE TABLE poet ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 诗人姓名, dynasty_id INT NOT NULL COMMENT 所属朝代id逻辑外键, intro TEXT COMMENT 人物简介, image_url VARCHAR(255) COMMENT 头像地址, KEY idx_dynasty (dynasty_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT诗人表; -- 诗词表 CREATE TABLE poem ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL COMMENT 诗词标题, poet_id INT NOT NULL COMMENT 作者id, dynasty_id INT NOT NULL COMMENT 朝代id冗余字段方便筛选, content TEXT NOT NULL COMMENT 诗词正文, source VARCHAR(100) COMMENT 出处, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_poet (poet_id), KEY idx_dynasty (dynasty_id), KEY idx_title (title) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT诗词表; -- 用户表 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL COMMENT 建议存MD5或BCrypt, nickname VARCHAR(50), email VARCHAR(100), status TINYINT DEFAULT 1 COMMENT 1正常 0禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 收藏表user_id poem_id 联合唯一防重复收藏 CREATE TABLE favorite ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, poem_id INT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_poem (user_id, poem_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT收藏表; -- 管理员表 CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, last_login_time DATETIME ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT管理员表;这段脚本有三个细节值得说清楚。第一poem表给title加了普通索引前台检索大概率按标题做精确匹配或前缀匹配这部分能吃到索引至于LIKE %关键词%的模糊查询用不上索引面试被问到要能说清区别。第二favorite表用联合唯一索引防重复收藏这个约束放在数据库层比在Java代码里“先查再插”可靠得多也少一次查询。第三所有表都指定InnoDB和utf8mb4InnoDB支持事务批量导入诗词数据时涉及事务回滚MyISAM做不到。脚本执行完要往表里灌数据。常见做法是先在后台管理页面里一条条添加但那样太慢。更推荐的做法是先给dynasty和poet表插入基础数据再写一个批量导入接口或者用LOAD DATA INFILE导入整理好的CSV。诗词正文里如果有换行符导入前要把CSV里的换行处理干净否则一行数据会被拆成两行。3. 从zip到能访问环境配对、关键配置与启动步骤3.1 环境版本配对JDK、Maven、Tomcat、MySQL怎么搭最省事很多人在这个环节翻车不是因为代码有问题而是环境版本不配对。SSM是2015年前后最流行的技术栈年代感很强用太新的环境反而容易出兼容问题。我固定用下面这一套组合亲测踩坑最少组件推荐版本说明JDK1.88u201或更高不要用17老项目在17上编译容易遇到javax问题Maven3.6.x3.8以上对部分老仓库依赖解析会失败Tomcat8.5.x 或 9.0.x和JDK 1.8搭配最稳MySQL5.7 或 8.08.0要用对应驱动连接串写法见下文IDEA2019.3以上社区版够用不需要旗舰版有几个容易忽略的细节。JDK装完要确认JAVA_HOME、PATH、classpath三个环境变量都配好命令行执行java -version能看到1.8才算数。Maven要配国内镜像仓库否则拉SSM依赖时卡在org.springframework下半天。MySQL安装时5.7默认root加密码本机登录8.0的安装向导会让你选认证方式这一步别选错否则JDBC连接时会被认证插件拦住。MySQL 8.0有一个很典型的坑默认的caching_sha2_password认证方式配合老版本mysql-connector-java 5.1.x驱动一定会报Public Key Retrieval is not allowed。解决办法是驱动版本跟随数据库版本8.0数据库配8.0.x的驱动并在JDBC连接串里显式加上allowPublicKeyRetrievaltrue。3.2 导入源码后先改这四个关键文件拿到解压后的项目先看src/main/resources目录下有哪些配置文件。这类SSM项目的标配是jdbc.properties、applicationContext.xml、spring-mvc.xml、mybatis-config.xml、web.xml。其中jdbc.properties和web.xml是启动前必须检查的。# jdbc.properties 数据库连接配置 jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/poetry_db?useUnicodetruecharacterEncodingUTF-8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password你的密码这段配置里每个参数都有讲究。useUnicodetrue加characterEncodingUTF-8保证写入数据库的中文不乱码useSSLfalse避免MySQL 8.0客户端连接时做SSL握手报警告或失败serverTimezoneAsia/Shanghai解决时区报错如果缺这个参数连接池初始化会一直报The server time zone value unrecognizedallowPublicKeyRetrievaltrue配合8.0的认证方式使用。改完jdbc.properties再看web.xml。web.xml里挂着Spring的ContextLoaderListener、SpringMVC的DispatcherServlet和CharacterEncodingFilter。三者顺序错了SpringMVC的请求地址会全部404。另外web.xml里配置的spring配置文件路径要和resources目录下的文件名保持一致很多zip包解压后目录结构变了这一处经常对不上。如果项目用了Maven还要检查pom.xml里的jstl和servlet-api的scope。jstl的scope应该是compilejavax.servlet-api和javax.servlet.jsp-api的scope必须是provided否则部署到Tomcat时会因为jar冲突报ClassNotFoundException。这个坑在导入老项目时很常见启动日志里看到和servlet相关的NoClassDefFoundError基本就是它。3.3 Tomcat配置与两种启动方式IDEA里导入项目后依次做这几步确认Maven仓库路径、等待依赖下载、添加Tomcat Server、在Deployment里把war exploded挂上去、配置Application context通常是/poetry或/。启动时如果报端口被占用要么杀掉占用进程要么在Tomcat配置里换一个端口比如8081。如果不想用IDEA命令行也能启动。先mvn clean package把项目打成war包再扔到Tomcat的webapps目录执行bin/startup.sh或startup.bat启动后访问http://localhost:8080/项目名/。这个方案更贴近生产部署也方便演示给评委看。启动成功的标志是Tomcat日志里出现“Deploying web application archive”和启动耗时信息。如果日志停在某处不动九成是数据库连接池初始化失败或者Spring包扫描的类里缺了依赖。这时候不要急着改代码先看异常堆栈第一行按后面第5章的思路排查。4. 把查询链路走通Controller到Mapper的代码该怎么改4.1 一次完整请求走过的环节古诗词平台的列表页是功能核心。用户在前台选择“唐代”、点击“李白”浏览器发一个GET请求路径大致是/poem/list?dynastyId3poetId12。请求先被DispatcherServlet拦截根据RequestMapping注解找到PoemController对应方法Controller不写SQL把参数交给ServiceService调Mapper接口MyBatis在运行时给Mapper生成代理对象去执行Mapper.xml里对应的SQL拿到结果后逐层返回最终由SpringMVC把Model填充到指定JSP页面。这条链路里新手最容易漏两个地方一是Service层漏加Service导致Controller注入时直接空指针二是Mapper接口漏加Repository或者applicationContext.xml里没配Mapper扫描路径导致Mapper Bean创建失败。这两个问题启动时往往不明显等你真正点击查询按钮才报错定位起来要多花一倍时间。4.2 诗词列表页Controller、Mapper接口与动态SQL的写法用“朝代诗人标题关键词”三个条件组合查询作为例子这是古诗词平台最常用到的检索方式也是文档里必写的功能点。// PoemController.java Controller RequestMapping(/poem) public class PoemController { Autowired private PoemService poemService; /** * 诗词列表接口 * param pageNum 页码从1开始前端不传默认1 * param dynastyId 朝代id可以为空 * param poetId 诗人id可以为空 * param title 标题关键词可以为空 */ RequestMapping(/list) public String list(RequestParam(defaultValue 1) Integer pageNum, RequestParam(required false) Integer dynastyId, RequestParam(required false) Integer poetId, RequestParam(required false) String title, Model model) { // PageHelper分页只对下一条查询生效 PageHelper.startPage(pageNum, 10, id desc); ListPoemVO list poemService.searchPoems(dynastyId, poetId, title); PageInfoPoemVO pageInfo new PageInfo(list); model.addAttribute(pageInfo, pageInfo); return poem/list; } }// PoemMapper.java 接口定义 public interface PoemMapper { ListPoemVO searchPoems(Param(dynastyId) Integer dynastyId, Param(poetId) Integer poetId, Param(title) String title); }!-- PoemMapper.xmlnamespace必须和接口全限定名一致 -- mapper namespacecom.example.dao.PoemMapper select idsearchPoems resultTypecom.example.vo.PoemVO SELECT p.id, p.title, p.content, p.source, po.name AS poetName, d.name AS dynastyName FROM poem p LEFT JOIN poet po ON p.poet_id po.id LEFT JOIN dynasty d ON p.dynasty_id d.id where if testdynastyId ! null AND p.dynasty_id #{dynastyId} /if if testpoetId ! null AND p.poet_id #{poetId} /if if testtitle ! null and title ! AND p.title LIKE CONCAT(%, #{title}, %) /if /where ORDER BY p.create_time DESC /select /mapper这套代码里有一个关键细节SQL用了 标签加 标签做动态条件拼装。三个条件都不传时where标签会自动去掉多余的AND避免SQL语法错误传了部分条件时只拼装对应的过滤语句。这种写法比在Java里用StringBuffer拼SQL干净得多也是MyBatis最核心的价值。另一个细节是LEFT JOIN。poem表存的是poet_id和dynasty_id两个数字id页面上要显示诗人姓名和朝代名所以查询时要join两张关联表。用LEFT JOIN而不是INNER JOIN是为了数据导入不完整时即使某首诗的诗人信息缺失诗词本身还能显示不会整行消失。课程设计的数据量小join性能完全没压力。4.3 收藏去重用联合唯一索引而不是代码判断收藏是这类平台常用的增删改功能也是说明文档里必写的功能点。实现不复杂用户点“收藏”后台往favorite表插一条记录再点一次则删除。但有一个细节决定这个功能稳不稳重复点击。如果只靠Java代码先查一遍再决定插入还是删除并发高了会出现重复数据。更可靠的做法是让数据库兜底。前面建表脚本里favorite表加了UNIQUE KEY uk_user_poem (user_id, poem_id)插入时用下面的SQL让MySQL自己处理重复INSERT INTO favorite (user_id, poem_id, create_time) VALUES (#{userId}, #{poemId}, NOW()) ON DUPLICATE KEY UPDATE create_time NOW();执行后通过受影响行数判断结果1表示新增2表示重复更新0表示没有变化。Controller里只需要判断这个数字不用再写“先select再insert”的冗余代码。如果拿到的源码里收藏功能是先查再插建议改成这种写法答辩时也能多讲一个优化点。5. 排错与避坑SSM项目里最常见的五类问题SSM的坑排久了你会发现一半是配置和环境的玄学问题一半是版本不匹配。下面这五条是我从这类项目里踩出来的高频问题按出现频率排了序每一条都按“现象、原因、解决”来说清楚。5.1 现象MySQL 8.0连接池初始化失败报Public Key Retrieval is not allowed这个问题几乎每周都能遇到。原因在于MySQL 8.0默认的caching_sha2_password认证插件在非SSL连接下不允许直接获取公钥而老版本JDBC驱动不认识这个插件自然连不上。解决方法是双管齐下把驱动升级到mysql-connector-java 8.0.x同时在jdbc.url里加上allowPublicKeyRetrievaltrueuseSSLfalse大多数情况改完连接串就通了。如果还报Access denied for user去MySQL里确认账号对应plugin执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码;可以兜底但这相当于绕开了新认证方式正式环境不推荐这么干。5.2 现象调用Mapper方法抛BindingException: Invalid bound statement (not found)看到这个错先别慌九成是namespace或statement id对不上。检查两个位置PoemMapper.java接口里有没有对应方法PoemMapper.xml里 是不是写了接口的全限定名select标签的id和接口方法名是否一致。还有一个隐蔽原因Mapper.xml没被打进target/classes目录。Maven默认只编译resources目录下的xml如果xml文件放在java源码目录里pom.xml需要额外配置把xml也纳入构建资源。这个坑的特点很讨厌项目能正常启动spring配置也不报错但一运行功能就崩排查起来最费时间。5.3 现象页面中文正常但写入数据库后变成???这个现象十有八九是字符集参数没配齐。检查三处必须一致jdbc.url里有没有characterEncodingUTF-8建库时是不是utf8mb4表结构是不是utf8mb4。三处任一缺失都会乱码。另外SpringMVC工程里表单提交的中文乱码需要在web.xml里配置CharacterEncodingFilter并把forceEncoding设为true强制请求和响应都走UTF-8。有些源码包里有这个过滤器但路径写的/*有些写的是.do要和你的请求路径后缀匹配。现在SSM项目基本用restful风格了写/*就行。5.4 现象Tomcat启动成功但访问项目路径一直404先确认两件事。第一IDEA里Tomcat的Deployment有没有把项目部署上去只配了Server没配Deployment是常见操作失误。第二访问路径是不是少了context path。如果Deployment里Application context配的是/poetry那首页地址就是http://localhost:8080/poetry/直接访问8080根路径当然404。如果是用war包部署到独立Tomcat还要确认war包名。Tomcat默认访问路径是war包名项目叫poetry-1.0.war访问路径就是/poetry-1.0/。最简单的做法是把war包改名为poetry.war再扔进去访问/poetry/就能通。5.5 现象列表页能打开但每行数据的诗人名和朝代名都是null这个问题的根因是MyBatis驼峰映射开关没打开。Java属性叫poetName数据库字段叫poet_name默认情况下MyBatis不会自动把下划线字段映射成驼峰属性结果就是poetName一直是null。解决方法是二选一在mybatis-config.xml里开启mapUnderscoreToCamelCasetrue或者在SQL里用别名把字段名直接写成Java属性名比如SELECT po.name AS poetName。第一种方式改一处全局生效第二种方式要逐条改SQL一般推荐第一种。5.6 排查工具把MyBatis的SQL日志打开排错时看不到SQL等于蒙着眼睛猜。在mybatis-config.xml里加一行控制台就会打印出每个Mapper方法真正执行的SQL语句settings setting namelogImpl valueSTDOUT_LOGGING/ /settings如果项目里配了log4j也可以在log4j.properties里把对应包级别调到DEBUG。打开SQL日志后能直观看到#{}占位符替换成了什么值、分页SQL拼成什么样、查询是否走了索引很多问题一眼就能定位。答辩前我建议保留这个配置评委看到你能直接讲出执行的SQL印象分会高不少。6. 数据清洗与性能收尾让答辩项目再稳一点6.1 用一条SQL先解决标点乱码和数据统一古诗词数据源质量参差不齐常见问题是重复、全半角标点混用、正文里混入标题。准备答辩前先跑一遍下面的SQL把全角逗号和句号统一成半角至少保证检索时不因为标点不一致漏掉结果UPDATE poem SET content REPLACE(REPLACE(content, , ,), 。, .) WHERE content LIKE %% OR content LIKE %。%;更完整的做法是反过来统一成中文标点。这里的关键不是用哪套标点而是前端展示、后端搜索、数据库存储必须按同一套标准。否则用户搜“床前明月光”会因为库里存了“床前明月光”这种带标点差异的数据匹配不到。6.2 冒烟测试与面试追问清单跑通之后把项目当答辩材料验证。先做冒烟测试搜索李白、筛选唐代、收藏一首诗、后台新增一首词全流程走一遍记录每个页面对应的URL和操作步骤这些可以直接写进LW和PPT。再用Workbench或命令行执行几条关键SQL确认索引能用上。数据量过万时poem表的检索和收藏列表页会明显变慢这时去查执行计划问题通常出在排序字段没索引。把项目讲透比把功能写多重要得多。面试官追问SSM项目时最常问的几个点Spring中Bean默认是不是单例、MyBatis中#{}和${}的区别、事务注解在什么场景会失效、PageHelper分页插件拦截的是哪个环节。这四个问题都能在这个项目里找到答案建议照着源码一行行过一遍。我自己的习惯是每次改完代码都顺手看一眼控制台打印的SQL确认参数替换和数据结果都正常再点一次页面。这个习惯帮我在答辩前避免过很多次低级翻车希望你也能用得上。希望帮到你。本文还有配套的精品资源点击获取
返回列表