news 2026/5/5 16:56:39

JavaWeb(JDBC+JSP+Servlet)毕业设计技术指南:从单体架构到可维护实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaWeb(JDBC+JSP+Servlet)毕业设计技术指南:从单体架构到可维护实践

最近在帮学弟学妹们看毕业设计,发现很多用 JavaWeb(JDBC+JSP+Servlet)做的项目,虽然功能实现了,但代码结构真是一言难尽。业务逻辑全写在 JSP 里,数据库连接随手创建随手关,SQL 语句直接拼接字符串…… 这不仅是代码“丑”的问题,更埋下了安全漏洞和维护噩梦的种子。今天,我就结合自己的经验,系统梳理一下如何用这套经典技术栈,做出一个结构清晰、安全可靠、易于维护的毕业设计项目。

1. 学生项目中的那些“典型”痛点

在开始讲“正确姿势”前,我们先看看常见的“踩坑”操作,你是不是也中招了?

  1. 脚本式编码:一个ServletJSP文件里,从接收参数、校验数据、处理业务逻辑、操作数据库,到最终渲染页面,所有代码都堆在一起。这种代码读起来费劲,改起来更是牵一发而动全身。
  2. SQL 注入漏洞:这是最高频的安全问题。很多同学图省事,直接用字符串拼接的方式构造 SQL 语句,比如"SELECT * FROM user WHERE name='" + username + "'"。如果用户输入admin' OR '1'='1,后果可想而知。
  3. XSS 跨站脚本攻击:在 JSP 页面上,直接使用<%= request.getParameter("content") %>输出用户提交的内容。如果用户提交了一段<script>alert('xss')</script>,这段脚本就会被执行,窃取 cookie 或进行其他恶意操作。
  4. 资源泄露与无事务管理:在ServletdoGet/doPost方法里直接DriverManager.getConnection(),用完后可能忘记关闭ConnectionStatementResultSet。更复杂的是,涉及到多个数据库操作时(比如转账:A账户扣钱,B账户加钱),没有事务概念,一旦中间出错,数据就处于不一致状态。
  5. 混乱的页面逻辑:JSP 页面中充斥着大量的<% ... %>脚本片段,用于控制流程、查询数据。这导致前端美工无法介入,后端开发改页面也头疼,职责完全不清。

2. 为什么毕业设计还要学这套“老”技术?

现在 Spring Boot 这么火,为什么很多学校还要求用 JDBC+JSP+Servlet 呢?我认为这恰恰是教学的高明之处。

  1. 理解 Web 开发本质:Spring MVC 再强大,其底层依然是 Servlet 规范。亲手写一遍Servlet,你才能真正理解一个 HTTP 请求是如何被接收、处理、响应的。理解了RequestResponse对象,再看 Spring 的@RequestMapping@RequestParam就会觉得豁然开朗。
  2. 掌握基础原理:JDBC 是 Java 操作数据库的基石。通过手写 JDBC 代码,你能深刻理解连接池为什么重要、事务是如何控制的、ORM 框架(如 MyBatis, Hibernate)到底帮我们做了什么。有了这个基础,学习任何上层框架都会事半功倍。
  3. 培养架构意识:在没有框架“约束”的情况下,如何自己组织代码结构(分层)、如何管理依赖、如何处理异常,这些思考能极大地锻炼你的软件设计能力。用框架是“开车”,而学这套是“造车”,虽然慢,但对发动机(原理)的理解更深。

3. 核心实现细节:构建清晰的三层架构

我们的目标是构建一个表现层(JSP)- 控制层(Servlet)- 数据访问层(DAO)的清晰结构。

3.1 Servlet 的生命周期与控制层设计

Servlet是单例的,它的init()service()destroy()方法由容器(如 Tomcat)管理。我们主要重写doGet()doPost()

关键点:一个功能对应一个 Servlet 是糟糕的设计。推荐一个模块对应一个 Servlet,通过请求参数(如action)来分发不同的处理方法。

// UserServlet.java - 处理所有用户相关请求 @WebServlet("/user") public class UserServlet extends HttpServlet { private UserService userService = new UserService(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); String action = request.getParameter("action"); if ("login".equals(action)) { login(request, response); } else if ("list".equals(action)) { listUsers(request, response); } // ... 其他action } // 登录处理方法 private void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); // 1. 调用Service层处理业务逻辑 User user = userService.login(username, password); if (user != null) { // 登录成功,将用户信息存入Session request.getSession().setAttribute("currentUser", user); // 2. 跳转到成功页面(重定向,避免重复提交) response.sendRedirect(request.getContextPath() + "/success.jsp"); } else { // 登录失败,设置错误信息并转发回登录页 request.setAttribute("msg", "用户名或密码错误"); request.getRequestDispatcher("/login.jsp").forward(request, response); } } // ... 其他方法 }

