JavaWeb 项目实战:基于 Servlet+JSP 的简易留言板开发(5 大核心模块)

📅 2026/7/9 17:30:52
JavaWeb 项目实战:基于 Servlet+JSP 的简易留言板开发(5 大核心模块)
JavaWeb 项目实战基于 ServletJSP 的简易留言板开发5 大核心模块在 JavaWeb 开发领域Servlet 和 JSP 技术一直是构建动态网站的基础。本文将带您从零开始通过一个完整的留言板项目实战深入理解 JavaWeb 开发的五大核心模块用户界面、数据提交、Servlet 处理、JSP 展示和数据库 CRUD 操作。1. 项目概述与环境搭建留言板作为经典的 Web 应用案例涵盖了用户输入、数据处理、页面展示等典型场景。我们将使用以下技术栈前端技术HTML5 CSS3 Bootstrap 5后端技术Servlet 4.0 JSP 2.3数据库MySQL 8.0 JDBC服务器Tomcat 10开发环境准备首先确保您的开发环境已安装以下组件# 检查Java版本 java -version # 应显示1.8或更高版本 # 检查Tomcat运行状态 catalina version项目目录结构建议如下/MessageBoard ├── src/ │ └── com/example/messageboard/ │ ├── dao/ # 数据访问层 │ ├── model/ # 数据模型 │ ├── servlet/ # Servlet控制器 │ └── util/ # 工具类 ├── web/ │ ├── WEB-INF/ │ │ ├── lib/ # 依赖库 │ │ └── web.xml # 部署描述符 │ ├── css/ # 样式文件 │ ├── js/ # JavaScript文件 │ └── view/ # JSP视图 └── pom.xml # Maven配置文件数据库设计创建简单的留言表结构CREATE TABLE messages ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL, content TEXT NOT NULL, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );2. 用户界面模块开发前端界面是用户与系统交互的入口我们采用响应式设计确保在不同设备上都有良好的体验。核心页面布局!-- web/index.jsp -- % page contentTypetext/html;charsetUTF-8 languagejava % !DOCTYPE html html head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1 title简易留言板/title link hrefcss/bootstrap.min.css relstylesheet /head body div classcontainer mt-5 div classrow div classcol-md-8 mx-auto div classcard div classcard-header bg-primary text-white h4发表留言/h4 /div div classcard-body form idmessageForm actionMessageServlet methodpost div classmb-3 label forusername classform-label用户名/label input typetext classform-control idusername nameusername required /div div classmb-3 label forcontent classform-label留言内容/label textarea classform-control idcontent namecontent rows3 required/textarea /div button typesubmit classbtn btn-primary提交/button /form /div /div div classcard mt-4 div classcard-header bg-info text-white h4留言列表/h4 /div div classcard-body div idmessageList jsp:include page/MessageListServlet/ /div /div /div /div /div /div script srcjs/bootstrap.bundle.min.js/script script // AJAX提交表单 document.getElementById(messageForm).addEventListener(submit, function(e) { e.preventDefault(); const formData new FormData(this); fetch(this.action, { method: POST, body: formData }).then(response { if(response.ok) { location.reload(); // 刷新留言列表 } }); }); /script /body /html关键实现要点响应式布局使用 Bootstrap 的栅格系统确保在移动设备上正常显示表单验证通过 HTML5 的 required 属性实现基础验证异步提交使用 Fetch API 实现无刷新提交3. 数据提交与 Servlet 处理模块Servlet 作为控制器负责接收用户请求并协调各组件完成业务逻辑。消息处理 Servlet// src/com/example/messageboard/servlet/MessageServlet.java WebServlet(/MessageServlet) public class MessageServlet extends HttpServlet { private MessageDao messageDao; Override public void init() throws ServletException { super.init(); messageDao new MessageDaoImpl(); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置字符编码 request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); // 获取表单数据 String username request.getParameter(username); String content request.getParameter(content); // 验证数据 if(username null || username.trim().isEmpty() || content null || content.trim().isEmpty()) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, 参数不完整); return; } // 创建消息对象 Message message new Message(); message.setUsername(username); message.setContent(content); // 保存到数据库 try { messageDao.addMessage(message); } catch (SQLException e) { e.printStackTrace(); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 数据库操作失败); } } }数据访问层实现// src/com/example/messageboard/dao/MessageDaoImpl.java public class MessageDaoImpl implements MessageDao { private static final String INSERT_SQL INSERT INTO messages(username, content) VALUES(?, ?); private static final String SELECT_ALL_SQL SELECT id, username, content, create_time FROM messages ORDER BY create_time DESC; Override public void addMessage(Message message) throws SQLException { try (Connection conn DBUtil.getConnection(); PreparedStatement stmt conn.prepareStatement(INSERT_SQL)) { stmt.setString(1, message.getUsername()); stmt.setString(2, message.getContent()); stmt.executeUpdate(); } } Override public ListMessage getAllMessages() throws SQLException { ListMessage messages new ArrayList(); try (Connection conn DBUtil.getConnection(); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(SELECT_ALL_SQL)) { while (rs.next()) { Message message new Message(); message.setId(rs.getInt(id)); message.setUsername(rs.getString(username)); message.setContent(rs.getString(content)); message.setCreateTime(rs.getTimestamp(create_time)); messages.add(message); } } return messages; } }数据库工具类// src/com/example/messageboard/util/DBUtil.java public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/message_board; private static final String USER root; private static final String PASSWORD password; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }4. JSP 展示模块JSP 负责将数据以友好的方式呈现给用户我们使用 JSTL 简化页面逻辑。留言列表 Servlet// src/com/example/messageboard/servlet/MessageListServlet.java WebServlet(/MessageListServlet) public class MessageListServlet extends HttpServlet { private MessageDao messageDao; Override public void init() throws ServletException { super.init(); messageDao new MessageDaoImpl(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { ListMessage messages messageDao.getAllMessages(); request.setAttribute(messages, messages); request.getRequestDispatcher(/view/messageList.jsp).forward(request, response); } catch (SQLException e) { e.printStackTrace(); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 数据库查询失败); } } }留言列表 JSP 页面!-- web/view/messageList.jsp -- % page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % c:if test${empty messages} div classalert alert-info暂无留言/div /c:if c:forEach items${messages} varmessage div classcard mb-3 div classcard-header d-flex justify-content-between span${message.username}/span small classtext-muted fmt:formatDate value${message.createTime} patternyyyy-MM-dd HH:mm:ss/ /small /div div classcard-body p classcard-text${message.content}/p /div /div /c:forEachJSP 页面优化技巧使用 JSTL 和 EL 表达式避免在 JSP 中直接编写 Java 代码日期格式化利用fmt:formatDate标签美化日期显示条件判断使用c:if处理空数据情况循环遍历使用c:forEach展示列表数据5. 数据库 CRUD 与高级功能完整的留言板需要支持消息的增删改查操作下面我们扩展基础功能。消息删除功能实现DAO 层新增方法// MessageDao.java 新增接口方法 void deleteMessage(int id) throws SQLException; // MessageDaoImpl.java 实现 Override public void deleteMessage(int id) throws SQLException { try (Connection conn DBUtil.getConnection(); PreparedStatement stmt conn.prepareStatement( DELETE FROM messages WHERE id ?)) { stmt.setInt(1, id); stmt.executeUpdate(); } }新增 DeleteMessageServletWebServlet(/DeleteMessageServlet) public class DeleteMessageServlet extends HttpServlet { private MessageDao messageDao; Override public void init() throws ServletException { super.init(); messageDao new MessageDaoImpl(); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String idStr request.getParameter(id); if(idStr null || !idStr.matches(\\d)) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, 无效的消息ID); return; } try { int id Integer.parseInt(idStr); messageDao.deleteMessage(id); response.setStatus(HttpServletResponse.SC_OK); } catch (SQLException e) { e.printStackTrace(); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 删除消息失败); } } }修改 messageList.jsp 添加删除按钮div classcard-footer text-end button classbtn btn-sm btn-danger delete-btn >// MessageDao.java ListMessage getMessagesByPage(int page, int size) throws SQLException; int getTotalCount() throws SQLException; // MessageDaoImpl.java Override public ListMessage getMessagesByPage(int page, int size) throws SQLException { ListMessage messages new ArrayList(); String sql SELECT id, username, content, create_time FROM messages ORDER BY create_time DESC LIMIT ?, ?; try (Connection conn DBUtil.getConnection(); PreparedStatement stmt conn.prepareStatement(sql)) { stmt.setInt(1, (page - 1) * size); stmt.setInt(2, size); try (ResultSet rs stmt.executeQuery()) { while (rs.next()) { Message message new Message(); message.setId(rs.getInt(id)); message.setUsername(rs.getString(username)); message.setContent(rs.getString(content)); message.setCreateTime(rs.getTimestamp(create_time)); messages.add(message); } } } return messages; } Override public int getTotalCount() throws SQLException { try (Connection conn DBUtil.getConnection(); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(SELECT COUNT(*) FROM messages)) { if (rs.next()) { return rs.getInt(1); } } return 0; }修改 MessageListServlet 支持分页protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { int page 1; int size 5; String pageParam request.getParameter(page); if(pageParam ! null !pageParam.isEmpty()) { page Integer.parseInt(pageParam); } ListMessage messages messageDao.getMessagesByPage(page, size); int total messageDao.getTotalCount(); int totalPages (int) Math.ceil((double) total / size); request.setAttribute(messages, messages); request.setAttribute(currentPage, page); request.setAttribute(totalPages, totalPages); request.getRequestDispatcher(/view/messageList.jsp).forward(request, response); } catch (SQLException e) { e.printStackTrace(); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 数据库查询失败); } }在 JSP 中添加分页导航!-- 分页导航 -- nav aria-labelPage navigation ul classpagination justify-content-center c:if test${currentPage 1} li classpage-item a classpage-link hrefMessageListServlet?page${currentPage - 1}上一页/a /li /c:if c:forEach begin1 end${totalPages} vari li classpage-item ${i currentPage ? active : } a classpage-link hrefMessageListServlet?page${i}${i}/a /li /c:forEach c:if test${currentPage totalPages} li classpage-item a classpage-link hrefMessageListServlet?page${currentPage 1}下一页/a /li /c:if /ul /nav6. 项目部署与优化完成开发后我们需要将项目部署到 Tomcat 服务器并进行性能优化。Tomcat 10 部署指南打包项目使用 Maven 打包为 WAR 文件mvn clean package部署到 Tomcat将生成的message-board.war文件复制到 Tomcat 的webapps目录启动 Tomcat 服务器访问http://localhost:8080/message-board数据库连接池配置 替换简单的 DBUtil 为连接池实现如 HikariCP// 更新DBUtil类 public class DBUtil { private static HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/message_board); config.setUsername(root); config.setPassword(password); config.setMaximumPoolSize(10); config.setMinimumIdle(5); config.setConnectionTimeout(30000); dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }性能优化建议前端优化使用 CDN 加载 Bootstrap 和 jQuery压缩 CSS 和 JavaScript 文件启用浏览器缓存后端优化使用 Servlet 3.0 的异步处理实现缓存机制如 Redis优化 SQL 查询添加适当索引安全加固防止 XSS 攻击对用户输入进行转义防止 SQL 注入始终使用 PreparedStatement添加 CSRF 防护完整项目结构回顾最终项目结构应包含以下关键组件src/ main/ java/ com/example/messageboard/ dao/ # 数据访问接口和实现 model/ # 数据模型(Message) servlet/ # 所有Servlet控制器 util/ # 工具类(DBUtil等) resources/ # 配置文件 web/ WEB-INF/ lib/ # 依赖库 web.xml # 部署描述符 css/ # 样式文件 js/ # JavaScript文件 view/ # JSP视图文件 index.jsp # 首页 pom.xml # Maven项目配置7. 扩展功能与进阶学习完成基础留言板后可以考虑添加以下功能提升项目完整性用户认证实现注册/登录功能使用 Session 管理用户状态添加权限控制富文本编辑集成 CKEditor 或 TinyMCE支持图片上传和表情符号实时更新使用 WebSocket 实现新留言实时推送添加消息通知功能RESTful API设计符合 REST 规范的 API支持 JSON 格式数据交互前端框架集成使用 Vue.js 或 React 构建前端实现前后端分离架构学习资源推荐Servlet/JSP 进阶《Head First Servlets and JSP》Oracle 官方 Servlet 文档数据库优化《高性能 MySQL》JDBC 批量操作和事务管理现代 JavaWeb 技术Spring Boot 框架学习MyBatis 持久层框架前端技术Bootstrap 5 官方文档jQuery 或现代 JavaScript 框架通过这个完整的留言板项目您已经掌握了 JavaWeb 开发的核心技术栈。从简单的表单提交到数据库操作再到页面展示每个环节都体现了 JavaWeb 开发的基本原理。