JSP+Servlet+MySQL博客系统源码部署与改造指南
简介一份基于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. 为什么到今天还有人下载 JSPServletMySQL 的博客系统源码看到这个标题很多人的第一反应是“都什么年代了还在用 JSP”。但翻一下搜索引擎的下载量这类 JSPServletMySQL 的博客系统源码包依然是 Java 后端岗位面试前、计算机专业课程设计、以及刚入行想完整看一个 Web 项目的同学下载量最高的资源之一。原因不复杂这个技术栈虽然老但它把 HTTP 请求从 Tomcat 接收到 Servlet 分发、再到 JDBC 查询 MySQL、最后在 JSP 里渲染输出一整条链路都摆在明面上没有 Spring Boot 帮你把一切包好。你看到的每一个类、每一条 SQL、每一次页面跳转都是能直接读懂的。这篇博文不评价这个技术选型是否时髦而是假设你已经下载到了这个 .zip 源码包想把它跑起来、读懂它、并且改造成自己能写到简历里的项目。我会按“本地环境与部署 → 数据库设计与初始化 → Servlet 路由与 JSP 渲染 → 部署排错与二次开发”这个顺序讲。涉及的代码片段以这个技术栈最常见的教学项目结构为基准你拿到的包大概率是类似的组织方式个别文件名不同不要紧思路是通用的。2. 先把 zip 里的项目跑起来JDK、MYSQL、TOMCAT 的版本匹配2.1 解压之后先别急着导入 IDE先看目录结构常见的 JSPServletMySQL 博客系统 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这是一个最常见的启动失败点。老 JSPServlet 项目几乎全部基于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 8Tomcat 8.5/9.0javax.servlet和绝大多数源码包完全匹配常用替代JDK 11Tomcat 9.0javax.servletJDK 11 同样兼容高版本尝试JDK 17Tomcat 9.0javax.servletTomcat 9 支持 JDK 17但个别老代码可能报反射错误新版本坑JDK 17Tomcat 10.1jakarta.servlet直接跑传统源码包会编译失败提示如果你下载的源码包里没有任何 Maven 或 Gradle 配置文件说明它依赖的 jar 包都在WebContent/WEB-INF/lib目录下比如mysql-connector-java-5.1.49.jar或类似版本。这是传统 Web 项目的标准做法。2.3 MySQL 安装与初始化用 zip 免安装版还是 InstallerWindows 上装 MySQL我习惯用免安装版zip 压缩包。原因是不用装成系统服务随时能换版本对跑这种课程设计型项目最省事。免安装版的配置步骤大致是解压后在根目录新建my.ini指定basedir和datadir然后执行mysqld --initialize-insecure初始化再执行mysqld --console启动。初始化之后 root 用户默认没有密码用mysql -u root就能直接登录。[mysqld] basedirD:/tools/mysql-8.0.40-winx64 datadirD:/tools/mysql-8.0.40-winx64/data port3306 character-set-serverutf8mb4 [client] default-character-setutf8mb4这段配置里character-set-serverutf8mb4很关键。博客系统的文章内容必然包含中文如果数据库默认字符集是 latin1JSP 页面上会看到一屏问号。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 CompilerWebContent/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 cant specify target table for update in FROM clause。正确的做法是先把子查询结果包一层别名或者干脆分成两步先查id再UPDATE。3.4 修改 db.properties 后必须重启 Tomcatdb.properties文件里通常长这样drivercom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/blog_db?useUnicodetruecharacterEncodingutf8 usernameroot password123456注意这里如果密码是空的就写password不要把整行删掉。useUnicodetruecharacterEncodingutf8这段参数的作用是告诉 JDBC 驱动客户端和数据库之间传输中文时使用 UTF-8少了它写入的文章内容很容易变乱码。修改这个文件后需要重启 Tomcat 才能生效因为 DBUtil 静态块只在类加载时执行一次。注意如果你用的是 MySQL 8.0 以上版本driver 要改成com.mysql.cj.jdbc.Driver同时在 url 末尾加上serverTimezoneAsia/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-nameArticleServlet/servlet-name servlet-classcom.blog.servlet.ArticleServlet/servlet-class /servlet servlet-mapping servlet-nameArticleServlet/servlet-name url-pattern/article/url-pattern /servlet-mapping意思是所有http://localhost:8080/blog/article的请求都交给ArticleServlet处理。Servlet 类的doGet和doPost方法里会根据request.getParameter(action)的值二次分发比如actiondetail进详情逻辑actionlist进列表逻辑。想看懂这个项目按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} varitem div classpost-item h2a href${pageContext.request.contextPath}/article?actiondetailid${item.id}${item.title}/a/h2 p classmeta${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)方法返回ListArticle再调count()方法拿总记录数算出总页数最后把结果放进 requestforward 到index.jsp。列表页底部用一段c:if判断是否有上一页/下一页生成对应的翻页链接。这里务必记得给pageNum做非法值过滤不然有人手动访问?pageNum-99会导致 SQL 里的LIMIT出现负值轻则查询异常重则暴露 SQL 报错信息给用户。5. 部署排错三步走乱码、热部署和 jar 包冲突还有把博客项目写成简历项目的两个小改进5.1 SQL 查出的中文正常JSP 页面却乱码这类项目的乱码问题按“数据流方向”分成三段排查。第一段是 JSP 页面本身文件头部必须有% page contentTypetext/html;charsetUTF-8 languagejava %第二段是 Servlet 接收请求参数在doPost开头必须执行request.setCharacterEncoding(UTF-8)第三段是数据库连接db.properties的 url 里要带characterEncodingutf8。三段都设置正确了乱码问题几乎不可能出现。如果只有 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 已删列表页的分类导航链接都拼接上?categoryIdxServlet 里多处理一个参数条件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能打开登录页、后台任意一个列表页未登录时访问会跳回登录页。三个功能验证通过这个 JSPServletMySQL 的博客系统就能纳入你自己的调试节奏了。本文还有配套的精品资源点击获取