3.2 JDBC 工具类封装与 DAO 模式

绝对不能在每个需要数据库操作的地方都写一遍连接代码。我们需要一个JdbcUtil工具类来管理连接(这里引入连接池,如 Druid),并提供统一的获取连接和释放资源的方法。

// JdbcUtil.java public class JdbcUtil { // 使用Druid连接池 private static DataSource dataSource; static { try { Properties prop = new Properties(); prop.load(JdbcUtil.class.getClassLoader().getResourceAsStream("druid.properties")); dataSource = DruidDataSourceFactory.createDataSource(prop); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("初始化数据库连接池失败!"); } } // 获取连接 public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } // 释放资源 (重载方法,用于增删改) public static void close(Connection conn, Statement stmt) { close(conn, stmt, null); } // 释放资源 (重载方法,用于查询) public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs != null) rs.close(); } catch (SQLException e) { e.printStackTrace();} try { if (stmt != null) stmt.close(); } catch (SQLException e) { e.printStackTrace();} try { if (conn != null) conn.close(); } // 注意:这里是归还连接到池,不是关闭物理连接 } catch (SQLException e) { e.printStackTrace();} } }

然后,我们为每个实体(如User)创建对应的DAO(Data Access Object) 接口和实现类,专门负责数据库操作。

// UserDao.java (接口) public interface UserDao { User findByUsernameAndPassword(String username, String password) throws SQLException; List<User> findAll() throws SQLException; } // UserDaoImpl.java public class UserDaoImpl implements UserDao { @Override public User findByUsernameAndPassword(String username, String password) throws SQLException { Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; User user = null; try { conn = JdbcUtil.getConnection(); // 关键!使用PreparedStatement预编译,防止SQL注入 String sql = "SELECT id, username, nickname FROM t_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.setNickname(rs.getString("nickname")); } } finally { JdbcUtil.close(conn, pstmt, rs); // 确保资源被释放 } return user; } }

3.3 JSP 与 Java 逻辑分离

JSP 应该只负责显示数据,不要在里面写业务逻辑或数据库查询。数据由Servlet准备好,通过request.setAttribute()传递过来,JSP 使用JSTL 标签库EL 表达式来渲染。

首先,在 JSP 头部引入 JSTL 核心库:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

然后,在页面中优雅地展示数据和控制流程:

<%-- login.jsp --%> <form action="${pageContext.request.contextPath}/user?action=login" method="post"> <input type="text" name="username" placeholder="用户名"> <input type="password" name="password" placeholder="密码"> <button type="submit">登录</button> <%-- 使用EL表达式安全地输出错误信息 --%> <c:if test="${not empty msg}"> <div style="color:red;">${msg}</div> </c:if> </form> <%-- userList.jsp --%> <table> <tr><th>ID</th><th>用户名</th><th>昵称</th></tr> <%-- 使用JSTL的forEach遍历Servlet传来的userList --%> <c:forEach items="${userList}" var="user"> <tr> <td>${user.id}</td> <%-- 使用JSTL的c:out防止XSS,它会自动转义HTML特殊字符 --%> <td><c:out value="${user.username}" /></td> <td><c:out value="${user.nickname}" /></td> </tr> </c:forEach> </table>

4. 安全性与性能考量

  1. 密码加密存储:绝对不要用明文存密码!即使是毕业设计,也要养成好习惯。使用BCryptMD5(加盐)等算法对密码进行哈希处理后再存入数据库。在UserService.login()方法中,对输入的密码用同样算法加密后,再与数据库中的密文对比。
  2. 会话超时与安全:在web.xml中配置会话超时时间(如30分钟)。对于敏感操作(如支付、修改密码),除了检查 Session 中是否存在用户对象,还可以增加验证码、二次密码确认等。
  3. 避免 N+1 查询问题:在UserDao.findAll()中,如果你还需要显示用户的订单信息,不要在循环里为每个用户单独查询一次数据库。应该使用 SQL 的JOIN语句一次查询出来,或者在后端进行适当的数据组装。
  4. 统一异常处理:不要在每个Servlet方法里都写try-catch。可以创建一个实现ServletExceptionHandler类,并在web.xml中配置<error-page>,将不同类型的异常(如SQLException,NullPointerException)统一导向友好的错误提示页面。

