简介:基于Java+SSM框架与MySQL数据库开发的博客系统完整毕业设计项目,面向高校计算机专业学生、毕业设计选题者及希望掌握Spring+SpringMVC+MyBatis三层架构实践的Java初学者。压缩包共含775个文件,容量约34.87MB,以98个Java源码文件、54个Vue组件、160个JavaScript脚本、52个CSS样式为主体,辅以SQL数据库脚本、论文文档、启动脚本(install/run/build)及GIF操作演示截图,覆盖从环境搭建到最终答辩展示的完整链路。系统功能涵盖文章发布、分类管理、评论互动等博客核心模块,界面基于ElementUI与Bootstrap构建,风格清爽、交互流畅,所有代码经严格调试可直接运行。已有69人学习参考,下载后可直接导入IDEA配合MySql8.0与Tomcat使用,既可作为毕业设计提交材料,也适合在此基础上二次开发学习。
1. 为什么毕业设计选 java+ssm+mysql 博客系统:一个绕不开的起步组合
打开一个号称“基于java+ssm+mysql的博客系统源码+数据库+论文”的项目包,最常见的三种结局:导进去全是红叉、启动就报 404、能跑但答辩讲不清。SSM 博客系统是 Java 后端从业者最熟悉的起步组合——Spring 管对象、SpringMVC 收请求、MyBatis 连 MySQL,功能上把登录、文章增删改查、分页、评论、上传都串了一遍。它不像电商秒杀系统那样炫,但胜在结构完整、条理清楚,刚好覆盖毕业设计和初级后端面试的绝大部分考点。这篇笔记会按“架构→数据库→跑通→避坑→论文”的顺序,把这套系统的骨架和落地细节一次讲透。
2. 先把架构看明白:Spring、SpringMVC、MyBatis 在博客系统里的分工
SSM 不是三个独立工具凑在一起,而是一条完整的处理链。浏览器发出请求,SpringMVC 先接住,转发给 Controller;Controller 调 Service 处理业务;Service 通过 MyBatis 的 Mapper 访问 MySQL;返回结果再逐层交回去。很多新手把代码成功跑起来后,最容易被问住的一句话是“你这里 new 的对象是哪来的”。这一句答不上来,论文写得再长也白搭。
2.1 Spring 容器:对象不再靠 new,而是靠容器注入
传统写法里,Controller 要用 Service 就直接 new 一个,Service 要用 Mapper 也 new 一个。对象之间的耦合被写死在代码里,测试、替换都很麻烦。SSM 项目改由 Spring 容器统一创建和管理这些对象,要用的地方通过 @Autowired 注入进来,这就是控制反转(IoC)和依赖注入(DI)最朴素的体现。
一个典型的 applicationContext.xml 长这样:
<!-- 1. 开启注解扫描:容器会去这些包下找 @Controller @Service @Repository --> <context:component-scan base-package="com.blog.controller"/> <context:component-scan base-package="com.blog.service"/> <context:component-scan base-package="com.blog.dao"/> <!-- 2. 配置数据源:告诉容器数据库在哪、怎么连 --> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf8&useSSL=false"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <!-- 3. 配置 SqlSessionFactory:MyBatis 的入口,Mapper XML 位置在这里声明 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean>这段 XML 里最值得注意的不是 bean 本身,而是 base-package 和 mapperLocations 两个属性。前者决定了哪些包下的类会被容器扫描并纳入管理,漏掉任何一个中间层,注入了就会报 NoSuchBeanDefinitionException;后者告诉 MyBatis 去哪里加载 SQL 映射文件,路径写错时项目能启动成功,但一访问数据库接口就报 Invalid bound statement (not found),属于典型的“启动不报错、运行即翻车”。
参数说明:driverClassName 使用 com.mysql.jdbc.Driver 时,连 MySQL 5.x 没问题;MySQL 8.0 需要换成 com.mysql.cj.jdbc.Driver。url 里的 useUnicode=true 和 characterEncoding=utf8 是防中文乱码的标配,刚导入项目看到中文变问号,九成是这两项缺失。
2.2 SpringMVC:一条博客详情请求是怎么被处理的
SpringMVC 的核心是前端控制器 DispatcherServlet。所有请求先经过它,再由它分配 HandlerMapping 找到对应的 Controller 方法,方法执行完返回一个视图名或 JSON,这个过程在博客系统里非常直观。
例如点击“阅读全文”这条链接时,地址是 /article/detail/12,对应 Controller 代码:
@Controller @RequestMapping("/article") public class ArticleController { @Autowired private ArticleService articleService; @RequestMapping("/detail/{id}") public String detail(@PathVariable("id") Integer id, Model model) { Article article = articleService.getArticleById(id); model.addAttribute("article", article); return "article/detail"; } }代码逻辑分三步:@PathVariable 从 URL 中取出文章 id;调用 service 层拿到文章实体和关联分类;放入 Model 后交给视图解析器渲染 article/detail.jsp。注意 Controller 没有 new 过任何 Service,它只负责声明“我需要一个 ArticleService”,具体实现由 Spring 注入。
参数说明:@RequestMapping 支持多级路径和通配符,{id} 这种写法叫路径变量,默认按参数名匹配。如果前端传的 id 是字符串且无法转换为 Integer,SpringMVC 会直接抛 400 错误,这种情况在测试时也常遇到,不是你接口写错,是类型对不上。
2.3 MyBatis:博客列表分页与动态 SQL
Service 层拿到请求后,最终要拼出 SQL 去查库。MyBatis 把 SQL 写在 Mapper XML 里,和 Java 代码分离,这是 SSM 项目最好讲、也最容易被问到的一层。博客首页的文章列表通常要支持搜索和分页,一个常见的查询写法:
<select id="selectArticleList" resultType="com.blog.entity.Article"> SELECT a.id, a.title, a.summary, a.create_time, c.name AS categoryName FROM blog_article a LEFT JOIN blog_category c ON a.category_id = c.id <where> <if test="title != null and title != ''"> AND a.title LIKE CONCAT('%', #{title}, '%') </if> </where> ORDER BY a.create_time DESC </select>这个 XML 里有两个关键设计。LEFT JOIN 是为了在文章列表页直接展示分类名称,而不是让前端再拿 category_id 做二次查询;动态 标签解决的是搜索条件可选的问题——title 为空时不拼条件,不为空时自动拼上 LIKE,并且自动去掉多余的 AND。用 #{} 拼参数时 MyBatis 会生成预编译占位符,能有效防止 SQL 注入,这也是面试官喜欢追问的点——如果写成 ${title},同样能运行,但属于直接拼接字符串,注入风险完全不同。
分页在 SSM 项目里常见做法是引入 PageHelper 插件。使用时要紧挨着查询方法调用 startPage:
PageHelper.startPage(pageNum, pageSize); List<Article> list = articleMapper.selectArticleList(article); PageInfo<Article> pageInfo = new PageInfo<>(list);startPage 设置的分页参数只对紧随其后的第一条 Mapper 查询生效,中间如果插了别的查询或被其他逻辑打断,分页就失效。这个细节是面试和现场调试的高频坑,后面避坑章节还会展开。
3. 数据库设计:博客系统的五张核心表、索引与初始化导入
数据库是这套系统里最“实”的部分。论文里的概念数据模型(E-R 图)、物理数据模型,答辩时很可能直接盯着你的表结构问。博客系统的表不算多,常见设计是五张主表加一张关联表:用户、文章、分类、评论、标签,以及文章-标签中间表。下面按这个结构把字段讲清楚。
3.1 五张核心表:字段怎么定,类型怎么选
用户表最简单,但最容易踩坑的是密码字段:
CREATE TABLE `blog_user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID', `username` varchar(50) NOT NULL COMMENT '登录名', `password` varchar(128) NOT NULL COMMENT '密码(MD5)', `nickname` varchar(64) DEFAULT NULL COMMENT '昵称', `avatar` varchar(255) DEFAULT NULL COMMENT '头像路径', `role` tinyint(1) DEFAULT '1' COMMENT '角色: 1管理员 0普通用户', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;为什么 username 要唯一键?因为登录查询依赖 username,唯一索引既保证业务上不重复,又让 where username=? 的查询走索引而不是全表扫描。password 用 varchar(128) 而不是简单写成 varchar(20),是因为 MD5 密文是 32 位,如果后续改成加盐 MD5 或 BCrypt,长度也得够。
文章表是整库的核心,字段最多:
CREATE TABLE `blog_article` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '文章ID', `title` varchar(200) NOT NULL COMMENT '标题', `summary` varchar(500) DEFAULT NULL COMMENT '摘要', `content` longtext COMMENT '正文', `cover_image` varchar(255) DEFAULT NULL COMMENT '封面图', `category_id` int(11) DEFAULT NULL COMMENT '分类ID', `author_id` int(11) DEFAULT NULL COMMENT '作者ID', `status` tinyint(1) DEFAULT '1' COMMENT '状态 1发布 0草稿', `view_count` int(11) DEFAULT '0' COMMENT '浏览量', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '发布时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_category_create` (`category_id`, `create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;表设计说明:content 用 longtext 而不是 varchar,因为博客正文长度可能超过 65535 字节,longtext 可以存到 4GB。category_id 和 author_id 不建物理外键,只保留普通索引,这是不少 SSM 毕设项目的常见选择——逻辑外键在应用层保证一致性,物理外键在批量插入和删除时容易成为锁和性能瓶颈。论文里能说清楚这个取舍,反而是加分项。
分类表和评论表字段更少,但评论表有一个容易忽略的字段 parent_id:
CREATE TABLE `blog_category` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `description` varchar(200) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `blog_comment` ( `id` int(11) NOT NULL AUTO_INCREMENT, `article_id` int(11) NOT NULL COMMENT '文章ID', `user_id` int(11) NOT NULL COMMENT '评论用户ID', `content` varchar(1000) NOT NULL, `parent_id` int(11) DEFAULT '0' COMMENT '父评论ID,0为顶级评论', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_article_id` (`article_id`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;3.2 表关系、索引与 SQL 导入
关系要点可以概括成四条:文章和分类是多对一,一篇文章属于一个分类;用户和文章是一对多,一个作者可以写多篇文章;文章和评论是一对多,一条评论只属于一篇文章;文章和标签是多对多,通过 blog_article_tag 中间表关联。
索引设计遵循三条原则就够了:查询条件里的字段加索引,比如 username、article_id;排序字段加索引,比如 create_time;索引不是越多越好,单表控制在五个以内,否则插入和更新会变慢。
初始化数据导入用命令行重定向是最稳的方式:
mysql -u root -p -e "create database blog default charset utf8mb4;" mysql -u root -p blog < /path/to/blog.sql第一次导入最容易出现 unknown database 'blog',原因是你还没建库就导表。上面两条命令先建库再导表,顺序反了大方向必错。
3.3 初始化数据的三个检查步骤
导入完成后不要急着启动项目,先做三轮检查:
use blog; show tables; select id, username, password from blog_user;检查点有三个:show tables 能看到至少 blog_article、blog_user、blog_category、blog_comment、blog_tag 这几张表;blog_user 里存在管理员账号,通常 username 是 admin;密码字段不是明文而是 32 位 MD5 密文。如果密码是明文,这个项目的前后端拦截就有安全隐患,论文里要写明这是演示用法。
4. 用 IDEA 把源码跑起来:从解压到浏览器出现首页
拿到压缩包后,大多数人最先做的事不是读代码,而是跑通。跑不通的原因很多时候不是代码问题,而是环境问题。下面这条路径覆盖面比较广,JDK、Maven、Tomcat、MySQL 四个环节逐个对齐。
4.1 环境清单与版本匹配
SSM 博客系统这一类项目,最稳的组合是 JDK 1.8、Maven 3.6.x、Tomcat 8.5、MySQL 5.7。MySQL 8.0 也能跑,但驱动和时区要多配两步。
| 软件 | 推荐版本 | 注意事项 |
|---|---|---|
| JDK | 1.8 | 不要用 11 或 17,老 SSM 项目对高版本兼容性差 |
| Maven | 3.6.x | 3.9 以上部分仓库证书会报错,必要时换镜像源 |
| Tomcat | 8.5.x | 7.x 对 JDK 8 支持不够;9.x 也可以但没必要升级 |
| MySQL | 5.7 | 8.0 需要改驱动类并配置 serverTimezone |
| IDEA | 2019+ | Community 版也能跑,主要是 Maven 和 Tomcat 插件 |
4.2 修改数据库连接配置:定位 jdbc.properties
解压后先找配置文件,常见位置是 src/main/resources/jdbc.properties,也有项目写在 spring-datasource.xml 里。不管在哪,核心就是三个参数:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf8&useSSL=false jdbc.username=root jdbc.password=123456需要改的是三处:3306 后面的数据库名;用户名;密码。如果是 MySQL 8.0,驱动类和连接串要一起改:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456注意 properties 文件里的 & 不需要写成 &,这是 properties 和 XML 的一个区别;如果配置写在 XML 的 bean 里,就必须用 & 转义。很多人在复制粘贴配置时报错,问题往往出在这个符号上。
4.3 用 IDEA 部署到 Tomcat:war exploded 最省事
IDEA 部署 SSM 项目的标准流程是 Maven 导入加 Tomcat 容器。具体步骤分五步:用 IDEA 的 Import Project 选择解压目录下的 pom.xml,等待依赖下载;Run → Edit Configurations,新增 Tomcat Server → Local;Deployment 页签到 “+” 添加 Artifact,选择 war exploded 而不是 war;Server 页确认 Application context 和项目访问路径一致,一般为 /;启动前再确认 Maven 的 settings.xml 配置了可用镜像源,否则依赖下载不下来会报一片红叉。
war exploded 和 war 的区别,在这类老项目上很实际:exploded 是解压目录直接作为 Web 资源,修改 JSP 后热部署速度更快,省去每次重新打包 war 的等待时间。调试阶段用 exploded,最终交付用 war,这是常规做法。
4.4 启动后的验证清单
启动成功后,浏览器按下面顺序验证:前台首页 http://localhost:8080/blog/ 能显示文章列表;点进任意文章详情,标题和正文不转码;后台地址通常是 /blog/admin 或 /blog/admin/login,用 SQL 里初始化的管理员账号登录;登录后发布一篇带中文标题的测试文章,回前台首页看是否按时间倒序展示。
如果列表打不开,优先看 IDEA 控制台有没有 Mapper 相关报错;如果 JSP 能打开但样式全乱,跳到下一章的静态资源排查。
5. SSM 博客系统避坑指南:5 个高频翻车现场与排查思路
下面这 5 条是我接触这类项目时遇到频率最高的,覆盖环境、框架、数据库三个层面。每一条都是现象、原因、解决,可以当排查手册直接对照。
5.1 启动报 ClassNotFoundException: com.mysql.jdbc.Driver
现象:Tomcat 启动时控制台抛出 java.lang.ClassNotFoundException: com.mysql.jdbc.Driver,应用无法访问数据库。
原因:项目里没有 MySQL 驱动 jar,或者 Maven 没把依赖打到 war 包的 WEB-INF/lib 下。常见于驱动依赖的 scope 被标成 test 或 provided,编译时能找到,运行时找不到。
解决:打开 pom.xml,确认 mysql-connector-java 的依赖 scope 是 compile,然后 Build → Rebuild Project,部署时勾选 Build Artifact。MySQL 8.0 下还有一个高频变形:代码里写的是 com.mysql.jdbc.Driver,8.0 的驱动类已经改名 com.mysql.cj.jdbc.Driver,两个类名要跟驱动版本对齐,混搭必翻车。
5.2 数据库中文乱码:插入正常,查询变问号
现象:后台发布中文标题,首页显示 ??,去数据库里直接 select 也是 ??。
原因:字符集从客户端到表到连接串三层中至少有一层不是 utf8mb4。最常见组合是 jdbc.url 缺 characterEncoding=utf8,或者建表时 MySQL 默认用了 latin1。
解决:先把表改回 utf8mb4,ALTER TABLE blog_article CONVERT TO CHARACTER SET utf8mb4;,再确认连接串带了 useUnicode=true&characterEncoding=utf8。如果是 Spring 容器里配置的数据源,注意 & 要写成 &。
5.3 静态资源 404:页面能到 JSP,但 css/js 全挂
现象:浏览器能打开登录页,但整页没有样式,F12 看到 .css、.js 请求全是 404。
原因:web.xml 里 DispatcherServlet 映射成 /,把静态资源请求也拦截了,SpringMVC 找不到对应的 Handler。这是 SSM 项目最经典的配置坑。
解决:在 spring-mvc.xml 里同时配置两行,缺一不可:
<mvc:default-servlet-handler/> <mvc:annotation-driven/>default-servlet-handler 把没有对应 Handler 的请求交回容器默认 Servlet 处理,annotation-driven 保证 Controller 的注解还能正常解析。只配其中一个,要么静态资源好了但接口全挂,要么接口好了静态资源继续 404。
5.4 PageHelper 分页失效:调了 startPage 却返回全部数据
现象:调用 PageHelper.startPage(2, 5) 后,列表还是把所有文章都查出来了,执行 SQL 里也没有 LIMIT。
原因:startPage 和实际执行的 select 之间隔着别的逻辑;或者项目里同时存在多个 PageHelper / 不同版本的 MyBatis,SQL 拦截器没生效。
解决:代码检查 startPage 必须紧贴 selectArticleList 调用,中间不要插任何其他 Mapper 操作。pom.xml 里确认只有一份 pagehelper 依赖,推荐 com.github.pagehelper 的 5.x 版本配 MyBatis 3.4.x。定位时打开 MyBatis 的日志,如果打印出来的 SQL 没有 LIMIT,多半是 interceptor 没注册进 sqlSessionFactory。
5.5 Tomcat 8080 端口被占用
现象:启动时提示 Port 8080 is already in use,Tomcat 直接启动失败。
原因:之前启动的 Tomcat 没有正常停止,或其它进程占用了 8080。这类老项目在 IDEA 里反复 Run/Restart 很容易残留进程。
解决:命令行执行 netstat -ano | findstr 8080,拿到占用端口的 PID 后 taskkill /F /PID 加 PID。不建议为了省事随意改 Tomcat 端口,因为后端代码里如果写死了 callback 地址,改端口会引入新的不一致问题。
6. 把源码变成高分毕业设计:论文结构与答辩验证清单
跑通只是第一步,毕业设计的重头戏是把代码讲成论文。SSM 博客系统虽然功能不复杂,但胜在每一层都有东西可写,关键看你有没有把源码里的设计决策翻译成论文语言。
6.1 论文目录与每一章怎么写
| 论文章节 | 内容要点 | 与源码对应 |
|---|---|---|
| 绪论 | 选题背景、博客系统研究现状、本人主要工作 | 不要贴代码,写你用到的技术栈和解决的问题 |
| 需求分析 | 用例图、功能需求、非功能需求 | 从 admin 后台的菜单反推用例,别凭空编 |
| 概要设计 | 系统分层架构、模块划分、数据库 E-R 图 | 画清楚 Spring/SpringMVC/MyBatis 三层数据流 |
| 详细设计 | 核心模块流程、类图、数据库表结构 | 挑文章发布、评论、分页三个点展开 |
| 系统测试 | 功能测试用例、测试结果、典型问题修复 | 对应第 5 章的避坑记录就是现成素材 |
写论文最常见的错误是“概念抄一堆,设计全靠编”。照源码的实际结构去写,比如 Controller 里哪个方法做了参数校验、MyBatis 哪个查询用了 LEFT JOIN,这些细节才是答辩老师想看到的真实工作量。
6.2 答辩前必须做的一轮自测:功能验证与截图存档
答辩现场演示翻车率最高的五个操作:登录时密码错误有没有提示;发布文章后首页能否按时间倒序看到;修改和删除文章后数据是否一致;评论提交后能否立即显示;分类筛选是否正常。每个操作提前截图保存,按顺序插到论文测试章节,现场即使网络或数据库出问题,截图也能撑住场面。
6.3 一个加分改造:把评论改成异步提交
如果想让项目在答辩时有亮点,把评论改成异步是一个既安全又可控的改造。前端用 jQuery 提交 JSON,后端返回 JSON,而不是整页刷新。前端代码:
$('#btnComment').click(function () { $.ajax({ url: '/comment/add', type: 'POST', data: { articleId: $('#articleId').val(), content: $('#commentContent').val() }, dataType: 'json', success: function (res) { if (res.code === 200) { // 把返回的评论节点追加到列表末尾,无需刷新页面 $('#commentList').append(buildCommentHtml(res.data)); } else { alert(res.message); } } }); });这里真正要讲的不是 jQuery,而是前后端交互方式的变化:Controller 用 @ResponseBody 返回 JSON,页面通过局部更新呈现新评论。答辩现场能讲出“页面不刷新、数据局部更新”这个点,比堆技术名词更能体现你没白做这个项目。
这次带一个学生改这个项目时,他把数据库密码直接写在代码里,我说你先别急着交,把配置外置、把接口异常处理补上再答辩。他问这样改是不是更容易被问倒。其实恰恰相反——真正让你翻车的不是没做高级功能,是被问到“为什么这么配”时答不出来。跑通一个 SSM 博客系统只是起点,能对着数据流把每一层讲清楚,才是这套毕业设计真正的价值。希望帮到你。
本文还有配套的精品资源,点击获取