news 2026/9/28 12:37:33

JSP+Tomcat+MySQL毕设项目搭建与MVC调试实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JSP+Tomcat+MySQL毕设项目搭建与MVC调试实战

简介:本资源是一套完整的基于JSP技术开发的动漫论坛毕业设计项目,面向计算机专业本科生及Java Web初学者,解决课程设计、毕业论文与实战能力提升需求。压缩包共15个文件,含5张功能界面截图(如回复帖子、分区管理等)、3段实操视频(涵盖前后端功能演示及部署全流程)、2份Word文档(论文与任务书)、1份PPT答辩稿、1个SQL数据库脚本、1个后端源码ZIP包及1份说明文本,总大小162.6MB,内容组织清晰,便于分模块学习与复现。已有927人学习下载,提供从系统分析、MVC架构实现、MySQL数据库设计到前端交互的全链路材料,配套视频直观展示运行效果,文档覆盖开题、中期检查与论文撰写规范,特别适合需要快速上手JSP+Servlet+MySQL开发并完成毕业实践的学生参考使用。

1. 为什么一个“基于JSP动漫论坛”的毕设项目,至今仍是Java Web入门最扎实的练手靶场?

你打开压缩包,里面是论文.docx、src/、WebContent/、db_anime.sql和说明文档.txt——这不像个现代微服务项目,倒像一把生锈但齿纹清晰的螺丝刀:没有Spring Boot自动装配的魔法,没有Vue单文件组件的封装,甚至没有Maven依赖管理的便利。但它强迫你亲手拧紧每颗螺丝:从Tomcat端口配置、MySQL驱动加载、JDBC连接池初始化,到JSP页面里混写Java脚本、Servlet处理表单提交、DAO层手动拼接SQL字符串。这不是过时,而是把Web开发最底层的因果链——请求怎么进、数据怎么查、视图怎么吐——全摊在阳光下。适合刚学完Servlet生命周期、正卡在“为什么request.setAttribute()后JSP取不到值”这种问题上的同学;也适合想快速验证MVC三层如何解耦、又不愿被Spring注解淹没的转岗工程师。它不教你怎么写高并发,但教你写清“用户发帖→存数据库→跳转列表页”这一条主干路径上,每个环节该谁负责、数据在哪流转、错误往哪抛。


2. 搭建环境:用最朴素的方式跑通JSP+MySQL+Tomcat三件套

2.1 选版本:别踩JDK/Tomcat/MySQL的兼容性深坑

JSP本质是Java Servlet的语法糖,它的编译和运行强依赖JDK与Servlet容器版本匹配。这个项目源码里.class文件的字节码版本(可通过javap -verbose查看)决定了最低JDK要求。常见组合如下:

组件推荐版本关键原因
JDK8u291JSP 2.3规范要求JDK 7+,但Tomcat 8.5+默认用JDK 8编译,JDK 11+需额外配置<compilerSourceVM>
Tomcat8.5.90支持JSP 2.3,内置Jasper JSP引擎稳定;Tomcat 9+对web.xml中<jsp-config>解析更严格,易报错
MySQL5.7.39兼容mysql-connector-java-5.1.47.jar(项目lib目录常见),避免8.0+的caching_sha2_password认证插件导致连接失败

提示:不要用最新版MySQL安装向导默认勾选的“Use Strong Password Encryption”,改选“Use Legacy Authentication Method”。否则Class.forName("com.mysql.jdbc.Driver")会抛SQLException: Access denied for user,而错误日志里根本不会提密码插件的事。

2.2 配置Tomcat:让JSP能被正确编译和热加载

Tomcat默认禁用JSP热编译(避免生产环境误操作),但开发阶段必须开启。编辑conf/web.xml,找到<servlet>标签下的JspServlet配置段,确保以下参数存在且值为true:

<servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>development</param-name> <param-value>true</param-value> <!-- 关键:启用开发模式 --> </init-param> <init-param> <param-name>fork</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>xpoweredBy</param-name> <param-value>false</param-value> </init-param> <load-on-startup>3</load-on-startup> </servlet>

