简介:一份基于JSP、Servlet和MySQL的博客系统网站源码,适合Web开发初学者,以及需要快速搭建博客平台的开发者。这份代码完整实现了用户、文章、评论、搜索、权限等核心模块,覆盖用户注册登录、文章发布编辑删除、评论发表回复、关键词搜索、管理员权限分配等典型业务场景。资源包共327个文件,压缩后仅2.43MB,主要包含49个jsp页面、52个java源码和52个class文件,以及js、css前端样式、gif操作演示、sql建表脚本等,目录结构清楚,便于按模块检索。目前已有1160人学习下载。通过学习源码,可以深入理解JSP在页面展示中的作用、Servlet处理HTTP请求的完整生命周期、JDBC连接与操作MySQL的方法,掌握从数据库设计到Web交互的实现思路。也可直接部署到Tomcat运行,作为课程设计、毕业设计或Java Web实训项目的参考基础,后续可自行扩展Spring等框架进行重构。
1. 为什么到今天还有人下载 JSP+Servlet+MySQL 的博客系统源码
看到这个标题,很多人的第一反应是“都什么年代了还在用 JSP”。但翻一下搜索引擎的下载量,这类 JSP+Servlet+MySQL 的博客系统源码包依然是 Java 后端岗位面试前、计算机专业课程设计、以及刚入行想完整看一个 Web 项目的同学下载量最高的资源之一。原因不复杂:这个技术栈虽然老,但它把 HTTP 请求从 Tomcat 接收到 Servlet 分发、再到 JDBC 查询 MySQL、最后在 JSP 里渲染输出,一整条链路都摆在明面上,没有 Spring Boot 帮你把一切包好。你看到的每一个类、每一条 SQL、每一次页面跳转,都是能直接读懂的。
这篇博文不评价这个技术选型是否时髦,而是假设你已经下载到了这个 .zip 源码包,想把它跑起来、读懂它、并且改造成自己能写到简历里的项目。我会按“本地环境与部署 → 数据库设计与初始化 → Servlet 路由与 JSP 渲染 → 部署排错与二次开发”这个顺序讲。涉及的代码片段以这个技术栈最常见的教学项目结构为基准,你拿到的包大概率是类似的组织方式,个别文件名不同不要紧,思路是通用的。
2. 先把 zip 里的项目跑起来:JDK、MYSQL、TOMCAT 的版本匹配
2.1 解压之后先别急着导入 IDE,先看目录结构
常见的 JSP+Servlet+MySQL 博客系统 zip 包解压后,通常不是 Maven 工程,而是传统的 Dynamic Web Project 目录。你大概率会看到类似这样的结构:
blog/ ├── src/ │ ├── com/blog/dao/ // 数据访问层,JDBC 操作 │ ├── com/blog/entity/ // 实体类,对应数据表 │ ├── com/blog/servlet/ // 控制器,继承 HttpServlet │ ├── com/blog/filter/ // 过滤器,登录校验、编码设置 │ ├── com/blog/util/ // DBUtil、分页工具等 │ └── db.properties // 数据库连接配置 ├── WebContent/ │ ├── admin/ // 后台管理页面 │ ├── css/js/images/ // 静态资源 │ ├── article.jsp // 文章详情 │ ├── index.jsp // 首页 │ └── login.jsp ├── sql/ │ └── blog.sql // 建库建表脚本 └── .classpath / .project先找到sql/blog.sql和db.properties这两个文件。前者决定你要在 MySQL 里建什么库,后者决定 JDBC 怎么连上这个库。把这两个文件的内容确认清楚了,整个项目的运行逻辑就明朗了一半。
2.2 JDK 8 和 JDK 11 是安全区,JDK 17+ 要留意 javax 包问题
很多下载这个项目的人电脑上装的是最新 JDK,这是一个最常见的启动失败点。老 JSP+Servlet 项目几乎全部基于javax.servlet包名编写,而 Tomcat 10 及以上版本把包名改成了jakarta.servlet。如果你用 Tomcat 10 去跑一个import javax.servlet.http.HttpServlet的项目,编译直接报“程序包 javax.servlet.http 不存在”。
我一般建议做这类项目时用 JDK 8 或 JDK 11,配 Tomcat 8.5 或 Tomcat 9。版本匹配关系可以参考这张表:
| 组合 | JDK 版本 | Tomcat 版本 | 包名 | 说明 |
|---|---|---|---|---|
| 老项目安全区 | JDK 8 | Tomcat 8.5/9.0 | javax.servlet | 和绝大多数源码包完全匹配 |
| 常用替代 | JDK 11 | Tomcat 9.0 | javax.servlet | JDK 11 同样兼容 |
| 高版本尝试 | JDK 17 | Tomcat 9.0 | javax.servlet | Tomcat 9 支持 JDK 17,但个别老代码可能报反射错误 |
| 新版本坑 | JDK 17+ | Tomcat 10.1+ | jakarta.servlet | 直接跑传统源码包会编译失败 |
提示:如果你下载的源码包里没有任何 Maven 或 Gradle 配置文件,说明它依赖的 jar 包都在
WebContent/WEB-INF/lib目录下,比如mysql-connector-java-5.1.49.jar或类似版本。这是传统 Web 项目的标准做法。
2.3 MySQL 安装与初始化:用 zip 免安装版还是 Installer
Windows 上装 MySQL,我习惯用免安装版(zip 压缩包)。原因是不用装成系统服务,随时能换版本,对跑这种课程设计型项目最省事。免安装版的配置步骤大致是:解压后在根目录新建my.ini,指定basedir和datadir,然后执行mysqld --initialize-insecure初始化,再执行mysqld --console启动。初始化之后 root 用户默认没有密码,用mysql -u root就能直接登录。
[mysqld] basedir=D:/tools/mysql-8.0.40-winx64 datadir=D:/tools/mysql-8.0.40-winx64/data port=3306 character-set-server=utf8mb4 [client] default-character-set=utf8mb4这段配置里character-set-server=utf8mb4很关键。博客系统的文章内容必然包含中文,如果数据库默认字符集是 latin1,JSP 页面上会看到一屏问号。utf8mb4比utf8多支持 emoji 和生僻字,也兼容老数据。
2.4 导入 Eclipse 或 IDEA 的步骤
假设你用的是 Eclipse for Enterprise Java,操作路径是:File → Import → General → Existing Projects into Workspace,选择解压后的根目录,勾选Copy projects into workspace。IDEA 的话,在欢迎页选Open,定位到目录,IDEA 识别出 .classpath 文件后会自动按 Web 项目加载。
导入后第一件事不是启动 Tomcat,而是确认两个东西:项目的 Java Compiler level 是否和你本机 JDK 匹配(右键项目 → Properties → Java Compiler);WebContent/WEB-INF/lib下是否有 mysql 驱动 jar。如果 lib 目录为空而代码里用了Class.forName("com.mysql.jdbc.Driver"),那一定是依赖丢失,需要自己补一个mysql-connector-javajar 包。版本上 5.1.49 对 MySQL 5.7 和 8.0 都兼容,8.0.33 则要求驱动类名改为com.mysql.cj.jdbc.Driver,且连接串要追加时区参数。
3. 数据库设计:博客系统源码里的表结构是如何组织的
3.1 打开 sql/blog.sql,看四张核心表
JSP 博客系统的数据库设计大同小异,核心表一般不超过四张:用户表、分类表、文章表、评论表。拿到 sql 文件后,先通读一遍建表语句,理解字段含义比直接执行更重要,因为后面你要改功能时,改的就是这些表。
CREATE DATABASE IF NOT EXISTS blog_db DEFAULT CHARACTER SET utf8mb4; USE blog_db; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, nickname VARCHAR(50), avatar VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE t_category ( id INT PRIMARY KEY AUTO_INCREMENT, category_name VARCHAR(50) NOT NULL ); CREATE TABLE t_article ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT, user_id INT, title VARCHAR(200) NOT NULL, summary VARCHAR(500), content TEXT, view_count INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE t_comment ( id INT PRIMARY KEY AUTO_INCREMENT, article_id INT, user_id INT, content VARCHAR(1000), create_time DATETIME DEFAULT CURRENT_TIMESTAMP );执行方式很简单:在命令行执行mysql -u root -p < sql/blog.sql,或者在 Navicat / MySQL Workbench 里打开这个 sql 文件直接运行。注意blog_db这个库名要和db.properties里的配置一致,不一样就改其中一边。
3.2 为什么很多项目外键用逻辑关联而不是物理外键
细心的同学会发现,这个建表脚本里t_article的category_id和user_id没有声明FOREIGN KEY。这是这种老项目的普遍做法。物理外键在删除分类或用户时会产生约束冲突,而课程设计型的博客系统通常用代码逻辑保证关联完整性,比如删除文章时手动先删评论。对于你来说,这个设计不用改,理解为主。
3.3 核心业务 SQL:列表查询带分页
博客首页的核心查询是文章列表,往往还要关联分类名和作者昵称,一条 SQL 同时查三张表:
SELECT a.id, a.title, a.summary, a.view_count, a.create_time, c.category_name, u.nickname FROM t_article a LEFT JOIN t_category c ON a.category_id = c.id LEFT JOIN t_user u ON a.user_id = u.id ORDER BY a.create_time DESC LIMIT ?, ?;LEFT JOIN保证了即使某篇文章没有分类或作者被删,文章本身依然能显示。分页参数通常由两个 request 参数控制:pageNum(当前页码)和pageSize(每页条数)。对应的查询总数 SQL 是SELECT COUNT(*) FROM t_article,总页数用(totalCount + pageSize - 1) / pageSize计算。
有一点要特别提醒:MySQL 中更新子查询不能直接在同一张表上进行,我见过这个项目里有人写“把文章点击数加一后再查询”的逻辑,直接写了UPDATE t_article SET view_count = view_count + 1 WHERE id = (SELECT id FROM ...),结果报错You can't specify target table for update in FROM clause。正确的做法是先把子查询结果包一层别名,或者干脆分成两步,先查id再UPDATE。
3.4 修改 db.properties 后必须重启 Tomcat
db.properties文件里通常长这样:
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/blog_db?useUnicode=true&characterEncoding=utf8 username=root password=123456注意这里如果密码是空的,就写password=,不要把整行删掉。useUnicode=true&characterEncoding=utf8这段参数的作用是告诉 JDBC 驱动,客户端和数据库之间传输中文时使用 UTF-8,少了它,写入的文章内容很容易变乱码。修改这个文件后,需要重启 Tomcat 才能生效,因为 DBUtil 静态块只在类加载时执行一次。
注意:如果你用的是 MySQL 8.0 以上版本,driver 要改成
com.mysql.cj.jdbc.Driver,同时在 url 末尾加上&serverTimezone=Asia/Shanghai,否则会报The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized之类的时区错误。
4. Servlet 路由和 JSP 渲染:看懂一次请求的前半生
4.1 从 web.xml 开始读代码
使用 Eclipse 导入项目后,先打开WebContent/WEB-INF/web.xml。这个文件是整个应用的“总路由表”。无 Maven 的 JSP 项目里,Servlet 的映射方式通常是这样的:
<servlet> <servlet-name>ArticleServlet</servlet-name> <servlet-class>com.blog.servlet.ArticleServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ArticleServlet</servlet-name> <url-pattern>/article</url-pattern> </servlet-mapping>意思是:所有http://localhost:8080/blog/article的请求,都交给ArticleServlet处理。Servlet 类的doGet和doPost方法里会根据request.getParameter("action")的值二次分发,比如action=detail进详情逻辑,action=list进列表逻辑。想看懂这个项目,按web.xml里的映射清单逐个看 Servlet 就行,不用想着从第一行代码读起。
4.2 写一个最典型的 LoginServlet,看登录流程怎么走
多数源码包中登录逻辑都长得很像:接收用户名密码 → 调 DAO 查数据库 → 比对成功就写 Session → 重定向到后台首页。下面是简化后的核心代码:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); UserDao userDao = new UserDao(); User user = userDao.findByUsernameAndPassword(username, password); if (user != null) { request.getSession().setAttribute("loginUser", user); response.sendRedirect(request.getContextPath() + "/admin/index.jsp"); } else { request.setAttribute("error", "用户名或密码错误"); request.getRequestDispatcher("/login.jsp").forward(request, response); } }这里的两个关键 API 要分清。sendRedirect是重定向,浏览器地址栏会变化,属于第二次请求;forward是服务器内部转发,浏览器地址栏不变,可以往 request 里塞属性(比如上面的error)带到 JSP 页面。两者的选择标准是:登录成功跳转到新页面用重定向,校验失败回显错误信息用转发。
4.3 过滤器:登录校验和统一编码
后台管理页面不能让人直接通过 URL 访问,这个逻辑是用 Filter 实现的。一个典型的 AuthFilter 代码:
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; HttpSession session = request.getSession(false); boolean loggedIn = (session != null && session.getAttribute("loginUser") != null); String path = request.getRequestURI(); if (loggedIn || path.endsWith("/login.jsp") || path.contains("/login")) { chain.doFilter(request, response); } else { response.sendRedirect(request.getContextPath() + "/login.jsp"); } }session = request.getSession(false)的意思是:如果没有会话,不要新建一个,直接返回 null。这里如果用getSession(true),每个未登录用户的请求都会创建一个无意义 Session,浪费服务器内存。另外注意web.xml中 Filter 的<url-pattern>通常配置为/admin/*才能只拦截后台路径。
4.4 JSP 如何拿到 Servlet 传过来的数据
Servlet 处理完数据后,通常会把结果集放到 request 或 session 里,再 forward 给 JSP 渲染。JSP 页面里通过 EL 表达式和 JSTL 标签取值:
<c:forEach items="${articleList}" var="item"> <div class="post-item"> <h2><a href="${pageContext.request.contextPath}/article?action=detail&id=${item.id}">${item.title}</a></h2> <p class="meta">${item.nickname} 发表于 ${item.createTime}</p> <p>${item.summary}</p> </div> </c:forEach>这段代码里的pageContext.request.contextPath是动态获取项目上下文路径,写死了是/blog,如果发布时改了应用名,硬编码路径会立刻 404。使用 JSTL 前,确认WEB-INF/lib下有jstl.jar和standard.jar,否则页面会报错说找不到标签库描述文件。一个有用的排查技巧是:当 JSP 页面上出现预期之外的内容时,找到 Tomcat 的work/Catalina/localhost/blog/org/apache/jsp目录,里面是 JSP 被翻译后的 Java 源文件和 class 文件,直接打开能看见 JSP 中的每一行 HTML 和 Java 代码的对应关系,定位问题比猜快得多。
4.5 常见的 JSP 页面细节:个人信息展示和离开时提示
多数 JSP 博客系统的前台页面里会有一块“个人信息展示页面”,通常放在侧边栏,用来显示当前登录用户的头像、昵称、文章数、评论数。这块数据的来源一般是 Session 中的loginUser对象,页面里直接${loginUser.nickname}即可,不需要再查库。还有一个细节是,部分模板里会有页面离开确认提示,通过 JavaScript 的beforeunload事件实现:
window.onbeforeunload = function() { return "确定要离开当前页面吗?"; };这种代码在文章编辑页是有意义的,防止用户误关页面丢掉草稿;但如果在博客首页也生效,会严重影响浏览体验。如果源码包自带的编辑页面里有这个提示,并且它导致正常的表单提交也弹窗,可以把触发条件收紧,只在表单内容发生变化时才启用监听。
4.6 文章列表分页的完整实现思路
翻看 Servlet 源码时你会发现,分页是这类项目里最有含金量的部分。常见做法是:从 request 中取pageNum参数,给一个默认值 1;调 DAO 的list(pageNum, pageSize)方法,返回List<Article>;再调count()方法拿总记录数,算出总页数;最后把结果放进 request,forward 到index.jsp。列表页底部用一段<c:if>判断是否有上一页/下一页,生成对应的翻页链接。这里务必记得给pageNum做非法值过滤,不然有人手动访问?pageNum=-99会导致 SQL 里的LIMIT出现负值,轻则查询异常,重则暴露 SQL 报错信息给用户。
5. 部署排错三步走:乱码、热部署和 jar 包冲突,还有把博客项目写成简历项目的两个小改进
5.1 SQL 查出的中文正常,JSP 页面却乱码
这类项目的乱码问题按“数据流方向”分成三段排查。第一段是 JSP 页面本身,文件头部必须有<%@ page contentType="text/html;charset=UTF-8" language="java" %>;第二段是 Servlet 接收请求参数,在doPost开头必须执行request.setCharacterEncoding("UTF-8");第三段是数据库连接,db.properties的 url 里要带characterEncoding=utf8。三段都设置正确了,乱码问题几乎不可能出现。如果只有 JSP 页面里的中文静态文字乱码,检查 IDE 的文件编码设置,Eclipse 里右键文件 → Properties → Text file encoding,改成 UTF-8 重新保存即可。
5.2 修改 JSP 不生效:清 work 目录
Tomcat 默认对 JSP 是开发模式,修改后重新访问应该自动重新编译。如果你发现改了 JSP 但页面没变化,通常是 Tomcat 没有把 JSP 翻译后的 class 文件更新。手动到 Tomcat 的work目录删除对应项目的子目录,然后重新访问。IDEA 里如果使用 Artifacts 方式部署,还可能是没有执行Build → Rebuild Project,导致target目录下打的是旧包。排查时可以看看页面显示的远处和源码是否一致,如果差一步,基本就是这个原因。
5.3 修改 Servlet 代码后控制台报 ClassNotFound
如果只是改了 Java 文件,没有重启 Tomcat,运行时会报ClassNotFoundException或各种奇怪的类状态异常。传统 Web 项目不像 Spring Boot 那样有自动化热部署,需要手动重启 Tomcat 才能加载新类。IDEA 里可以配置On frame deactivation为Update classes and resources(编译并把 .class 复制到 target),但对 Servlet 的修改依然建议完整重启。
5.4 二次开发建议:把 JSP 的博客列表接口化
如果这个项目准备用在简历上,老学长给出两条具体的改造路径。第一条是把文章列表改造成能按分类筛选:t_category表里加一个is_deleted字段(0 正常 1 已删),列表页的分类导航链接都拼接上?categoryId=x,Servlet 里多处理一个参数条件,SQL 加一个WHERE a.category_id = ?。第二条是习惯性地在pom.xml都还没有的情况下,自行引入commons-dbutils替换纯手写 JDBC——它能帮你省掉大量ResultSet取值代码,又不会像 MyBatis 那样离 JSP 项目太远。这两步改动不改变整体架构,代码量大约各在半小时内,但面试时能说明白“为什么用 dbutils 而不是完全手写 JDBC”和“分页为什么用 LIMIT 而不一次性查出全部数据”,比项目本身是博客系统更有价值。
5.5 验证部署是否成功的三个接口
启动 Tomcat 后,在浏览器里依次确认以下三个标志:访问http://localhost:8080/blog/index.jsp能看到首页且无数据库异常、访问/blog/login.jsp能打开登录页、后台任意一个列表页未登录时访问会跳回登录页。三个功能验证通过,这个 JSP+Servlet+MySQL 的博客系统就能纳入你自己的调试节奏了。
本文还有配套的精品资源,点击获取