
简介一份基于经典MVC三层架构的Java Web练习项目面向初学Java Web开发的学生或自学者帮助理解使用JSP、JavaBean与Servlet协同完成常见业务功能的方法。项目实现了注册、登录、留言板发布以及数据分页展示模型层独立封装数据库读写操作数据库名为webapp未设置密码视图层通过JSP动态生成页面控制层利用Servlet接收前端请求并调用模型完成响应分层清晰便于逐段调试与学习。资源压缩包共78个文件含14个Java源文件、14个JSP页面、14个Class编译文件、10个CSS样式文件、9个XML配置文件及1个SQL建库脚本整体大小1.86MB目录和包结构完整可直接导入IDE运行。目前已有1610人学习或下载对于入门阶段而言具有不错的参考价值。通过研读并运行该项目能清晰看到一次请求从表单提交到Servlet、再经JavaBean落到数据库并返回到JSP展示的完整链路同时可借助留言板分页功能练习SQL查询、结果集封装和页面循环输出为后续学习更复杂的框架打下基础。 最近好几个同学在后台留言问同一个问题学校课程设计要做“jspjavabeanservlet实现登录注册以及留言板”网上搜到的教程要么太老跑不起来要么代码贴出来一大堆也不知道从哪下手。正好我上个月刚帮一个学弟把这套项目从零到一调通趁着印象还热把完整的实现思路和踩坑记录整理出来给正在做类似课设或者刚接触Java Web的同学一个可以直接抄作业的参考。这套技术组合其实是Java Web开发里非常经典的一套“老搭档”JSP负责页面展示Servlet负责接收请求和控制跳转JavaBean负责封装数据和业务逻辑。虽然现在企业里主流已经切到Spring Boot这类框架但Servlet和JSP的底层原理依然是理解后面所有框架的基础。把登录注册加留言板这套流程弄明白后面学什么框架都快。1. 项目整体设计与思路拆解1.1 为什么是JSPJavaBeanServlet这个组合先搞清楚这三兄弟各自干什么是关键。很多新手容易把代码全堆在JSP里页面里又写HTML又写Java又写SQL当时跑起来没问题后面改需求的时候想死的心都有。我习惯用一个生活化的类比来解释Servlet是前台接待员负责收客人的需求请求、叫对应的部门干活、再把结果告诉客人JavaBean是公司里的业务专员专门处理具体事务比如验证账号密码对不对、把新用户信息存进数据库JSP是展示窗口只负责把结果漂亮地展示给客人看。这个组合对应的其实是JSP Model 2架构也就是MVC模式在Java Web里的落地JSP充当ViewServlet充当ControllerJavaBean充当Model。这样分层之后改页面不用动Java代码改逻辑不用翻HTML协同开发的时候也不容易打架。1.2 项目功能拆分与目录规划按照登录注册留言板的项目标题我把它拆成三个核心模块用户注册、用户登录、留言管理。留言管理又包含发布留言、展示留言列表、删除留言三个子功能。项目的目录结构建议一开始就规范好不然后面找文件找到怀疑人生。我在实际项目中一般这样组织src/ ├── com.demo.entity # JavaBean实体类User、Message ├── com.demo.dao # 数据访问层UserDao、MessageDao ├── com.demo.servlet # Servlet控制层LoginServlet、RegisterServlet、MessageServlet └── com.demo.util # 工具类DBUtil数据库连接、MD5Util密码加密 WebContent/ ├── index.jsp # 首页/登录页 ├── register.jsp # 注册页 ├── main.jsp # 登录后的留言板主页 └── WEB-INF/ └── web.xml # Servlet映射配置这样一个包一个职责看起来很清爽。entity就是数据模型dao就是跟数据库打交道servlet就是接收请求和跳转util放公共工具。新手照着这个结构写逻辑会清晰很多。1.3 数据库设计两张表搞定数据库设计是这个项目里很关键但经常被忽略的一环。留言板功能只需要两张表用户表和留言表。用户表我一般这样设计CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, nickname VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP );留言表这样设计CREATE TABLE t_message ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, content VARCHAR(500) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES t_user(id) );这里有个细节要注意password字段长度设64位因为后面要用MD5加密MD5生成的是32位十六进制字符串给64位是留了余量万一以后想升级成SHA-256也不用改表结构。留言表的user_id外键关联用户表这样就能知道每一条留言是谁发的删除的时候也能校验权限。2. 环境准备与基础框架搭建2.1 开发环境怎么选这套技术栈对开发环境的要求很宽容我用的是经典组合JDK 8 Tomcat 8.5 MySQL 5.7 Eclipse或者IDEA。JDK 8到现在依然是很多高校和企业的主力版本兼容性最好。Tomcat 8.5支持Servlet 3.1规范跑这套代码绰绰有余。有个小建议如果你的机器上装了JDK 17甚至更高版本而Tomcat版本没跟上可能会出现一些奇怪的兼容性问题。我用过JDK 17配Tomcat 8.5只要注意JRE环境配好其实也能跑但没必要折腾直接用JDK 8最省心。MySQL版本的话5.7和8.0都可以区别主要在驱动依赖上。MySQL 8.0需要对应的驱动也要换成mysql-connector-java-8.0.x.jar否则会报Public Key Retrieval is not allowed这个错。2.2 数据库连接工具类的封装每次操作数据库都去写一遍Class.forName、DriverManager.getConnection太痛苦了所以我习惯把数据库连接封装到一个工具类里后面所有DAO都调用这个工具类拿连接。package com.demo.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/yourdb?useUnicodetruecharacterEncodingutf8useSSLfalse; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这里有个新手特别容易忽略的坑连接MySQL的URL一定要带useUnicodetruecharacterEncodingutf8不然插入中文数据到数据库查询出来全是问号。我当年第一次做课设就栽在这上面折腾了一整天最后发现就是URL少了编码参数。注意如果用的是MySQL 8.0驱动类要改成com.mysql.cj.jdbc.DriverURL里的useSSLfalse建议保留否则控制台会刷一堆SSL警告。3. 登录注册模块的核心实现3.1 注册流程表单到Servlet到数据库的完整链路注册功能的流程是用户在register.jsp填完表单点提交浏览器把数据通过HTTP POST请求发给RegisterServletServlet用request.getParameter()拿到表单里的字段封装成User对象这就是JavaBean的作用然后调用UserDao的addUser方法插入数据库最后根据结果跳转到成功页或错误提示页。先看JavaBean的写法。JavaBean本质上就是一个普通Java类要求属性私有、提供getter/setter、有无参构造。以User为例package com.demo.entity; public class User { private int id; private String username; private String password; private String nickname; public User() { } // 省略getter和setter方法 }有人可能会问为什么不直接用HashMap来传数据原因是JavaBean能提供类型安全的访问可以在setter里加校验逻辑而且后期在JSP里用EL表达式${user.username}取值的时候JavaBean是直接支持的。这属于规范带来的隐形好处。再看Servlet的核心代码package com.demo.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.demo.dao.UserDao; import com.demo.entity.User; WebServlet(/register) public class RegisterServlet extends HttpServlet { private UserDao userDao new UserDao(); Override 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 nickname request.getParameter(nickname); User user new User(); user.setUsername(username); user.setPassword(password); user.setNickname(nickname); boolean success userDao.addUser(user); if (success) { response.sendRedirect(index.jsp?msg注册成功请登录); } else { request.setAttribute(error, 用户名已存在或注册失败); request.getRequestDispatcher(register.jsp).forward(request, response); } } }这里面两个细节值得注意第一doPost里的第一行request.setCharacterEncoding(UTF-8)必须写否则POST提交的中文参数会乱码第二注册成功用sendRedirect重定向失败用forward转发。区别在于重定向是浏览器再发起一次新请求地址栏会变能防止用户刷新时重复提交表单转发是服务器内部跳转地址栏不变适合携带错误信息回显给用户。3.2 登录逻辑与Session管理登录流程跟注册类似但多了一个关键步骤——Session会话管理。登录成功后要把当前用户信息存到Session里这样后续访问留言板、发留言、删留言服务器才知道“你是谁”。WebServlet(/login) public class LoginServlet extends HttpServlet { private UserDao userDao new UserDao(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); User user userDao.findByUsernameAndPassword(username, password); if (user ! null) { HttpSession session request.getSession(); session.setAttribute(loginUser, user); response.sendRedirect(main.jsp); } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(index.jsp).forward(request, response); } } }为什么用Session而不是直接用Cookie因为Session的数据存在服务器端客户端拿不到更安全。Cookie是存在浏览器端的用户可以随便改如果用来存用户身份信息那别人伪造个Cookie就能冒充管理员登录。Session的唯一标识是JSESSIONID这个值存在Cookie里但真正的用户数据在服务器上改不了。3.3 密码存储为什么不直接存明文登录注册项目里密码处理是个容易被忽略的点。很多课设代码直接把用户输入的密码明文存进数据库这在真实项目里是绝对不能接受的。数据库一旦泄露所有用户的密码就裸奔了。我在这个项目里用MD5加盐的方式处理密码。MD5是一种哈希算法特点是不可逆同样的输入永远得到同样的输出。用户注册的时候存的是MD5(密码 固定盐值)登录的时候把输入的密码做同样的运算再跟数据库比对。public class MD5Util { public static String md5(String input) { try { MessageDigest md MessageDigest.getInstance(MD5); byte[] digest md.digest(input.getBytes(UTF-8)); StringBuilder sb new StringBuilder(); for (byte b : digest) { String hex Integer.toHexString(0xff b); if (hex.length() 1) { sb.append(0); } sb.append(hex); } return sb.toString(); } catch (Exception e) { throw new RuntimeException(e); } } }实际调用的时候加一个固定盐值mySaltMD5Util.md5(password mySalt)能让彩虹表攻击失效。如果你还想更进一步可以给每个用户随机生成不同的盐存到数据库里但对课设项目来说固定盐的MD5已经足够交差了。4. 留言板模块的实现与优化4.1 发布留言与列表展示登录成功进入main.jsp后页面上要展示所有用户的留言同时提供一个文本框供当前登录用户发布新留言。发布留言的表单提交到MessageServletServlet里先判断用户是否登录——通过检查Session里有没有loginUser这个属性。如果没登录直接重定向回登录页。这个判断逻辑建议在留言板相关的每个Servlet里都写一遍。WebServlet(/addMessage) public class AddMessageServlet extends HttpServlet { private MessageDao messageDao new MessageDao(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); HttpSession session request.getSession(); User loginUser (User) session.getAttribute(loginUser); if (loginUser null) { response.sendRedirect(index.jsp); return; } String content request.getParameter(content); if (content ! null !content.trim().isEmpty()) { messageDao.addMessage(loginUser.getId(), content); } response.sendRedirect(main.jsp); } }留言列表的展示稍微麻烦一点因为要同时显示留言内容和留言者的昵称。这需要一条带JOIN的SQLpublic ListMessageVO getMessagesWithUser() { String sql SELECT m.id, m.content, m.create_time, u.nickname FROM t_message m JOIN t_user u ON m.user_id u.id ORDER BY m.create_time DESC; // 执行查询并封装结果 }这里我单独建了一个MessageVO类用来封装留言和用户昵称的组合数据而不是硬把用户对象塞进Message实体里。VOValue Object的概念现在听起来高大上其实就是“为展示而生的数据容器”跟数据库表结构不完全对应。注意JSP页面里遍历留言列表用JSTL的c:forEach加上EL表达式最简洁。用纯Java的for循环配合out.println会导致JSP里大段Java代码页面会非常混乱。JSTL的标签库在课设里只要引入两个jar包就能用jstl.jar和standard.jar然后在JSP头部加% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %。4.2 分页查询留言多了怎么办如果不在课设里加分页留言一多页面就巨长体验很差。分页的原理不复杂每页显示N条点击页码时传入当前页数SQL里用LIMIT限制偏移量。public ListMessageVO getMessagesByPage(int pageNum, int pageSize) { int offset (pageNum - 1) * pageSize; String sql SELECT m.id, m.content, m.create_time, u.nickname FROM t_message m JOIN t_user u ON m.user_id u.id ORDER BY m.create_time DESC LIMIT ?, ?; // 设置参数offset, pageSize }同时还要查总数来计算总页数public int getTotalCount() { String sql SELECT COUNT(*) FROM t_message; // 执行查询返回总数 }页面上就是一组“上一页、下一页”的链接携带?page2的参数传给Servlet再去查对应页的数据。我实测下来每页显示10条比较合适阅读体验好翻页压力也小。分页加进去之后你会发现代码量增加了不少。但这属于“花了时间但值得”的功能因为几乎所有真实管理系统都要分页面试时聊到这个项目分页能算一个亮点。4.3 删除留言的权限控制删除留言时一定要做权限校验只有留言本人或者管理员才能删。这个校验逻辑放在Servlet里通过Session里的用户ID和留言记录里的user_id比对实现。WebServlet(/deleteMessage) public class DeleteMessageServlet extends HttpServlet { private MessageDao messageDao new MessageDao(); Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session request.getSession(); User loginUser (User) session.getAttribute(loginUser); if (loginUser null) { response.sendRedirect(index.jsp); return; } int messageId Integer.parseInt(request.getParameter(id)); Message message messageDao.findById(messageId); if (message ! null (message.getUserId() loginUser.getId() || loginUser.getRole() 1)) { messageDao.deleteById(messageId); } response.sendRedirect(main.jsp); } }这里我用的是doGet因为删除操作是通过超链接或JavaScript跳转的GET请求。虽然严格来说删除属于修改操作应该用POST但课设项目里为了简单都用GET。如果你愿意做得更规范可以把删除按钮做成一个表单的POST提交这也是一个加分项。4.4 页面模板与EL表达式优化JSP页面里我会用sessionScope和EL表达式来简化取值。比如登录后头部显示用户名直接写成p欢迎你${sessionScope.loginUser.nickname}/p遍历留言列表用JSTL的forEachc:forEach varitem items${messageList} div classmessage-item span classnickname${item.nickname}/span span classtime${item.createTime}/span p${item.content}/p c:if test${sessionScope.loginUser.id item.userId} a hrefdeleteMessage?id${item.id} onclickreturn confirm(确定删除吗)删除/a /c:if /div /c:forEachEL表达式配合JSTL最大的优势是页面里几乎看不到Java代码了前端同学拿到页面也能直接改样式。我用这套写法试过JSP源文件的干净程度会让人心情愉悦不少。注意${item.createTime}直接输出的是java.sql.Timestamp对象格式是2025-01-15 10:30:00.0带个末尾的.0不太好看。想格式化的话可以用fmt:formatDate value${item.createTime} patternyyyy-MM-dd HH:mm/这是JSTL的格式化标签库同样需要在JSP头部引入对应taglib。5. 常见问题与排查技巧实录5.1 跑不起来先看这几处我调过不少同学的代码发现报错原因翻来覆去就那么几个。整理成一个速查表遇到问题先对照排查一遍。症状常见原因解决方案ClassNotFoundException: com.mysql.jdbc.DriverMySQL驱动jar包没放进WEB-INF/lib目录把mysql-connector-java.jar复制到WEB-INF/lib下页面请求404Servlet的WebServlet注解路径写错或者web.xml没配好检查注解URL是否和表单action一致用/login这种格式不加项目名中文乱码服务器接收请求时没指定UTF-8编码POST请求在Servlet开头加request.setCharacterEncoding(UTF-8)GET请求在Tomcat的server.xml里给Connector加URIEncodingUTF-8插入数据库中文变问号JDBC URL没有指定编码参数URL后追加?useUnicodetruecharacterEncodingutf8The requested resource is not available项目没部署到Tomcat的webapps目录或者访问路径错误确认项目部署状态检查访问的URL是否包含正确的上下文路径数据库连接拒绝MySQL服务没启动或账号密码不对确认MySQL是启动状态用客户端工具试一下能不能连上5.2 使用web.xml方式还是注解方式配置Servlet现在写Servlet有两种方式注解和web.xml配置。刚开始学的时候教材上经常用web.xml但实际开发中注解更简洁。!-- web.xml 方式配置Servlet -- servlet servlet-nameLoginServlet/servlet-name servlet-classcom.demo.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login/url-pattern /servlet-mapping如果是Servlet 3.0以上的规范的Tomcat直接用注解WebServlet(/login)两种二选一就行但要注意别同时用两种方式配置同一个Servlet路径那样启动会报冲突。我在教学的时候建议新手先写web.xml版本这样能更清楚Servlet在Web容器中的注册机制用熟了之后再切换到注解开发效率会高很多。5.3 JSP改了不生效的排查思路这个热搜词我也看到了确实是个高频痛点。JSP改了不生效十有八九是浏览器缓存了旧页面。JSP在Tomcat里会被翻译成Java类再编译成class如果Tomcat没有重新编译自然就是你改之前的旧效果。建议三步排查第一步清浏览器缓存或者用无痕模式打开看看第二步停掉Tomcat后把work目录Eclipse环境是workIDEA是out目录下的对应缓存目录里的旧class删除重启项目第三步如果项目是部署的war包确认解压出来的目录是不是旧的。我踩过最坑的一次是在IDEA里改了JSP没重新构建就点运行结果跑的还是旧版本。后来养成习惯凡改完代码都手动CtrlF9重新Build再启动Tomcat。6. 部署与扩展从课设到能用的项目6.1 项目部署的两种方式开发完要给别人演示或者交作业一般用两种方式部署一种是直接将整个项目文件夹复制到Tomcat的webapps目录启动Tomcat后自动部署另一种是打成WAR包丢到webapps下。打WAR包的方式在IDEA里很方便Build菜单里有个Build Artifacts选择Build或Rebuild就会生成war包。打好的war包复制到Tomcat的webapps目录重启Tomcat它自动解压并部署。启动后访问地址是http://localhost:8080/项目名/。注意如果发现访问时一直404先确认Tomcat端口有没有被占用。改了Tomcat默认端口的话访问URL也要跟着改。6.2 这个项目还能怎么扩展课设做完不是终点。基于现在这套框架往上加功能其实是水到渠成的事我随手列几个方向供参考个人中心页面登录后显示用户个人信息支持修改昵称和密码这正好对应热搜词里的“jsp个人信息展示页面”。留言回复功能在留言表里加一个parent_id字段0代表顶级留言非0代表回复某条留言页面展示时用递归或嵌套循环就能做成盖楼效果。数据导出Excel用Apache POI把留言列表导出成Excel文件对应热搜词里的“jsp实现数据导出为excel”。视频展示个人首页支持上传、播放MP4视频对应热搜词里的“jsp实现mp4视频播放”本质就是HTML5的video标签配合文件上传功能。最后一个话题额外提醒一句文件上传不要用request.getParameter去取文件字段要用Part接口或者引入Apache Commons FileUpload组件这是很多新手踩坑的地方。写在最后的经验之谈这套登录注册加留言板写完之后我自己最大的体会是框架只是工具理解数据怎么在页面、Servlet、JavaBean、数据库之间流动才是核心。你看完这篇博客代码能跑起来只是第一步真正值钱的是你能讲清楚“用户点了一下按钮从浏览器发起到数据库落盘中间发生了什么”。如果这个项目是你第一个Java Web作品建议在答辩或者写报告的时候把架构分层、Session管理、密码加密、分页查询这几点作为重点讲。这几个点虽然代码量不大但每一个都来自真实项目的常见需求比只会说“我实现了增删改查”要有说服力得多。祝大家的课设顺利跑通有问题欢迎在评论区交流。本文还有配套的精品资源点击获取