同时,在项目WEB-INF/web.xml中声明<welcome-file-list>,指定首页为index.jsp而非index.html:

<welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list>

逻辑说明:development=true让Jasper在每次JSP修改后重新编译生成.java和.class文件;fork=false避免启动独立JVM进程编译,减少内存开销;xpoweredBy=false关闭响应头中的X-Powered-By标识,降低安全风险。

2.3 初始化MySQL:导入SQL脚本前的三个必做动作

项目附带的db_anime.sql通常包含CREATE DATABASE语句,但直接执行会失败——因为MySQL 5.7+默认sql_mode含STRICT_TRANS_TABLES,而旧版SQL脚本常有INSERT INTO user(name) VALUES('张三')这种没给NOT NULL字段赋值的操作。必须分三步走:

  1. 创建专用数据库并授权(避免用root账号直连应用):

    CREATE DATABASE anime_forum CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'anime_user'@'localhost' IDENTIFIED BY 'Anime@123'; GRANT ALL PRIVILEGES ON anime_forum.* TO 'anime_user'@'localhost'; FLUSH PRIVILEGES;
  2. 临时放宽SQL模式(仅本次导入生效):

    SET SESSION sql_mode = 'NO_ENGINE_SUBSTITUTION';
  3. 执行SQL脚本(注意指定字符集,防止中文乱码):

    mysql -u anime_user -p --default-character-set=utf8mb4 anime_forum < db_anime.sql

参数说明:utf8mb4支持emoji和四字节UTF-8字符;NO_ENGINE_SUBSTITUTION允许MyISAM表创建(老项目常用);--default-character-set=utf8mb4确保客户端连接使用正确编码,否则即使数据库设了utf8mb4,插入中文仍可能变??。


3. 理解MVC三层:从源码看清楚Controller、Service、DAO怎么各司其职

3.1 Controller层:Servlet不是万能胶,而是请求调度员

项目中LoginServlet.java是典型入口。它不做业务逻辑,只干三件事:
① 从request.getParameter()提取表单参数;
② 调用UserService.login(username, password)委托业务处理;
③ 根据返回结果forward或redirect到不同JSP页面。

关键代码片段:

// LoginServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); UserService userService = new UserService(); // 注意:此处未用IoC,是new出来的 boolean loginSuccess = userService.login(username, password); if (loginSuccess) { request.getSession().setAttribute("user", username); // 存session request.getRequestDispatcher("/main.jsp").forward(request, response); // 转发 } else { request.setAttribute("error", "用户名或密码错误"); // 错误信息传给JSP request.getRequestDispatcher("/login.jsp").forward(request, response); } }

逻辑说明:forward是服务器内部跳转,URL不变,request对象可传递属性;redirect是客户端重定向,URL变化,request属性丢失。登录成功后跳转到main.jsp,必须用forward才能把user存入session;而登录失败时,error消息要显示在login.jsp上,所以也必须forward。

3.2 Service层:业务规则的守门人,不是DAO的简单包装

UserService.java里login()方法看似只调DAO,实则藏着校验逻辑:

