
做Java后端的人或多或少都绕不开SSM这三个字母。Spring、Spring MVC、MyBatis十年前是主流现在依然是很多中小型公司线上项目的基础框架也是面试里出现频率最高的组合。如果你想找一个能打通“前端页面 后端接口 数据库设计”完整链条的项目来练手个人博客系统是我最推荐的一种形态代码量不大但该有的web开发环节一个都不少。我最近把一套Java SSM架构的个人博客系统源码完整跑通了这套项目自带MySQL数据库脚本、运行教程和一份万字设计文档算是一站式交付的那种适合课程设计、毕业设计也适合准备Java实习面试的时候拿来补项目经验。下面从项目结构、框架协作、数据库设计、本地部署到二次开发把整条链路拆开讲一遍你照着走基本不会卡壳。1. 为什么“SSM博客系统”能成为Java学习者的经典项目1.1 这个项目到底适合谁说实话Java Web方向的项目五花八门网上源码一抓一大把但很多新手项目要么太简单只有增删改查的crud demo要么太复杂一上来就是分布式微服务连依赖都装不明白。个人博客系统刚好卡在中间是一个“麻雀虽小、五脏俱全”的经典形态。适合的人群大概有三类。第一类是刚学完Java SE、数据库基础想做一个完整Web项目的学习者。博客系统的功能足够清晰——写文章、看文章、分类、标签、评论、登录每一块都能对应到具体的后端知识点而且你完全可以自己复述出每一行代码的作用这对学习阶段来说非常重要。很多人学完Java基础后不知道下一步干什么其实把这样一个系统吃透比盲目追新技术靠谱得多。第二类是正在准备Java实习或校招面试的同学。面试官问项目经验的时候SSM博客系统是一个非常好讲的题目因为它的技术栈足够经典Spring的IoC和事务管理、Spring MVC的请求流转、MyBatis的Mapper映射每一个都能展开聊。而且它不像微服务项目那样一听就知道是培训班的博客系统这种“小而完整”的项目反而更容易讲出深度。第三类是课程设计、毕业设计需要交一个能跑、有文档、有数据库设计的作品。这套源码自带的万字文档能省掉大量整理说明的精力你只需要在跑通它的基础上结合自己的理解做一些功能修改就能变成一个有模有样的作品。1.2 一个博客系统覆盖了哪些核心技能我拿到这套源码之后做的第一件事就是对照着把技术点清单列出来方便后面学习或者面试的时候查漏补缺。这个项目虽然不大但覆盖的面其实挺全的Web层Spring MVC的Controller、拦截器、参数绑定、JSON数据返回业务层Service接口与实现拆分、事务控制、业务逻辑封装持久层MyBatis的Mapper接口与XML编写、动态SQL、结果集映射数据库设计用户表、文章表、分类表、标签表、评论表以及它们之间的关联关系前端JSP页面、Bootstrap样式、jQuery请求、富文本编辑器工程化Maven依赖管理、配置文件分离、日志输出、资源文件处理可以说这个项目做一遍你对一个“传统Java Web项目从开发到部署”的全部环节都会有直观感知。很多人在学校只写过页面和接口但页面和接口之间到底是怎么串起来的对这个项目的完整链路没概念。跑通这套项目之后再去学Spring Boot会轻松很多因为Spring Boot只是把SSM的繁琐配置自动化了底层的组件和设计思路完全是一脉相承。1.3 我对这类源码项目的使用建议每次分享这类源码项目的时候我都要多说一句源码可以拿但不要原封不动地拿来交作业。我看到很多人的习惯是源码导入IDEA启动完看到页面出来就觉得自己已经会了然后去睡觉。这样其实浪费了一大半价值。我的习惯是做三件事。一是跑通之后从数据库表开始把核心表的所有字段都弄明白知道为什么要有这张表、这个字段为什么这么设计。二是把文章发布流程的代码从头到尾走读一遍搞清楚一个请求从前端页面发出后经过了哪些类、哪些方法、最终写到数据库的哪个表。三是至少动手改一个功能比如改一下首页的文章列表样式或者给评论增加回复功能哪怕改得很粗糙这个过程也比单纯跑通项目有价值得多。这套源码里的文档部分对前两步帮助很大设计文档里把需求分析、E-R图、表结构、核心流程都写清楚了你跟着文档走读代码效率比自己瞎翻高很多。2. 交付物盘点源码、数据库脚本、运行教程、万字文档各怎么用2.1 源码目录结构和模块划分一个标准的Maven项目中src/main/java、src/main/resources、src/main/webapp这三块是核心这套源码也没有脱离这个经典结构但优秀的项目会在包名上做得足够清晰方便后人阅读。典型的结构大致是这样src/main/java ├── com.blog │ ├── controller # 控制层接收前端请求 │ ├── service # 业务层接口 │ ├── service.impl # 业务层实现 │ ├── mapper # MyBatis的Mapper接口也叫DAO层 │ ├── pojo # 实体类对应数据库表 │ ├── interceptor # 拦截器用于登录权限控制 │ └── util # 工具类 src/main/resources ├── mapper # MyBatis的XML映射文件 ├── spring # Spring相关配置文件 ├── db.properties # 数据库连接配置 └── mybatis-config.xml # MyBatis全局配置 src/main/webapp ├── static # CSS、JS、图片等静态资源 ├── WEB-INF │ ├── views # JSP视图页面 │ └── web.xml # Web应用部署描述符看到这个结构你大概就能判断这套源码的“底子”如何。包名规范、分层清晰的后面改起来就顺如果所有类都堆在一个包里大概率维护性很差你也就不要指望文档能有多完整了。这套项目从包结构上看是合格的controller、service、mapper三层分离得很干净。2.2 数据库脚本的重要性与导入前提这套源码附带一个完整数据库脚本一般是 .sql 文件里面通常包含建库语句、建表语句和初始化数据。很多新手以为导入数据库就是把文件拖进去其实这里面有几个关键点处理不好项目根本跑不起来。第一确认MySQL版本。如果你用的是MySQL 8.0而脚本里的建表语句是MySQL 5.7时代写的需要注意字符集设置和密码加密方式。比如有些老脚本用的认证插件是 mysql_native_password新版MySQL 8默认是 caching_sha2_passwordJDBC连接时可能报认证插件错误。第二注意编码问题。导入之前最好在命令行执行 source 命令或者在Navicat里选择脚本文件时明确指定UTF-8编码否则表数据里出现中文后页面显示全是问号那就是导入时字符集没对。我见过太多人项目一启动首页文章标题全是乱码查了半天最后发现是导入SQL时编码没选对。第三脚本里可能有外键依赖导入时如果报错先看是不是外键顺序问题。博客系统里博客表依赖用户表标签关联表依赖博客表和标签表导入顺序必须由外向内。如果你用的是可视化工具导入通常会自动处理顺序但使用命令行source导入时就要自己注意。2.3 运行教程和万字文档的使用顺序拿到这套源码里面通常会有两个文档一个是运行教程README或者运行说明.md另一个是上万字的设计文档。这两个文件的用法完全不同别搞混了。运行教程是给你“应急”的照着它一步步配置环境、导入数据库、启动项目目的是先让项目跑起来。设计文档是给你“理解”的它记录了项目的背景、需求分析、数据库设计、模块设计、部分核心代码说明适合在项目跑通之后通读一遍。我个人的顺序是先按运行教程把环境配好、把项目跑起来再打开设计文档看数据库设计和模块说明最后对照着源码走读核心代码。不要一拿到就往代码里翻先跑起来再看文档这样你对项目的理解才是立体的。如果运行教程写得足够详细你应该能在半小时内跑起来如果卡住了多数是环境版本问题后面第5章我会专门说。3. SSM真正协作起来的方式三个框架各管什么、怎么串联3.1 SpringIoC容器如何管理Blog的各个BeanSSM里的第一个S是Spring。这套博客系统里Spring主要负责两件事IoC容器管理和事务管理。IoC容器用大白话说就是你不自己 new 对象了而是在配置文件或注解里声明“这个类需要被管理”Spring启动时会把这些对象创建好并组装起来。在博客系统里控制器需要调用ServiceService需要调用Mapper这些对象都不直接在代码里 new而是通过注解注入。Autowired private BlogService blogService;这行代码就是典型的依赖注入。Spring容器在启动时发现 BlogController 需要一个 BlogService 类型的对象就会从容器里找到已经创建好的实现类注入进来。如果这一段理解不了建议先去补一下Spring的Bean生命周期因为SSM项目后续所有代码都建立在这个基础上。事务管理也是Spring的强项。博客发布这个操作涉及到写文章表、写标签关联表如果第二句SQL执行失败了第一句已经插入的数据就会变成脏数据。Spring通过 Transactional 注解或者XML里配置事务管理器保证这个操作要么全部成功要么全部回滚。3.2 Spring MVC从前端请求到Controller的完整链路第二个S是Spring MVC负责处理HTTP请求。前端页面点了“发布文章”按钮浏览器发起一个POST请求Spring MVC会按照一条固定的链路处理。请求先到达 DispatcherServlet这是整个Spring MVC的前置控制器所有请求都要经过它DispatcherServlet 根据URL找到对应的 Controller 方法这个过程叫 HandlerMappingController 方法被调用并且Spring MVC会自动帮我们把表单参数绑定成实体对象Controller 返回视图名或者用 ResponseBody 返回JSON数据经过视图解析器ViewResolver找到对应的JSP页面再把响应写回浏览器。这套博客系统里大多数页面渲染走的是JSP比如 /blog/list 返回的是带文章列表的视图而后台管理部分接口返回JSON比如删除文章后前端用Ajax刷新列表。理解这条链路你在排查“为什么请求没进入到Controller”这类问题时就会很从容——先看拦截器是否拦截了、再看URL是否匹配了Controller的RequestMapping。3.3 MyBatisSQL与Java方法的映射思路MyBatis是SSM里的持久层框架核心作用是把Java方法和SQL语句对应起来。博客系统里你不需要写传统的JDBC代码只需要在Mapper接口里声明方法在XML里写SQLMyBatis负责把参数传进去、把结果集映射成Java对象。public interface BlogMapper { ListBlog selectBlogList(Param(page) int page, Param(rows) int rows); }select idselectBlogList resultTypecom.blog.pojo.Blog SELECT * FROM t_blog ORDER BY create_time DESC LIMIT #{page}, #{rows} /select这里最关键的是 resultType 和 resultMap。如果数据库字段是 create_time而Java实体类字段是 createTimeMyBatis默认的自动映射不一定会处理这种下划线和驼峰的转换需要在 mybatis-config.xml 里开启驼峰映射setting namemapUnderscoreToCamelCase valuetrue/这一行配置能省掉很多手动定义resultMap的麻烦。很多新手拿到这个项目发现文章时间字段查出来是null多半就是忘了配这个。类似的坑后面第5章、第7章还会遇到先标记一下。4. 从数据库脚本反推业务博客系统的表到底怎么设计4.1 核心表用户、文章、分类、标签、评论博客系统的数据模型不复杂但设计得规范与否直接影响后面写业务代码的难度。这套源码里核心表一般有以下这些。用户表t_user主键ID、用户名、密码一般存MD5或加盐后的密文、昵称、头像、角色类型。管理员和普通用户的区分通常靠role字段。这个表要注意的就是密码字段不要用明文哪怕培训项目里全是明文你自己做项目的时候也至少要用MD5加盐。文章表t_blog主键ID、标题、正文内容、摘要、首图地址、分类ID、用户ID、发布时间、更新时间、浏览量、点赞数、是否发布状态。这里有几个字段的设计需要理解正文内容一般用 TEXT 或 LONGTEXT因为博客正文很长用 VARCHAR 肯定不行是否发布状态用 tinyint0表示草稿1表示已发布这是列表页查询的过滤条件浏览量、点赞数用 int每次访问时做 1 操作简单项目直接 UPDATE 就行不需要引入计数服务。分类表t_type主键、分类名称。分类和博客的关系是一对多一篇文章只能属于一个分类一个分类下可以有多篇文章。标签表t_tag主键、标签名称。标签和博客的关系是多对多一篇文章可以有多个标签一个标签也能对应多篇文章所以需要一张中间表 t_blog_tags 来维护。评论表t_comment主键、文章ID、评论者昵称、评论内容、评论时间、父评论ID。父评论ID这个字段是用来做楼中楼回复的如果为null表示是顶级评论如果不为null则表示回复某条评论。4.2 表关联查询的设计思路表设计出来之后关键是要搞清楚每个查询要连哪些表。我列几个实际会用到的高频查询。首页文章列表要展示每篇文章的分类名和标签。文章表里有分类ID所以需要 JOIN t_type 表取分类名标签要通过中间表再JOIN t_tag表而且要考虑到一篇文章多个标签怎么拼接成字符串MySQL可以用 GROUP_CONCAT 来实现。文章详情页根据文章ID查出文章本身同时关联出作者昵称、分类名、标签列表、评论列表。评论列表在早期版本里是单独查询再放到request作用域的你也可以写一个联合查询。分类页和标签页本质上都是先查出分类或标签对应的文章ID集合再按文章ID回查文章信息。这里要注意SQL的查询顺序先关联中间表再关联文章表避免直接对三张表做笛卡尔积式的大查询。4.3 初始化数据与常见字段陷阱数据库脚本里除了表结构通常还会初始化一个管理员账号和几条示例文章。示例文章的作用是让项目一跑起来就能看到页面效果不至于首页空白一片。但要注意初始化的管理员密码往往是明文或者固定加密值如果你要部署到公网上线前一定要改掉。这里我想特别提醒几个字段相关的坑都是我实际遇到过的问题。一是数据库表名和字段名尽量别用保留字。我之前用过一个项目字段名叫 desc结果每次查询都要加反引号非常痛苦。这套博客系统里如果看到 status 这类字段通常没问题但如果出现 order、desc、user 这类一定要小心写SQL时记得加反引号。二是字符集。建表语句里最好统一CHARSETutf8mb4因为utf8mb4支持emoji和特殊字符博客正文里出现特殊符号很常见。如果表是utf8写进去直接报错或者显示成问号这算是技术债的典型。三是时间字段。建议用 datetime 类型存具体时间不要用 time 或时间戳因为前端展示的时候要格式化用datetime会方便很多。而且查询的时候如果要按时间排序datetime加索引效果更好。5. 从0到1跑起来的完整步骤环境、导入、改配置、启动验证5.1 环境版本搭配建议运行这套源码之前先把环境准备好。个人建议的版本组合如下组件推荐版本说明JDK8 或 11SSM项目最稳的是JDK 811也能跑Maven3.6.x 或 3.8.x版本别太新3.9可能有兼容问题Tomcat8.5 或 9.0和Servlet版本匹配Tomcat 10不支持MySQL5.7 或 8.0两种版本注意JDBC驱动的区别IDEA2021及以上社区版也可以跑只是没有Tomcat集成插件需要手动配置这个组合是经历了很多项目验证的不建议一上来就追新。比如Tomcat 10因为Jakarta EE的命名空间变更很多老SSM项目的 javax.servlet 依赖会直接报ClassNotFoundException这是最典型的版本不兼容坑。如果你本机装的是Tomcat 10建议再装一个Tomcat 9老项目真的离不开。5.2 IDEA导入项目和配置Tomcat用IDEA导入Maven项目的步骤不难打开IDEA选择 File - Open选中项目根目录下的 pom.xmlIDEA会识别出这是Maven项目然后开始下载依赖。依赖下载是整个过程中最容易卡住的一步。默认的Maven中央仓库在国内下载速度很慢甚至超时。解决问题的办法是在 Maven 的 settings.xml 里配置阿里云镜像这个步骤网上教程很多但很多人会忽略。mirror idaliyunmaven/id mirrorOf*/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror依赖下载完成后配置TomcatRun - Edit Configurations - 加号 - Tomcat Server - Local然后选择本地的Tomcat路径在 Deployment 标签页添加 Artifact一般选择 war exploded 模式。exploded模式不需要每次修改代码后重新打war包开发调试效率高很多。5.3 数据库导入与配置文件修改数据库导入推荐用Navicat或者IDEA自带的Database面板。先创建一个数据库名字按脚本里的建库语句来然后执行SQL脚本。执行完以后检查一下表是否都建出来了初始化数据是否都进去了。然后打开源码里的 db.properties 文件把数据库连接信息改成你自己本机的配置。这里有三个地方不能错数据库地址、用户名、密码。URL部分特别注意jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/blog?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.password123456如果你用的是MySQL 5.7及以下版本driver那里写成 com.mysql.jdbc.Driver 也认如果是MySQL 8.0必须用 com.mysql.cj.jdbc.Driver并且URL里要带 serverTimezone 参数否则会报时区错误。这一步非常经典每次项目出问题十有八九都是这个文件没改对。5.4 启动后的验证清单一切配置好之后启动Tomcat浏览器访问 http://localhost:8080/blog具体端口和context path看配置。为了确认项目真的跑通了我建议按这个清单顺序检查首页是否能正常打开文章列表、分类、标签区域有没有数据随便点进一篇文章看详情页内容和评论区域是否正常尝试后台登录用初始化管理员账号进入后台管理页面发一篇文章写点中文内容看发布后首页是否出现给文章加个评论刷新页面看评论是否显示。如果这五步都正常说明项目核心链路没有问题可以进入代码走读阶段了。如果哪一步出了问题不要慌先用第7章的速查表对照一下大部分问题都能找到方向。6. 核心代码走读发布一篇博客请求到底经历了什么6.1 发布文章的后端处理流程理解了框架协作之后我建议从“发布文章”这个功能入手做整条链路的走读因为它串起了表单、Controller、Service、Mapper、数据库多张表。用户在后端管理界面填写文章标题、分类、标签和正文点击发布。浏览器会把表单数据POST到类似 /admin/blog/save 的地址。Controller方法接收到参数后做几件事把表单参数封装成Blog对象调用 BlogService 的 saveBlog 方法在Service实现里先往 t_blog 表插入一条文章记录拿到自增主键再处理标签把标签字符串拆开逐个去 t_tag 表查找或插入标签然后往 t_blog_tags 中间表插入关联记录最后返回结果给ControllerController再重定向到文章列表页。这段流程里有几个细节值得注意。一是标签的“查找或插入”需要先按标签名查一遍不存在才insert存在就直接拿ID用否则标签表会存大量重复记录。二是整个操作要加事务因为 t_blog 插入和关联表插入必须保证原子性比如博文插进去了标签关联插入失败那这篇博文就是一个没有标签的孤儿数据。三是在保存博客之前Controller层和Service层都要做必要的参数校验至少标题和正文不能为空否则数据库里会存进一堆空数据。6.2 首页文章列表的分页与分类筛选首页的文章列表不可能一次性把数据库里所有文章都查出来所以必须分页。这套源码里一般有两种做法如果用了PageHelper那么原理很简单PageHelper.startPage(pageNum, pageSize) 后面接上正常的Mapper查询语句它会自动拦截SQL并生成带LIMIT的查询同时把总数和页数信息封装到 PageInfo 里。如果是手写LIMIT那么就需要自己计算 offsetint offset (pageNum - 1) * pageSize; ListBlog blogList blogMapper.selectPageList(offset, pageSize);前端分页组件拿到总页数后渲染页码。这里最容易忽略的是“分类筛选 分页”的组合查询点击某个分类列表要展示该分类下的文章并按页分。SQL里就要带上条件和LIM