JSP+Servlet+MySQL博客系统:从部署到代码改造的完整指南 简介基于JSP、Servlet与MySQL构建的博客系统网站源码面向Java Web初学者和需要快速搭建博客平台的开发者涵盖用户注册登录、文章发布编辑、评论互动、分类检索、权限管理等常用模块可直接部署学习或二次开发。压缩包共327个文件约2.43MB包含49个jsp页面负责界面展示、52个java与52个class文件处理Servlet业务逻辑、1个sql数据库脚本及3个db文件用于数据存储另有js、css、图片和swf等前端资源结构清晰便于对照学习。目前已有1161人学习下载。通过研究源码可掌握JSP脚本元素、Servlet生命周期、JDBC操作MySQL等核心知识点同时理解前端与后端如何协作完成动态交互对想进阶的开发者也可以此为基础尝试引入Spring MVC或前后端分离技术栈进行重构升级。1. 这套 JSPServletMySQL 博客源码为什么我劝你别跳过老技术栈一提到 JSP很多新入行的同学直接划走觉得早该进博物馆了。但真把 Spring Boot 用熟之后回头看当初没搞懂的请求转发、Session 管理、JDBC 事务全在这套老博客系统里以最原始的方式摆着。这套源码的组成很朴素JSP 写页面、Servlet 接请求、MySQL 存数据没有任何框架包装逻辑全裸在代码里适合刚学完 Java 基础想看看 Web 项目长什么样的初学者也适合想快速搭一个博客站点边改边练的开发者。它覆盖的模块很全用户注册登录、文章发布编辑删除、评论、好友添加、图片上传、管理员权限。不像那些只做半个登录的 demo这套东西是一个能完整跑起来的小型站点。你下载之后可以做两件事照着部署一遍把环境、请求、数据库之间的链路跑通再挑一个模块动手改造比如把明文密码换成加密存储。接下来我从部署到代码阅读把它完整拆一遍重点讲清楚哪些地方容易翻车哪些代码值得学哪些必须改。2. 理解系统骨架JSP、Servlet、MySQL 是怎么协作的JSP Servlet MySQL 是 Java Web 最经典的组合也是理解后来一切 Web 框架的地基。先搞懂每一层负责什么再去看项目代码才不会一头扎进文件堆里出不来。2.1 先看懂一次请求的完整路径用户在浏览器输入地址、点击提交按钮请求先到 Tomcat。Tomcat 根据 web.xml 里的 servlet-mapping 映射把请求交给对应的 Servlet。Servlet 负责处理参数、调用 DAO 数据访问对象、把结果放进 request 或 session 域再通过 forward 或 redirect 把控制权交给 JSP 页面。JSP 最后把 Java 数据渲染成 HTML返回给浏览器。这个项目里几个类的命名非常直白RegeditConfirm是注册确认SendArticleDAO是发布文章的数据访问对象DeleteArticleDAO负责删除文章getArticle取文章详情ModifyUserData修改用户资料。老项目没那么多设计模式类名基本就是功能名阅读成本很低。下面是一段典型的 web.xml 映射配置servlet servlet-nameshowArticle/servlet-name servlet-classcom.example.blog.servlet.ShowArticle/servlet-class /servlet servlet-mapping servlet-nameshowArticle/servlet-name url-pattern/showArticle/url-pattern /servlet-mapping这段配置的含义是servlet-class指定 Servlet 的实现类url-pattern决定浏览器访问哪个路径会触发这个 Servlet。com.example.blog.servlet要按你实际导入项目的包路径修改否则 Tomcat 启动时直接报 ClassNotFoundException。Servlet 里的doGet和doPost方法分别在收到 GET 和 POST 请求时执行。登录表单提交用 POST文章链接跳转用 GET这是源码里最基本的约定。代码风格大概是这样的public class ShowArticle extends HttpServlet { Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int articleId Integer.parseInt(request.getParameter(id)); Article article articleDao.getById(articleId); request.setAttribute(article, article); request.getRequestDispatcher(/article.jsp).forward(request, response); } }注意这里用forward而不是sendRedirect。forward 是服务器内部跳转浏览器 URL 不变request 域里的数据还能拿到sendRedirect 是浏览器重新发一次请求request 域数据直接丢失只能靠 session 或者 URL 参数传递。很多新手在这里踩坑页面白屏查了半天其实就看一眼代码里是 forward 还是 redirect 就能定位。2.2 数据库表设计用户、文章、评论、好友四张表这套博客系统不止有文章还带了用户管理和好友功能。数据库设计涉及四类核心数据表名主要字段作用userid, username, password, email, regtime用户账号信息articleid, title, content, user_id, category, createtime文章数据commentid, article_id, user_id, content, createtime评论数据friendid, user_id, friend_id, status好友关系建表 SQL 的思路大概是这样的。注意字符集要指定 utf8mb4否则后面中文乱码会非常痛苦CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(50) NOT NULL, email VARCHAR(100), regtime DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE article ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, content TEXT, user_id INT, category VARCHAR(50), createtime DATETIME, FOREIGN KEY (user_id) REFERENCES user(id) ); CREATE TABLE comment ( id INT PRIMARY KEY AUTO_INCREMENT, article_id INT, user_id INT, content TEXT, createtime DATETIME, FOREIGN KEY (article_id) REFERENCES article(id), FOREIGN KEY (user_id) REFERENCES user(id) );表之间的关联全靠外键字段article.user_id指向user.idcomment.user_id同理。查询时用 JOIN 把用户名带出来这是最经典的用法。文章列表页最常见的查询是按发布时间取最新文章SELECT id, title, user_id, createtime FROM article ORDER BY createtime DESC;ORDER BY createtime DESC是 SQL 排序里最常用的写法。初学者常把DESC漏掉结果列表顺序是反的。还有一点值得注意老项目里password字段基本是明文存储如果你打算把这个项目对外发布密码加密是第一个必改项。2.3 环境版本组合JDK 1.8 Tomcat 8/9 MySQL 5.7老项目挑环境版本乱配是最常见的坑。JSP 和 Servlet 是 Java EE 规范Tomcat 版本直接决定 Servlet API 版本Tomcat 8.5 支持 Servlet 3.1Tomcat 9 支持 Servlet 4.0JDK 1.8 两者都兼容。MySQL 建议用 5.7因为 8.0 改成了默认caching_sha2_password认证插件老项目如果用的是 mysql-connector-java 5.1.x连接时会报Unable to load authentication plugin。如果机器上已经装了 MySQL 8.0也有补救办法换 8.x 的 JDBC 驱动并修改驱动类名和连接参数。但最省事的方式还是直接用 5.7安装时选 utf8mb4 字符集后面麻烦少一半。组件版本建议说明JDK1.8太新的 JDK 可能不支持老编译选项Tomcat8.5 或 9.0对应 Servlet 3.1 / 4.0MySQL5.7兼容老 JDBC 驱动避免认证插件问题IDEEclipse 或 IntelliJ IDEA导入时选择 Web 项目类型安装顺序上先装 JDK配置JAVA_HOME和PATH环境变量这关系到 Tomcat 能不能启动。再装 MySQLWindows 下安装完成后到“服务”里确认 MySQL 服务已启动。最后解压 Tomcat不需要安装程序解压即用。java -version echo %JAVA_HOME% net start mysqljava -version输出 1.8 说明 JDK 可用echo %JAVA_HOME%检查环境变量是否配好Tomcat 启动时报Neither the JAVA_HOME nor the JRE_HOME environment variable is defined就是这里没配net start mysql是 Windows 下手动启动 MySQL 服务。Tomcat 解压后双击bin/startup.bat浏览器访问http://localhost:8080能看到 Tomcat 首页就算通了。如果端口被占用用下面的命令排查netstat -ano | findstr :8080 taskkill /F /PID 占用端口的PIDnetstat找到 8080 端口的 PID再用taskkill强制结束进程。Tomcat 多开或者之前没关干净时这个操作几乎每天都能用到。3. 把项目跑起来从建库到访问首页这一章直接讲运行。我强烈建议按“先数据库、后项目、再启动”的顺序来。有一次我先启动了 Tomcat 再回头建库结果页面刷出来全是数据库连接错误白白排查了二十分钟。血泪教训先建库。3.1 MySQL 建库、建表、写入测试数据用管理员账号登录 MySQL先建一个名为 blog 的库指定 utf8mb4 字符集然后执行项目自带的 SQL 脚本。如果项目里没有现成的 SQL 文件就按 2.2 节的表结构手动建表。mysql -u root -p CREATE DATABASE blog DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE blog; SOURCE D:/sql/blog.sql; SHOW TABLES;参数说明-u root指定用户-p表示需要输入密码。SOURCE是 MySQL 命令行执行 SQL 文件的指令后面跟绝对路径。Windows 路径里尽量用正斜杠省得处理反斜杠转义。SHOW TABLES执行后如果能列出 user、article、comment、friend 这些表说明导入成功。如果项目没带 SQL 文件手动建完表后可以先插入一条管理员测试数据INSERT INTO user (username, password, email, regtime) VALUES (admin, admin123, admintest.com, NOW());这里有一个关键点插入之前先去源码里确认密码是怎么存的。如果源码注册逻辑没有做加密这条明文密码就能直接用如果做了 MD5你必须先把admin123加密再写进去否则登录永远失败。这个操作直接决定你后面测试能不能顺利登录。3.2 在 IDEA 里导入源码并配置 Tomcat打开 IntelliJ IDEA选择 Open 选中源码目录。如果源码是 Eclipse 结构IDEA 会提示是否转换为 IDEA 项目选择信任并转换。导入后确认 Project Structure 里的 Language level 是 8避免编译版本过高导致诡异报错。打开 Run - Edit Configurations点左上角加号选择 Tomcat Server - Local。如果在列表里找不到说明还没配置 Application Servers去 Settings - Build, Execution, Deployment - Application Servers 里把 Tomcat 解压目录加进去。配置页面主要填两个地方Server 选项卡选 Tomcat 安装目录Deployment 选项卡点加号把项目的 Artifact 加进去Application context 填/blog。这样启动后访问地址是http://localhost:8080/blog/。启动前多做两个检查一是打开 Project Structure - Facets确认项目里存在 Web Facet否则 Tomcat 不知道这是个 Web 应用二是确认没有端口冲突。真出了问题用命令行以前台模式启动日志输出比 IDEA 里直观得多cd D:/apache-tomcat-9/bin catalina.bat runcatalina.bat run会让 Tomcat 在前台运行控制台直接打日志。这个命令配合项目启动报错排查比在 IDE 里反复点按钮高效得多。3.3 修改数据库连接配置并启动源码里的数据库连接一般集中写在一个 DbUtil 或类似的工具类里。打开项目全局搜索jdbc:mysql找到连接配置改成你本机的参数。老项目没有独立配置文件直接写在 Java 静态常量里改完要重新编译部署。public class DbUtil { private static final String DRIVER com.mysql.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/blog?useUnicodetruecharacterEncodingutf8useSSLfalse; private static final String USER root; private static final String PASSWORD yourpassword; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }参数说明URL 里的blog要和建库名一致characterEncodingutf8是连接层编码少它中文必乱useSSLfalse避免 8.x 驱动输出大量无意义的告警。如果驱动 jar 是 8.0 及以上DRIVER要改成com.mysql.cj.jdbc.Driver。PASSWORD填你 MySQL 的实际密码别用默认空密码否则后面接二连三的 Access denied。改完启动 Tomcat控制台出现Server startup in xxx ms表示启动成功。浏览器访问/blog能看到博客首页说明部署通了。如果首页能开但登录和列表页报错大概率还是数据库连接细节不对回到上一节逐个核对。4. 核心链路阅读注册、登录、发文章、传图片部署跑通只是第一步读代码才是这套源码真正的价值。这一章沿着一条用户操作链路走注册账号、登录、发文章、传图片看 Servlet 和 DAO 是怎么配合的。类清单里那十几个类基本都能在这条链路里遇上。4.1 注册与登录RegeditConfirm 和 Session 的配合注册的入口是RegeditConfirm。流程是 JSP 提交表单Servlet 接参DAO 检查用户名是否重复再插入新用户最后重定向到登录页。典型代码是这种风格protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); String email request.getParameter(email); UserDao userDao new UserDao(); if (userDao.findByUsername(username) ! null) { response.sendRedirect(register.jsp?error1); return; } User user new User(username, password, email); userDao.addUser(user); response.sendRedirect(login.jsp?success1); }第一行setCharacterEncoding(UTF-8)必须在读取任何参数之前调用否则拿到的中文参数就是乱码。findByUsername返回非 null 说明用户名已存在重定向到注册页并带error1页面用这个参数显示提示。新用户组装成 User 对象再交给 DAO这个做法比把参数挨个传进去清晰得多。注意代码里密码是明文如果源码确实这样写那这就是一个明确的安全改造点。你可以用 commons-codec 给密码加一层 MD5import org.apache.commons.codec.digest.DigestUtils; String md5Password DigestUtils.md5Hex(password); userDao.addUser(username, md5Password, email);登录成功的标志是用户对象进了 Session。后续 JSP 页面通过session.getAttribute(user)判断是否登录。读源码时一定要看登录 Servlet 里有没有执行session.setAttribute(user, user)没有这一句页面刷新后就等于没登录。登录成功后最常去的页面就是个人信息展示页面。ModifyUserData负责修改个人资料包括用户名、邮箱、密码等字段。如果你看到页面里展示的头像和昵称没有按预期更新多半是数据库 UPDATE 成功了但 Session 里还是旧对象需要在修改成功后重新查询用户并覆盖 Session。4.2 发布文章与图片上传SendArticleDAO 和 UploadImage 的分工发布文章时SendArticleDAO处理数据落库UploadImage处理图片文件两个类职责分开。先看文章插入public boolean addArticle(String title, String content, int userId, String category) { String sql INSERT INTO article(title, content, user_id, category, createtime) VALUES (?,?,?,?,NOW()); try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, title); ps.setString(2, content); ps.setInt(3, userId); ps.setString(4, category); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }这里用PreparedStatement做参数化查询是防 SQL 注入的基本盘。NOW()让数据库生成时间不依赖 Java 的日期格式化。try-with-resources 会自动关连接老代码里常看到手动关流的写法顺手改成这个更干净。图片上传要重点看文件保存路径。核心逻辑近似这样Part filePart request.getPart(image); String fileName UUID.randomUUID().toString().replace(-, ) .jpg; String savePath D:/upload/ fileName; filePart.write(savePath);getPart是 Servlet 3.0 处理文件上传的 API前提是表单enctypemultipart/form-data。文件名用 UUID 重命名是为了避免重名覆盖。上传目录要选一个稳定的磁盘路径不要放在项目部署目录内否则 IDEA 重新部署时目录被清空图片全部丢失这就是下一章 5.3 节要细说的坑。在 JSP 页面显示图片时常见写法是img srcupload/文件名。如果你要调整图片显示的坐标位置本质是给外层容器做 CSS 定位用 position、top、left 控制跟 JSP 语法没有直接关系。这个思路对头像、封面图、文章配图都通用。4.3 管理员操作删除与修改如何避免外键约束系统里区分普通用户和管理员。管理操作主要有删用户、删文章、改文章。DeleteUser、DeleteArticleDAO、ModifyArticle三个类对应这三件事。删除用户时如果直接执行DELETE FROM user WHERE id?只要这个用户名下有关联的文章或评论数据库就会报外键约束错误。正确顺序是先删依赖数据Connection conn null; try { conn DbUtil.getConnection(); conn.setAutoCommit(false); String delComment DELETE FROM comment WHERE user_id?; String delArticle DELETE FROM article WHERE user_id?; String delFriend DELETE FROM friend WHERE user_id? OR friend_id?; String delUser DELETE FROM user WHERE id?; // 依次执行四个删除最后统一提交 conn.commit(); } catch (SQLException e) { if (conn ! null) { conn.rollback(); } }删除顺序有讲究先删评论再删文章再删好友关系最后删用户。conn.setAutoCommit(false)开启事务任何一步失败都回滚保证数据库不会出现半删除状态。这个代码虽然不算长但它把 JDBC 事务的写法完整演示了一遍值得多看两遍。ModifyArticle的逻辑更简单核心是一条 UPDATEUPDATE article SET title?, content?, category? WHERE id?关键在于修改前必须校验当前登录用户是否有权改这篇文章。老项目经常忽略这个校验只要知道文章 id 就能改别人的内容。如果你要改造第一件事就是在ModifyArticle的入口检查 Session 用户和文章作者是否一致。ShowArticle和getArticle配合完成文章详情页。getArticle从数据库查出文章对象ShowArticle把对象塞进 request再 forward 到文章详情 JSP。这正好对应 2.1 节里那个示例代码的完整链路。读到这里你其实已经把“请求进来、查数据、渲染页面”这条主线走通了一大半。5. 避坑指南部署运行中最常见的五个坑这套源码我在不同环境跑过很多遍下面五个问题属于高发区。每一条都按“现象、原因、解决”的结构整理遇到同类问题可以直接对照执行。5.1 数据库连接失败Access denied 或 No suitable driver现象项目启动后访问首页页面报 Cannot create JDBC connection控制台出现Access denied for user rootlocalhost或者No suitable driver found。原因三种可能MySQL 用户名密码不对数据库不存在驱动 jar 缺失或驱动类名与版本不匹配。老项目最容易出在驱动缺失上——jar 放在WebContent/WEB-INF/lib目录导入 IDEA 时如果没把这个目录标记为 Libraryjar 不会进入部署包运行时就找不到驱动。解决第一步确认 jar 在项目里。IDEA 中右键 lib 目录选 Add to Library或者把mysql-connector-java-5.1.x.jar复制到 Tomcat 的 lib 目录。第二步核对DbUtil里的驱动类和 URL确认库名是 blog。第三步用命令行mysql -u root -p测试同一个账号能否登录。三层都通过连接基本就通了。如果驱动换成 8.x驱动类名必须改成com.mysql.cj.jdbc.Driver这一步经常被忽略。5.2 中文乱码页面显示 ??? 或 锟斤拷现象文章标题、用户名全是问号或者出现“锟斤拷”这种标志性乱码。原因字符集不统一。数据库、JDBC URL、JSP 页面编码、请求编码四个地方必须保持一致坏一个就乱码。最典型的是建库时用了默认的 latin1或者 JSP 没写 pageEncoding。解决按顺序排查。MySQL 里执行SHOW CREATE DATABASE blog;看默认字符集不是 utf8mb4 就执行ALTER DATABASE blog DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;JDBC URL 加characterEncodingutf8。JSP 页面顶部补上% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Servlet 里读参数前写request.setCharacterEncoding(UTF-8)。四个位置对齐乱码问题基本消失。注意修改后要清浏览器缓存再测试否则看到的还是旧页面。5.3 图片上传成功但访问 404现象上传动作提示成功但页面里图片是破图浏览器直接打开图片 URL 返回 404 或 403。原因图片保存的物理路径和访问路径不一致。老项目在 IDEA 里部署时上传代码写的可能是相对路径而 Tomcat 的实际工作目录并不是项目目录结果图片写到别处去了页面自然访问不到。还有一种情况是上传目录在项目部署目录内重新部署时被清空。解决在 Tomcat 的conf/server.xml的 Host 节点下加一个虚拟目录映射Context docBaseD:/upload path/upload reloadabletrue/这样/upload/xxx.jpg就指向磁盘上的D:/upload/xxx.jpg。同时把UploadImage里的保存路径改成绝对路径D:/upload图片地址统一拼成/upload/文件名。改完重启 Tomcat再做一次上传验证。5.4 Session 失效或权限校验被跳过现象登录成功后跳回首页又变回未登录更严重的是不登录直接输入后台管理 URL 就能操作数据。原因第一类是登录 Servlet 里没写session.setAttribute或者这个项目用的是 request 域传递登录状态跳转后就丢。第二类是权限校验只写在 JSP 页面靠隐藏按钮或 JS 控制显示Servlet 层完全没有检查。解决登录成功后补上request.getSession().setAttribute(user, user);然后给所有需要登录的 Servlet 加上统一的前置检查在doGet或doPost第一行判断HttpSession session request.getSession(false); if (session null || session.getAttribute(user) null) { response.sendRedirect(login.jsp); return; }管理员接口还要额外判断用户角色字段。记住一条原则前端隐藏不等于安全校验必须发生在服务端。5.5 500 空指针request.getParameter 拿到 null现象点击提交后页面报 500控制台出现 NullPointerException定位到getParameter那一行。原因表单里 input 的 name 和 Servlet 里 getParameter 的字符串对不上。比如表单写了nameusername代码里写的却是request.getParameter(name)拿到的自然是 null。另一种情况是提交按钮的 type 不对点击后压根没有提交表单。解决打开表单页面源码逐个对比 input 的 name 和 Servlet 里的参数名。确认提交按钮是button typesubmit或input typesubmit。这类问题用浏览器开发者工具看 Network 里的 Form Data一眼就能看出提交了哪些字段和代码里对一遍即可定位。参数名对不上是老项目最常见的低级错误排查思路要养成习惯。6. 验证系统真的能跑三步走通主链路部署完成之后别急着去点各种花哨功能。我建议按下面三步走一遍先把核心链路验证到位再谈样式和功能优化。第一步注册一个新账号。注册成功后打开 MySQL查 user 表确认记录写入同时看密码字段是明文还是哈希。如果密码是明文说明源码确实没有加密逻辑后面改造成加密时这条数据要重新插入。SELECT id, username, password, email, regtime FROM user ORDER BY id DESC LIMIT 5;第二步登录后发布一篇带图片的文章。提交成功后去 article 表查记录确认 title、content、user_id 写入正确图片 URL 是一个可访问的完整路径。SELECT id, title, user_id, category, createtime FROM article ORDER BY id DESC LIMIT 5;第三步用管理员账号执行删除文章操作再去数据库确认这条记录是物理删除还是逻辑删除。有些博客系统做的是假删除用 status 字段标记隐藏列表里没了但数据库里还在。这种设计在博客场景里其实有它的道理你可以想想它和物理删除各自的适用场景。验证时最大的误区是只看页面反馈。我见过有人说“发布成功了”页面弹了成功提示但数据库里根本查不到原因是前端表单的 action 写错了请求压根没到目标 Servlet。所以我的习惯是左边浏览器操作右边 SQL 查询每一步都以数据是否变化为准页面提示只做参考。从那以后我每次部署这类老项目都会强制先走一遍“注册、登录、发文章、删文章”的闭环确认数据库每一步都有对应变化才继续做功能改造。这套 JSPServletMySQL 的博客源码值得你花一个下午按这条链路完整过一遍。希望帮到你。本文还有配套的精品资源点击获取