5. 生产环境避坑指南(毕业设计也适用)

  1. 绝对避免在 JSP 中写业务逻辑:这会让你的项目难以测试和维护。所有逻辑应放在ServletService层。
  2. 使用连接池:正如我们上面用 Druid 做的。直接DriverManager.getConnection()在高并发下会拖垮数据库。
  3. 务必关闭资源:在finally块或使用 try-with-resources 语句确保ConnectionStatementResultSet被关闭,防止内存泄漏。
  4. 使用预编译 Statement (PreparedStatement):这是防止 SQL 注入最简单有效的方法,同时还能提升 SQL 执行效率。
  5. 输出内容做转义:在 JSP 中,使用<c:out value="${content}">JSTLfn:escapeXml()函数对用户输入的内容进行转义,防范 XSS。

写在最后

通过以上这些实践,你的 JDBC+JSP+Servlet 毕业设计项目就能摆脱“学生气”,拥有一个清晰、健壮、可维护的骨架。这个过程虽然繁琐,但每一步都在加深你对 Web 开发底层原理的理解。

当你熟练掌握了这套“原始”技术栈后,再去看 Spring Boot,你会发现它做的很多事情——比如依赖注入、MVC 分发、事务管理、JDBC 封装——都是为了更优雅、更自动化地解决我们上面手动处理的问题。你的Servlet变成了@RestController,你的JdbcUtilDAOJdbcTemplateMyBatis Mapper替代,你的web.xml配置变成了application.properties和注解。

这时,平滑迁移的思路就非常清晰了:将你手写的“轮子”,替换为 Spring 提供的成熟“组件”。你所积累的分层思想、面向接口编程、异常处理等良好习惯,在任何框架下都是通用的宝贵财富。希望这篇指南能帮助你不仅完成毕业设计,更能为未来的技术之路打下坚实的基础。

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

4步构建全球化开源工具:面向多语言用户的本地化实践指南

4步构建全球化开源工具&#xff1a;面向多语言用户的本地化实践指南 【免费下载链接】mobox 项目地址: https://gitcode.com/GitHub_Trending/mo/mobox 如何让开源工具跨越语言壁垒&#xff0c;实现真正的全球化协作&#xff1f;在跨国协作日益频繁的今天&#xff0c;语…

作者头像 李华
网站建设 2026/5/3 16:32:24

BewlyBewly插件完全攻略:从零开始打造个性化B站体验

BewlyBewly插件完全攻略&#xff1a;从零开始打造个性化B站体验 【免费下载链接】BewlyBewly Improve your Bilibili homepage by redesigning it, adding more features, and personalizing it to match your preferences. 项目地址: https://gitcode.com/gh_mirrors/be/Bew…

作者头像 李华
网站建设 2026/5/3 16:36:12

旧Mac如何突破系统限制?OpenCore Legacy Patcher完整指南

旧Mac如何突破系统限制&#xff1f;OpenCore Legacy Patcher完整指南 【免费下载链接】OpenCore-Legacy-Patcher 体验与之前一样的macOS 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 随着苹果对旧款Mac设备的系统支持逐步终止&#xff0…

作者头像 李华
网站建设 2026/5/3 16:36:12

Mac存储告急?从根源解决重复文件问题的完整方案

Mac存储告急&#xff1f;从根源解决重复文件问题的完整方案 【免费下载链接】czkawka 一款跨平台的重复文件查找工具&#xff0c;可用于清理硬盘中的重复文件、相似图片、零字节文件等。它以高效、易用为特点&#xff0c;帮助用户释放存储空间。 项目地址: https://gitcode.c…

作者头像 李华
网站建设 2026/5/3 16:36:14

HsMod:基于BepInEx框架实现炉石传说体验优化的创新方案

HsMod&#xff1a;基于BepInEx框架实现炉石传说体验优化的创新方案 【免费下载链接】HsMod Hearthstone Modify Based on BepInEx 项目地址: https://gitcode.com/GitHub_Trending/hs/HsMod HsMod&#xff08;Hearthstone Modify&#xff09;作为一款基于BepInEx框架开发…

作者头像 李华
网站建设 2026/5/3 17:27:24

Umi-OCR多语言混合文档识别优化指南:解决乱码与排版错乱的技术方案

Umi-OCR多语言混合文档识别优化指南&#xff1a;解决乱码与排版错乱的技术方案 【免费下载链接】Umi-OCR Umi-OCR: 这是一个免费、开源、可批量处理的离线OCR软件&#xff0c;适用于Windows系统&#xff0c;支持截图OCR、批量OCR、二维码识别等功能。 项目地址: https://gitc…

作者头像 李华