JSP+Servlet+MySQL博客系统源码部署与优化全攻略 简介这是一份基于 Java Web 三大核心技术——JSP、Servlet 与 MySQL 构建的博客系统网站源码适合刚开始接触 Java 服务端开发的初学者、正在准备课程设计或毕业设计的学生以及希望快速搭建个人博客的开发者。资源包含用户注册登录、文章发布编辑、评论回复、关键词搜索、用户与管理员权限区分等模块基本覆盖一个内容型网站的核心流程。压缩包整体大小约 2.43 MB内含 327 个文件其中 JSP 页面有 49 个负责网页界面与表单展示Java 类和 class 文件各有 52 个承担 Servlet 控制器、数据访问对象等业务逻辑另配 CSS、JavaScript 文件完成前端样式与交互以及 SQL 脚本、配置文件等导入即可部署运行。阅读这份源码可以系统理解 JSP 内置对象与页面标签的使用、Servlet 从初始化到销毁的生命周期、通过 JDBC 操作 MySQL 表数据的常用方法并串联起一次完整请求在浏览器、服务器和数据库之间的处理流程。该资源已有 1160 人浏览学习除了作为功能范本参考外还可以在此架构基础上尝试用 Spring MVC 或 Spring Boot 重构帮助提升分层设计和主流框架迁移能力。1. 拿到「JSPServletMySQL 博客系统源码」这个 ZIP 后的第一件事不是解压而是定版本当你从某个下载页把《博客系统网站jsp源码下载JSPSERVLETMYSQL.zip》拖进本地磁盘说明你想要的东西已经明确要么是今晚要把课程设计跑起来要么是想抄一套能讲清楚 Java Web 底层的源码结构。JSPServletMySQL 这个组合没有 Spring Boot 启动类也没有 pom.xml它全靠 Tomcat 把 web.xml、Servlet 和 JSP 页面串起来。这个 ZIP 里真正值钱的东西是那套没有框架遮挡的请求处理流程以及一张三张表就能撑起整个系统的数据库设计。若直接解压双击 index.jsp 期望着能看到页面你会发现浏览器只会给你一段源码文本——先装 JDK、再配 Tomcat、再导入 MySQL 脚本顺序错一步后面的报错会让你误以为源码本身是坏的。2. 先建立请求链路认知JSP、Servlet 与 MySQL 在博客系统里各管哪一段2.1 JSP 承担的是渲染层首次请求要经历「JSP 转 Servlet 再转 class」三个阶段博客系统的 JSP 页面一般按前台和后台分开布局根目录下的 index.jsp、article.jsp 给访客看文章列表与详情admin 子目录下的 login.jsp、edit.jsp 给管理者做登录和写作入口。这份源码里的 JSP 大多直接嵌着 Java 脚本片段也就是% %里写 while 循环输出文章标题。这正是 JSP 当年被诟病的写法但也是理解渲染层最直观的标本Tomcat 内置的 Jasper 引擎在某个 JSP 第一次收到请求时会把它翻译成一个 Servlet 源文件再编译成 class随后才走正常 Servlet 生命周期后续再次请求时容器会比较 JSP 文件时间戳与已编译 class 的先后源码有改动则自动重新编译。所以修改 JSP 通常不需要重启 Tomcat却总遇到“改了页面不变”的情况多数问题出在浏览器缓存或 Tomcat 的 work 目录残留和 JSP 本身的编译机制没关系。2.2 Servlet 是控制层web.xml 里每一条 url-pattern 都对应一个请求入口这种非 Maven 的老课设里Servlet 的注册高度集中在 web.xml少数工程半注解半 XML最容易出问题就在此处。博客系统的常见写法是每个业务域一个 Servlet文章、评论、用户各管各的 action。下面是最常见的配置片段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 filter filter-nameencodingFilter/filter-name filter-classorg.apache.catalina.filters.SetCharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping这段配置里有两个细节值得注意servlet-name只是逻辑名称真正决定类加载的是servlet-class两者写错任何一个都会在浏览器打出 404url-pattern写成/article表示访问http://IP:8080/项目名/article时 Tomcat 会把请求交给 ArticleServlet。Servlet 是懒加载的容器启动时并不会实例化直到第一次请求到达才创建对象并存进单例池。过滤器配置用 Tomcat 自带的SetCharacterEncodingFilter可以少写一个类它的本质就是给request.setCharacterEncoding(UTF-8)包一层让 POST 请求体按 UTF-8 解码。2.3 MySQL 表设计博客系统的核心是 user、article、comment 三张表课程设计级的博客系统不会引入复杂的权限模型也不需要把文章正文拆成多张表。数据库设计通常就三张核心表外加一张分类表可有可无表名核心字段作用userid, username, password, nickname, avatar登录认证与文章作者信息展示articleid, title, content, author_id, create_time, category博客文章正文与分类归属commentid, article_id, user_id, content, create_time文章评论与用户关联author_id与user.id建立外键页面展示时用 JOIN 查一次就能拿到作者昵称。MySQL 5.7 与 8.0 都能跑通的典型建表语句CREATE TABLE article ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, content MEDIUMTEXT, author_id INT DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, category VARCHAR(50) DEFAULT 未分类, CONSTRAINT fk_article_user FOREIGN KEY (author_id) REFERENCES user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;参数说明MEDIUMTEXT最多存 16MB 正文博客场景足够DEFAULT CURRENT_TIMESTAMP让插入时自动填充发布时间少写一条INSERT字段强制CHARSETutf8mb4是防止正文里出现 emoji 后数据库中能存、页面显示却是问号UTF-8 老字符集只支持到三字节utf8mb4 才覆盖完整 Unicode。2.4 DAO 层的 JDBC 代码是整个系统的执行核心Servlet 拿到请求后不直接写 SQL而是调用 DAO 类命名习惯通常是 ArticleDao、UserDao 这类接口加 Impl 实现。下面是首页文章列表分页查询里最稳定的一段写法public ListArticle findPage(int limit, int offset) throws SQLException { String sql SELECT id, title, LEFT(content, 300) AS summary, create_time FROM article ORDER BY create_time DESC LIMIT ? OFFSET ?; ListArticle list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, limit); ps.setInt(2, offset); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Article a new Article(); a.setId(rs.getInt(id)); a.setTitle(rs.getString(title)); a.setSummary(rs.getString(summary)); a.setCreateTime(rs.getTimestamp(create_time)); list.add(a); } } } return list; }这段代码里的两个问号是 PreparedStatement 占位符limit 和 offset 作为参数传给 MySQL而不是拼进 SQL 字符串这是防止 SQL 注入最基础的防线。LEFT(content, 300)在数据库层就完成摘要截断不要等把整篇正文取回 Java 再substring否则首页一旦有二十篇文章同时渲染内存和带宽都吃不消。整条请求链路到这里就闭合了JSP 页面超链接发起/article?actionlist请求ArticleServlet 的 doGet 方法调用 findPage接着request.setAttribute把 List 放进 request 域forward 回 JSP最后由 JSP 遍历输出 HTML。3. 版本匹配与环境初始化JDK、Tomcat、MySQL 驱动各就各位3.1 用一张兼容表看清 JDK 21 与 Tomcat 的搭配边界网上问得最多的就是“我装了 JDK 21这套老源码能不能跑”。直说结论JDK 21 配 Tomcat 9 能跑配 Tomcat 10.1 跑不了因为 Tomcat 10 开始把javax.servlet包换成了jakarta.servlet而这份 ZIP 里的 Servlet 和 JSP 页面全部按javax旧包名编写。把项目丢进 Tomcat 10编译直接报包不存在。实际组合参考这张表运行环境Tomcat 8.5Tomcat 9.0Tomcat 10.1JDK 8兼容兼容不兼容JDK 11兼容兼容不兼容JDK 17不建议兼容兼容JDK 21不建议兼容兼容但需改包名上表里所有“兼容”都只代表容器能启动。从 JDK 8 迁到 JDK 17 以上的老项目编译期还可能出现InaccessibleObjectException原因是新 JDK 强封装了java.base模块里的反射接口。稳妥组合是学习环境选 JDK 8 或 11 搭配 Tomcat 8.5/9生产维护环境选 JDK 17 搭配 Tomcat 9。没有旧 JDK 的话去 Oracle 官网拉 JDK 21 并搭配 Tomcat 9 的 Windows zip 版这是目前最少踩坑的路径。3.2 MySQL 8 下的驱动类名变了连接 URL 必须带时区参数老源码的 WEB-INF/lib 目录里通常放着 5.x 版的mysql-connector-java在 MySQL 8.0 上启动时会报ClassNotFoundException: com.mysql.jdbc.Driver或者连接成功后立刻抛Communications link failure。替换驱动 jar 到 8.0.x 版本后JDBC 配置按下面这套写jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://127.0.0.1:3306/blog?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingUTF-8 jdbc.usernameroot jdbc.password123456参数说明com.mysql.cj.jdbc.Driver是 MySQL 8 的完整驱动类名旧类名com.mysql.jdbc.Driver在 8.x 里只是保留的桥接仍能加载但会在日志里打 deprecated 警告serverTimezoneAsia/Shanghai不写的话驱动会用 JVM 默认时区去比对数据库时区MySQL 8 直接拒绝连接并提示时区值无法识别characterEncodingUTF-8保证中文不乱码useSSLfalse只适合本机开发环境线上必须启 TLS 并配套证书配置。如果下载的是 mysql 免安装版 zip初始化路径与安装版不同。在解压目录执行mysqld --initialize-insecure该命令会在 data 目录下生成一个 root 密码为空的基础实例然后手动启动服务mysqld --console--initialize-insecure关键字反过来记忆即可默认版本初始化会生成随机临时密码加了-insecure才生成空密码避免第一轮登录就卡在密码输入上。3.3 把裸写的 DriverManager 换成 Druid 连接池改动不到 30 行老博客源码里的 DBUtil 通常是每次操作都执行Class.forName再DriverManager.getConnection用完connection.close()。这种写法演示单机问题不大一旦页面同时被十几个浏览器刷新MySQL 默认连接数 151 很容易被打满。顺手替换成德鲁伊连接池是性价比最高的改造业务代码零变动public class DBUtil { private static DruidDataSource ds; static { ds new DruidDataSource(); ds.setDriverClassName(com.mysql.cj.jdbc.Driver); ds.setUrl(jdbc:mysql://127.0.0.1:3306/blog?serverTimezoneAsia/ShanghaiuseSSLfalse); ds.setUsername(root); ds.setPassword(123456); ds.setInitialSize(2); ds.setMaxActive(10); ds.setMaxWait(3000); ds.setValidationQuery(SELECT 1); } public static Connection getConnection() throws SQLException { return ds.getConnection(); } }参数说明initialSize2表示容器启动时预建两个物理连接避免第一个请求慢在建立 TCP 连接上maxActive10限定同一时刻最多 10 个活动连接超出的请求进入等待队列maxWait3000是拿连接的最大阻塞时间3 秒拿不到就抛异常比默认无限等待更能暴露问题validationQuery让 Druid 每次拿连接前用SELECT 1验证连接是否可用防止 MySQL 8 空闲连接超过wait_timeout被服务端断开后客户端还拿着失效连接去执行 SQL。4. 部署跑通把 ZIP 导入 IDE、配置 Tomcat、按四个固定点排查4.1 IDEA 或 Eclipse 里把源码结构变成可运行 Web 项目这套 ZIP 是传统WebRoot结构不是 Maven 项目进入 IDE 时不要选择 Maven 导入。IDEA 侧的操作大致如下解压 ZIP确认顶层能看到 src 与 WebRoot 或 WebContent 目录且没有 pom.xml。新建一个普通 Java 工程右键 Add Framework Support 勾选 Web Application。在 Project Structure 中把 src 标记为 Sources。将 WebRoot 或 WebContent 指定为 Web 资源目录编译器输出路径设为 WEB-INF/classes。在 Run Configuration 里添加 Tomcat ServerDeployment 选 war exploded。确认启动前 WEB-INF/lib 下存在 mysql 驱动 jar 和 jstl jar这是后端能连数据库、JSP 能渲染标签的前提。Eclipse 里则是 Import 选 Existing Projects into Workspace再在 Project Facets 里勾 Dynamic Web Module 和 Java把内容目录指到 WebContent。判断 ZIP 结构是否正常看根目录有没有.classpath、.project这类工程标志文件两个都没有则源码很可能只是文件快照而不是完整 IDE 工程需要手动补一遍项目结构。4.2 启动成功后的访问入口与请求路径清单服务启动成功只代表 Tomcat 没报错能不能打开页面是另一回事。这类博客系统常见的入口路径如下访问路径功能请求落在哪里http://IP:8080/项目名/index.jsp博客首页与文章列表index.jsp 模板http://IP:8080/项目名/article?actionshowid1文章详情页ArticleServlethttp://IP:8080/项目名/user/register.jsp用户注册页面user 目录下 JSPhttp://IP:8080/项目名/admin/login.jsp后台管理登录admin 目录下 JSP项目名是部署时 Artifact 的名称若部署成 ROOT.war则端口后直接接/index.jsp。访问这些路径如果全部 404先查 Tomcat webapps 目录下是否生成了对应文件夹再回到 4.3 检查映射与编译产物。4.3 三个高频挂点空指针、404 和「找不到或无法加载主类」第一个经典场景是首页能开但文章列表空白控制台报 NullPointerException原因十有八九是 SQL 脚本没导入或者数据库名大小写不一致。MySQL 的 Linux 版本表名大小写敏感Windows 不敏感源码里建表用blog但连接配置写Blog报错阶段完全一样排查时第一件事是执行SHOW TABLES;看表到底在不在。第二个经典场景是 JSP 能访问但admin/login.jsp提交后 404。去 Web 项目输出目录看 WEB-INF/classes 下有没有编译出的LoginServlet.class。看得到类却 404问题在 web.xml 的映射连类都没有问题在 IDE 的编译输出路径没有落在 WEB-INF/classes 下。IDEA 里 Artifact 的 Output Layout 一栏必须看到WEB-INF/classes指向编译输出目录否则启动时用的是旧 class。第三个是环境识别的提醒有同学把源码强行走 Maven 或 Gradle 导入再手动加 Spring Boot 依赖启动时报「错误: 找不到或无法加载主类 org.jeecg.jeecgsystemapplication」或者java.lang.NoClassDefFoundError: org/springframework/boot/web/servlet/support/SpringBootServletInitializer。这都属于项目识别错误——没有 pom.xml 的源码就按普通 Java Web 工程处理不要给它套 Spring Boot 骨架。4.4 SQL 初始化脚本用命令行导入避开复制粘贴的字符集陷阱源码压缩包里几乎都带着blog.sql或db/blog.sql这类文件。不要用记事本打开全选复制粘贴到 Navicat 执行中文字符与换行符在粘贴路径中可能被转坏导致某张表建了一半就中断。稳妥做法是命令行导入mysql -uroot -p -e CREATE DATABASE IF NOT EXISTS blog DEFAULT CHARACTER SET utf8mb4; mysql -uroot -p blog /path/to/blog.sql第一行命令先建库并指定字符集防止 SQL 脚本里没有建库语句却包含USE blog时报错第二行用重定向方式把 SQL 文件交给 mysql 客户端逐条执行。执行完用SHOW TABLES;验证三张核心表是否齐全。提示在 Windows PowerShell 里执行mysql ... blog.sql时会被 PowerShell 当作语法错误处理。改成cmd /c mysql -uroot -p blog D:/blog.sql或者用mysql -uroot -p -e source D:/blog.sql进入 mysql 客户端后以 source 方式导入。5. 改造与验证把 JSP 里的脚本片段迁到 JSTL再给编辑页加防丢失提示5.1 文章列表页从% %循环改成 EL JSTL老 JSP 文件里最常见的一段是% ListArticle list (ListArticle) request.getAttribute(articleList); for (Article a : list) { % h2% a.getTitle() %/h2 % } %把这段替换成 JSTL 后逻辑更清晰页面维护也少碰 Java% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % c:forEach items${articleList} vara h2c:out value${a.title}//h2 /c:forEach对应的 Servlet 只需在 forward 前把数据放进 request 域request.setAttribute(articleList, articleDao.findPage(10, 0)); request.getRequestDispatcher(/index.jsp).forward(request, response);items拿到的是 Servlet 里 setAttribute 的 keyvar是循环变量名${a.title}会自动调用getTitle()。这一改还顺带防了 XSS——c:out默认对 HTML 特殊字符做转义老式% %输出用户提交的评论内容则会把script原样打进页面。5.2 给个人信息展示页增加「离开提示」解决草稿误关问题博客后台通常有个个人信息展示页源码里往往是/user/info.jsp从 session 里取 user 对象显示昵称、头像和个人简介。既然已改成 EL页面可以用${sessionScope.user.nickname}直接取登录态数据。而下层编辑文章的子页面更常见的诉求是写了一半误关页面内容全丢。在admin/edit.jsp底部挂一段原生的浏览器 beforeunload 事件window.addEventListener(beforeunload, function (e) { var content document.getElementById(editor).value; if (content.trim() ! !savedFlag) { e.preventDefault(); e.returnValue ; } });代码逻辑是正文非空且未保存时触发浏览器默认的离开确认弹窗。注意returnValue赋值只是兼容老浏览器现代 Chrome 与 Firefox 会统一弹无自定义文案的提示框不要再写“离开会丢失内容”这类字符串浏览器不会显示。测试方式也简单进入后台新建文章往正文框随意输入然后直接关标签页浏览器拦截即生效保存过文章后标记savedFlagtrue再关页面就不会打扰。5.3 最后一个验证技巧从 work 目录直接看 JSP 编译产物JSP 改完后页面不刷新最直接的验证方式不是清浏览器缓存而是去 Tomcat 的work/Catalina/localhost/项目名目录下找 Jasper 生成的 Java 与 class 文件。路径结构按包名展开index.jsp对应的就是org/apache/jsp/index_jsp.class。确认编译时间戳晚于 JSP 修改时间说明容器已经重新编译时间戳没变则说明目录放错服务器加载的是另一个应用副本。看到 class 与源码时间对应就能确定问题在浏览器代理缓存而非应用层。本文还有配套的精品资源点击获取