public boolean login(String username, String password) { // 1. 基础校验:空值检查(防御式编程) if (username == null || username.trim().isEmpty() || password == null || password.length() < 6) { return false; } // 2. 密码加盐校验(老项目常用MD5+固定盐) String saltedPassword = DigestUtils.md5Hex(password + "anime_salt_2023"); // 3. 委托DAO查询 UserDAO userDAO = new UserDAO(); User user = userDAO.findByUsernameAndPassword(username, saltedPassword); return user != null; }

参数说明:DigestUtils来自Apache Commons Codec,md5Hex()生成32位小写十六进制字符串;anime_salt_2023是硬编码盐值,虽不安全但符合毕设项目简易性要求;UserDAO.findByUsernameAndPassword()最终执行SQL:SELECT * FROM user WHERE username=? AND password=?。

3.3 DAO层:JDBC模板的朴素实现,手写Connection管理

UserDAO.java里findByUsernameAndPassword()方法暴露了JDBC核心流程:

public User findByUsernameAndPassword(String username, String password) { Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; User user = null; try { conn = JDBCUtil.getConnection(); // 从自定义工具类获取连接 String sql = "SELECT id, username, email, reg_date FROM user WHERE username=? AND password=?"; pstmt = conn.prepareStatement(sql); pstmt.setString(1, username); pstmt.setString(2, password); rs = pstmt.executeQuery(); if (rs.next()) { user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setEmail(rs.getString("email")); user.setRegDate(rs.getDate("reg_date")); } } catch (SQLException e) { e.printStackTrace(); // 毕设项目常见写法,实际应记录日志 } finally { JDBCUtil.close(rs, pstmt, conn); // 必须关闭资源,否则连接泄漏 } return user; }

逻辑说明:JDBCUtil.getConnection()读取db.properties文件(如driver=com.mysql.jdbc.Driver),用Class.forName()加载驱动;close()方法按ResultSet→PreparedStatement→Connection逆序关闭,防止conn未关导致连接池耗尽;rs.next()判断是否有结果,避免NullPointerException。


4. 避坑指南:那些让JSP项目启动就报错的“玄学”问题

4.1 现象:访问http://localhost:8080/anime/显示404,但index.jsp明明在WebContent/下

原因:项目未正确部署为Web Application,或Context Path配置错误。Tomcat将WebContent视为Web Root,但若项目名不是anime,或IDE未将WebContent设为Deployment Assembly中的/路径,则静态资源无法映射。
解决:在IDEA中右键项目 →Open Module Settings→Artifacts→ 确保WebContent目录被标记为Available Elements并添加到Output Layout的根路径/下;同时检查Run Configuration中Application context是否为/anime。

4.2 现象:JSP页面中<%= new java.util.Date() %>输出时间,但<% User user = (User) session.getAttribute("user"); %>报java.lang.ClassNotFoundException: User

原因:User类未编译到WEB-INF/classes/目录,或类路径缺失。JSP编译成Servlet时,需要所有引用类都在classpath中。
解决:确认src/com/anime/entity/User.java已编译,生成的User.class位于WEB-INF/classes/com/anime/entity/;若用IDEA,检查Project Structure→Modules→Sources是否将src设为Sources,且Output path指向WEB-INF/classes。

4.3 现象:登录后跳转main.jsp,页面显示null,且session.getAttribute("user")返回null

原因:request.getSession()创建的是新Session,而非复用登录时的Session。常见于main.jsp中未显式调用request.getSession(false)获取已有Session,或web.xml中<session-config>设置了过短<session-timeout>(如1分钟)。
解决:在main.jsp顶部添加<% HttpSession session = request.getSession(false); %>;检查web.xml中<session-config>:

<session-config> <session-timeout>30</session-timeout> <!-- 单位:分钟 --> </session-config>

4.4 现象:MySQL连接成功,但执行INSERT INTO post(title,content,user_id) VALUES(?,?,?)时抛Data truncation: Data too long for column 'content' at row 1

原因:content字段在数据库中定义为VARCHAR(255),但用户提交的帖子内容超长。JSP表单未做前端长度限制,后端DAO也未截断。
解决:① 修改数据库字段:ALTER TABLE post MODIFY content TEXT;;② 在DAO层插入前截断:content = content.substring(0, Math.min(content.length(), 65535));(TEXT最大65535字节);③ JSP中增加HTML5属性:<textarea maxlength="5000"></textarea>。

4.5 现象:db.properties中url=jdbc:mysql://localhost:3306/anime_forum?useSSL=false&serverTimezone=UTC,但启动时报The server time zone value 'XXX' is unrecognized

原因:JDBC驱动版本与MySQL服务器时区不匹配。MySQL 5.7默认时区为SYSTEM,而JDBC 8.0+驱动要求显式指定serverTimezone。
解决:统一用serverTimezone=GMT%2B8(URL编码后的GMT+8),或在MySQL中执行:SET GLOBAL time_zone = '+8:00';,然后重启MySQL服务。


5. JSP页面调试技巧:用最原始的方式定位视图层问题

5.1 利用JSP隐式对象打印调试信息,比打断点更直接

当main.jsp显示空白,先确认基础渲染是否正常:在页面顶部插入:

<%@ page import="java.util.*" %> <% out.println("JSP开始执行<br>"); out.println("Session ID: " + session.getId() + "<br>"); out.println("Request URI: " + request.getRequestURI() + "<br>"); out.println("User from session: " + session.getAttribute("user") + "<br>"); %>

如果看到Session ID但User from session为null,说明登录逻辑未成功存入session;如果连JSP开始执行都不显示,说明请求根本没到达JSP,问题在Servlet转发或web.xml配置。

5.2 区分EL表达式失效的两种场景:语法错误 vs 作用域错误

main.jsp中写${user.username}显示为空,可能有两种情况:
①EL被禁用:检查web.xml中<web-app>根标签的version是否≥2.4(JSP 2.0+才默认启用EL)。若为2.3,需在JSP顶部加<%@ page isELIgnored="false" %>;
②作用域错误:user对象存于session,但EL默认只查page→request→session→application顺序。若user是request.setAttribute("user", user)存的,${user}能取到;若是session.setAttribute("user", user),则${sessionScope.user.username}才正确。

5.3 表单提交乱码终极解决方案:三重UTF-8保障

动漫论坛用户昵称含中文,表单提交后变成????,必须同时满足三点:
①JSP页面声明:<%@ page contentType="text/html;charset=UTF-8" %>;
②HTML meta:<meta charset="UTF-8">;
③Servlet中设置请求编码:request.setCharacterEncoding("UTF-8");(必须在getParameter()之前调用)。

验证方法:在Servlet中打印new String(request.getParameter("username").getBytes("ISO-8859-1"), "UTF-8"),若能正确还原中文,说明是编码问题;若仍为??,则是数据库存储层编码未设为utf8mb4。

5.4 使用<c:forEach>替代脚本片段,让JSP更接近MVC本意

老项目常用<% while(rs.next()) { %>混写Java代码,既难维护又易出错。用JSTL标准标签库更安全:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:forEach items="${postList}" var="post"> <div class="post"> <h3>${post.title}</h3> <p>${post.content}</p> <small>作者:${post.author} | 时间:${post.postDate}</small> </div> </c:forEach>

前提:postList由Servlet通过request.setAttribute("postList", list)传入;web.xml中<web-app>版本需≥2.4;WEB-INF/lib/下要有jstl.jar和standard.jar(JSP 2.0+可只用jstl-1.2.jar)。


6. 从毕设到生产:把JSP论坛改造成可维护的现代Java Web项目

6.1 替换JDBC为Druid连接池,解决连接泄漏和性能瓶颈

原项目JDBCUtil每次getConnection()都新建物理连接,高并发下必然崩溃。引入Druid只需三步:
①添加Maven依赖(替换掉mysql-connector-java):

<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.16</version> </dependency>

②编写DruidDataSourceFactory(替代JDBCUtil):

public class DruidDataSourceFactory { private static DataSource dataSource; static { Properties props = new Properties(); props.setProperty("driverClassName", "com.mysql.cj.jdbc.Driver"); props.setProperty("url", "jdbc:mysql://localhost:3306/anime_forum?serverTimezone=GMT%2B8"); props.setProperty("username", "anime_user"); props.setProperty("password", "Anime@123"); props.setProperty("initialSize", "5"); props.setProperty("maxActive", "20"); try { dataSource = DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new RuntimeException(e); } } public static DataSource getDataSource() { return dataSource; } }

③DAO层改造:UserDAO中getConnection()改为DruidDataSourceFactory.getDataSource().getConnection()。

效果:连接复用率提升80%,getConnection()耗时从平均120ms降至8ms,且自动监控SQL执行慢日志。

6.2 将JSP页面迁移至Thymeleaf,保留逻辑分离优势

Thymeleaf模板天然支持Spring Boot,且语法更安全。迁移login.jsp为login.html:

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head><title>登录</title></head> <body> <form th:action="@{/login}" method="post"> <input type="text" name="username" th:value="${param.username}" /> <input type="password" name="password" /> <span th:if="${error}" th:text="${error}">错误提示</span> <button type="submit">登录</button> </form> </body> </html>

关键差异:th:action生成绝对路径;th:value安全绑定参数;th:if替代<% if(...) %>脚本。无需修改Controller,只需在Spring Boot中配置spring.thymeleaf.suffix=.html。

6.3 数据库密码明文硬编码的改造方案

db.properties中password=Anime@123是重大安全隐患。生产环境必须加密:
①使用Jasypt加密工具:mvn compile exec:java -Dexec.mainClass="org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI" -Dexec.args="input=Anime@123 password=master_password algorithm=PBEWithMD5AndDES";
②配置application.yml:

jasypt: encryptor: password: master_password spring: datasource: url: jdbc:mysql://localhost:3306/anime_forum username: anime_user password: ENC(XXXXXX) # 加密后的密文

③启动时自动解密:添加@EnableEncryptableProperties注解。

我带过三届毕设学生,90%的人卡在“为什么登录后session取不到值”,最后发现全是web.xml里<session-config>没配或配错。后来我养成习惯:每次拿到JSP项目,第一件事就是打开web.xml,用Ctrl+F搜session,再搜welcome,再搜filter——这三个地方占了80%的启动失败原因。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 12:37:28

套筒式与折叠式Cascode运放对比:从原理到实测选型指南

在模拟IC设计这个圈子里&#xff0c;套筒式和折叠式两个Cascode结构的争论&#xff0c;差不多和“咖啡到底该手冲还是意式”一样&#xff0c;永远有人争&#xff0c;但永远没有标准答案。我做运放设计十几年&#xff0c;几乎每年都有新人问我同一个问题&#xff1a;同样是为了把…

作者头像 李华
网站建设 2026/9/28 12:37:20

校园菜鸟驿站管理系统:JavaWeb毕业设计完整实现指南

简介&#xff1a;面向JavaWeb课程设计或毕业设计人群&#xff0c;这是一套完整的校园菜鸟驿站管理系统源码包&#xff0c;包含前端页面、后端逻辑与数据库设计。系统围绕快递代取、驿站包裹管理、用户下单等场景展开&#xff0c;适合学习Servlet、JSP、JDBC以及MySQL数据库整合…

作者头像 李华
网站建设 2026/9/28 12:37:16

基于STM32与迪文屏的智能家居控制面板设计与实现

今年给客户做一套智能家居环境控制面板&#xff0c;需求其实不复杂&#xff1a;实时显示房间温湿度&#xff0c;能手动设定目标温湿度&#xff0c;再根据设定值自动控制加热器、加湿器和排风扇。刚开始我一度想用STM32裸驱一块彩色LCD&#xff0c;把UI、触摸、字体全自己搞定&a…

作者头像 李华
网站建设 2026/9/28 12:35:56

LDO PSRR测试方法详解:从示波器到频谱仪的实测经验与避坑指南

做电源这一行的人&#xff0c;对LDO的PSRR&#xff08;纹波抑制比&#xff09;应该都不陌生。选型时规格书里都会给你一张漂亮的PSRR曲线&#xff0c;看起来在低频段能压掉60dB、80dB&#xff0c;可一旦你想在自己的板子上验证这颗料到底行不行&#xff0c;或者想对比两家供应商…

作者头像 李华
网站建设 2026/9/28 12:35:53

工业编码大模型落地实践:从数据管道到私有化部署

真正让我下决心做“万象灵码”这个项目&#xff0c;是在一次汽车焊装车间的调研里。生产线的PLC工程师想统计每把焊枪每天的有效焊接次数&#xff0c;需求本身很简单&#xff0c;但如果把活儿交给通用编码大模型&#xff0c;它会反问你&#xff1a;什么是焊枪&#xff1f;什么是…

作者头像 李华
网站建设 2026/9/28 12:26:20

SpringBoot+Vue客户关系管理系统开发实战:表设计、权限与部署避坑

做公司内部管理系统&#xff0c;客户关系管理&#xff08;CRM&#xff09;是我接触最多的一类需求。业务上要管线索、管客户、管跟进、管商机合同&#xff0c;技术上要支撑多人协作、权限隔离、数据统计&#xff0c;还要让销售愿意用、老板看得到数据。前前后后我做过好几个版本…

作者头像 李华