JavaWeb用户管理系统实战:从Servlet到JSP的MVC架构全解析

发布时间:2026/8/14 4:22:13

JavaWeb用户管理系统实战:从Servlet到JSP的MVC架构全解析
1. 项目概述与核心价值最近在后台和社群里不少刚学完Java基础、准备上手Web开发的朋友都在问同一个问题有没有一个能串起所有核心知识点的实战项目既能巩固Servlet、JSP、JDBC又能提前接触点MVC分层的思想最好还能看到真实的业务逻辑流转。说实话我当年自学的时候也卡在这个阶段理论学了一堆一打开IDE就不知道从哪下手。今天要拆解的这个“JavaWeb用户管理系统”就是专门为这个阶段设计的“毕业设计级”练手项目。它麻雀虽小五脏俱全几乎涵盖了JavaWeb初级阶段你需要掌握的所有技术栈。这个系统的核心目标非常明确对用户信息进行增删改查CRUD。别小看这四个操作一个完整的Web应用无论前台多么花哨后台都离不开对数据的基本管理。通过实现它你能亲手把Servlet如何接收请求、JSP如何展示页面、JavaBean如何封装数据、JDBC如何操作数据库这一整套流程跑通。更重要的是我会带着你一行行读源码不仅告诉你怎么写更重点讲清楚为什么这么写——比如为什么要把数据库连接信息单独放到配置文件里为什么查询用户列表后要先把结果集转成List再传给JSP这些设计背后的考量才是从“会敲代码”到“懂开发”的关键跨越。项目采用经典的JSP Servlet JavaBean模式数据库选用最普遍的MySQL。这套组合是理解后续任何高级框架如Spring MVC的基石。我会假设你已经有Java基础和简单的SQL知识但即使你刚入门跟着详细的步骤和源码讲解也能一步步搭起来。接下来我们就从最核心的设计思路开始把这个项目彻底拆开揉碎讲明白。2. 系统整体设计与架构拆解2.1 技术选型与架构模式解析为什么选择JSPServletJavaBean而不是直接用Spring Boot对于初学者而言过早接触高度封装的框架就像还没学会走路就先坐上了汽车虽然快但会错过对底层HTTP协议、请求-响应生命周期、数据库连接池等核心机制的理解。JSPServletJavaBean模式通常被称为Model 1向Model 2的过渡形态是理解MVCModel-View-Controller思想的绝佳起点。在这个项目中我们清晰地划分了三层视图层View由JSP页面担任。负责数据的展示和用户交互表单的呈现。例如userList.jsp用来展示所有用户的表格addUser.jsp是添加用户的表单页。JSP中应尽量避免写入大量的Java代码Scriptlet我们通过JSTL标签和EL表达式来简化展示逻辑。控制层Controller由Servlet担任。它是系统的“交通枢纽”所有来自浏览器的请求如/addUser,/deleteUser都由对应的Servlet接收。Servlet负责解析请求参数、调用业务逻辑、并将处理结果数据或跳转指令传递给视图层。例如AddUserServlet会获取表单提交的用户名、年龄等信息然后调用服务层的方法进行插入操作。模型层Model由JavaBean和数据库操作类DAO共同构成。JavaBean如User.java是一个纯粹的实体类它的属性id, name, age等与数据库表中的字段一一对应并提供了标准的getter/setter方法。DAOData Access Object数据访问对象如UserDao.java封装了所有对数据库的操作细节增删改查为Servlet提供干净的接口。这种分层的好处是职责分离。JSP只关心怎么显示Servlet只关心流程调度JavaBean和DAO只关心数据和业务逻辑。当需要修改页面样式时你几乎不用动Java代码当数据库从MySQL换成Oracle时你也只需要修改DAO层的实现。这种可维护性和可扩展性是软件工程的核心追求。2.2 数据库设计与核心表结构任何管理系统设计好数据库是第一步。我们设计一张user表结构力求简单但典型CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 用户ID主键, username varchar(50) NOT NULL COMMENT 用户名, password varchar(50) NOT NULL COMMENT 密码实际项目中应加密存储, email varchar(100) DEFAULT NULL COMMENT 电子邮箱, gender varchar(10) DEFAULT NULL COMMENT 性别, birthday date DEFAULT NULL COMMENT 出生日期, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 记录创建时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) COMMENT 用户名唯一 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户信息表;设计思路与细节考量主键id使用自增INT类型这是最简单高效的主键策略。AUTO_INCREMENT让数据库自动生成唯一ID避免程序层处理并发带来的重复问题。字段类型与长度username和password用了varchar(50)这是一个折中的长度。对于邮箱varchar(100)更保险。gender用varchar(10)是为了兼容“男”、“女”、“未知”等中文存储。唯一索引uk_username在username上建立唯一约束这是业务逻辑的强要求——系统不允许存在两个同名的用户。数据库层面的约束是最可靠、性能最好的防重复手段。注释养成给表和字段写COMMENT的习惯。一个月后回头看或者交接给同事时这些注释价值千金。存储引擎与字符集InnoDB支持事务和外键虽然本项目未用到是MySQL的默认和推荐引擎。utf8mb4字符集能完整支持存储Emoji表情避免未来出现乱码问题。注意在实际生产环境中password字段绝不应该明文存储。这里为了简化直接存了明文。正确的做法是使用BCrypt、PBKDF2等不可逆的哈希算法加盐Salt后存储。你可以在DAO层的插入和验证逻辑中加入加密步骤这是一个非常重要的安全实践扩展点。3. 核心模块源码逐行精讲3.1 模型层ModelUser实体与UserDao数据访问对象1. User.java (JavaBean/实体类)这个类的对象就是系统中“用户”这个概念的载体。它严格遵循JavaBean规范。package com.yourdomain.usermgr.model; import java.util.Date; public class User { private Integer id; private String username; private String password; private String email; private String gender; private Date birthday; private Date createTime; // 无参构造器是JavaBean的规范要求很多框架如反射、EL表达式依赖它来创建对象。 public User() { } // 带参构造器方便测试和快速创建对象。 public User(String username, String password, String email) { this.username username; this.password password; this.email email; } // 以下是所有属性的getter和setter方法。 // IDE可以一键生成但你要理解每个方法的作用。 public Integer getId() { return id; } public void setId(Integer id) { this.id id; } public String getUsername() { return username; } public void setUsername(String username) { this.username username; } // ... 其他getter/setter省略 // 重写toString()方法方便调试时打印对象信息。 Override public String toString() { return User{ id id , username username \ , email email \ }; } }关键点Date类型对应数据库的datetime/date。createTime的命名采用了驼峰式而数据库字段是create_time这需要在JDBC操作时进行转换我们稍后在DAO层会看到如何处理。2. UserDao.java (数据访问对象)DAO层是连接Java对象和数据库表的桥梁。它封装了所有SQL语句对外提供面向对象的接口。package com.yourdomain.usermgr.dao; import com.yourdomain.usermgr.model.User; import com.yourdomain.usermgr.util.DBUtil; // 假设有一个封装好的数据库工具类 import java.sql.*; import java.util.ArrayList; import java.util.List; public class UserDao { // 1. 新增用户 public boolean addUser(User user) { Connection conn null; PreparedStatement pstmt null; String sql INSERT INTO user(username, password, email, gender, birthday) VALUES(?,?,?,?,?); try { conn DBUtil.getConnection(); // 从工具类获取连接 pstmt conn.prepareStatement(sql); // 设置参数注意序号从1开始 pstmt.setString(1, user.getUsername()); pstmt.setString(2, user.getPassword()); pstmt.setString(3, user.getEmail()); pstmt.setString(4, user.getGender()); // 处理Date类型将java.util.Date转换为java.sql.Date if (user.getBirthday() ! null) { pstmt.setDate(5, new java.sql.Date(user.getBirthday().getTime())); } else { pstmt.setNull(5, Types.DATE); } int affectedRows pstmt.executeUpdate(); // 执行插入返回影响的行数 return affectedRows 0; } catch (SQLException e) { e.printStackTrace(); // 实际项目应使用日志框架如Log4j return false; } finally { DBUtil.close(pstmt, conn); // 在finally块中确保资源被释放 } } // 2. 根据ID删除用户 public boolean deleteUserById(int id) { // 实现类似SQL: DELETE FROM user WHERE id? } // 3. 更新用户信息 public boolean updateUser(User user) { // SQL: UPDATE user SET username?, password?, email?, gender?, birthday? WHERE id? // 注意更新时要设置所有字段或者动态构造SQL更复杂。 } // 4. 根据ID查询单个用户 public User getUserById(int id) { Connection conn null; PreparedStatement pstmt null; ResultSet rs null; User user null; String sql SELECT * FROM user WHERE id?; try { conn DBUtil.getConnection(); pstmt conn.prepareStatement(sql); pstmt.setInt(1, id); rs pstmt.executeQuery(); if (rs.next()) { // 因为id是主键最多只有一条记录所以用if user resultSetToUser(rs); // 将ResultSet的一行转换为User对象 } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(rs, pstmt, conn); } return user; // 没查到就返回null } // 5. 查询所有用户核心中的核心 public ListUser getAllUsers() { ListUser userList new ArrayList(); Connection conn null; PreparedStatement pstmt null; ResultSet rs null; String sql SELECT * FROM user ORDER BY id DESC; // 按ID倒序让新加的显示在前面 try { conn DBUtil.getConnection(); pstmt conn.prepareStatement(sql); rs pstmt.executeQuery(); while (rs.next()) { // 遍历结果集 User user resultSetToUser(rs); userList.add(user); } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(rs, pstmt, conn); } return userList; // 即使为空也返回一个空的List而不是null避免调用方空指针。 } // 6. 将ResultSet转换为User对象的辅助方法重要 private User resultSetToUser(ResultSet rs) throws SQLException { User user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); user.setPassword(rs.getString(password)); // 注意这里取出的密码是明文 user.setEmail(rs.getString(email)); user.setGender(rs.getString(gender)); // 处理数据库中的date/datetime类型 Date birthday rs.getDate(birthday); if (birthday ! null) { user.setBirthday(new Date(birthday.getTime())); // java.sql.Date - java.util.Date } Timestamp createTime rs.getTimestamp(create_time); // 数据库字段是下划线 if (createTime ! null) { user.setCreateTime(new Date(createTime.getTime())); } return user; } }DAO层编码核心经验永远使用PreparedStatement不要用Statement拼接SQL字符串PreparedStatement能有效防止SQL注入攻击并且数据库会对预编译的SQL有更好的执行效率。setString,setInt等方法会自动处理特殊字符。资源释放务必放在finally块Connection,Statement,ResultSet都是宝贵的数据库资源必须确保在任何情况下正常执行或发生异常都能被关闭。否则会导致连接泄漏最终拖垮数据库。查询结果集到对象的转换resultSetToUser这个方法体现了ORM对象关系映射的雏形。手动映射虽然繁琐但让你深刻理解数据和对象的关系。这也是MyBatis等框架帮你做的事情。返回空集合而非nullgetAllUsers()方法返回一个空的ArrayList而不是null。这是《Effective Java》中提倡的最佳实践可以避免调用方进行繁琐的null检查减少NullPointerException。3.2 控制层ControllerServlet的请求分发与处理Servlet是MVC中的C它像路由器一样工作。我们以处理用户列表请求的Servlet为例。package com.yourdomain.usermgr.servlet; import com.yourdomain.usermgr.dao.UserDao; import com.yourdomain.usermgr.model.User; 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 java.io.IOException; import java.util.List; WebServlet(/user/list) // 使用注解配置Servlet的访问路径替代web.xml配置 public class UserListServlet extends HttpServlet { private UserDao userDao new UserDao(); // 持有DAO实例 Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 处理业务逻辑调用DAO获取所有用户数据 ListUser userList userDao.getAllUsers(); // 2. 将数据存入请求域request scope传递给JSP request.setAttribute(userList, userList); // 3. 请求转发到展示页面 request.getRequestDispatcher(/userList.jsp).forward(request, response); } // 通常列表页是GET请求所以我们只重写doGet方法。 // 如果是处理表单提交如添加用户则需要重写doPost方法。 }Servlet编程关键点解析注解WebServlet这是Servlet 3.0之后的标准配置方式比在web.xml中配置servlet和servlet-mapping更简洁直观。/user/list是这个Servlet的URL映射。请求方法doGet/doPost根据HTTP请求方法GET用于获取数据POST用于提交数据来重写对应的方法。这是RESTful风格的雏形。请求域setAttributeServlet和JSP之间传递数据的主要方式。将userList对象以键值对的形式存放在HttpServletRequest对象中转发到的JSP页面可以通过EL表达式${userList}直接获取。请求转发forward服务器端跳转地址栏URL不变仍是/user/list但内容由userList.jsp生成。与之相对的是重定向sendRedirect它是客户端跳转地址栏会变且request域中的数据会丢失。记住一个原则需要传递数据给下一个资源时用转发其他情况如提交后跳回列表页防止重复提交常用重定向。3.3 视图层ViewJSP页面与JSTL/EL表达式JSP页面负责渲染。一个良好的JSP应该尽可能少地出现Java代码块% ... %。我们使用JSTLJSP Standard Tag Library和ELExpression Language来让页面更清晰。userList.jsp (用户列表页)% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !DOCTYPE html html head title用户管理系统 - 用户列表/title link relstylesheet hrefhttps://cdn.jsdelivr.net/npm/bootstrap4.6.0/dist/css/bootstrap.min.css style .container { margin-top: 20px; } .table th, .table td { vertical-align: middle; } /style /head body div classcontainer h2 classmb-4用户列表 a hrefaddUser.jsp classbtn btn-success btn-sm float-right添加新用户/a /h2 c:if test${not empty message} div classalert alert-info${message}/div /c:if table classtable table-bordered table-hover thead classthead-light tr thID/th th用户名/th th邮箱/th th性别/th th出生日期/th th注册时间/th th操作/th /tr /thead tbody %-- 使用JSTL的c:forEach遍历Servlet传过来的userList --% c:forEach items${userList} varuser varStatusstatus tr td${user.id}/td td${user.username}/td td${user.email}/td td c:choose c:when test${user.gender M}男/c:when c:when test${user.gender F}女/c:when c:otherwise未知/c:otherwise /c:choose /td td${user.birthday}/td %-- EL表达式会自动调用Date的toString() --% td${user.createTime}/td td a hrefeditUser.jsp?id${user.id} classbtn btn-warning btn-sm编辑/a a hrefjavascript:if(confirm(确定删除用户【${user.username}】吗)) location.hrefdelete?id${user.id} classbtn btn-danger btn-sm删除/a /td /tr /c:forEach %-- 如果列表为空 --% c:if test${empty userList} tr td colspan7 classtext-center text-muted暂无用户数据/td /tr /c:if /tbody /table /div script srchttps://code.jquery.com/jquery-3.6.0.min.js/script script srchttps://cdn.jsdelivr.net/npm/bootstrap4.6.0/dist/js/bootstrap.bundle.min.js/script /body /htmlJSP页面核心技术点% taglib %指令引入JSTL核心标签库。prefixc表示在页面中使用c:作为前缀来调用这些标签。EL表达式${}这是页面获取数据的灵魂。${userList}会自动从pageContext,request,session,application四个作用域中查找名为userList的属性。它比传统的% request.getAttribute(userList) %简洁安全得多避免了Scriptlet和空指针。JSTL标签c:if条件判断。c:forEach循环遍历集合var定义循环变量items指定要遍历的集合。c:choose/c:when/c:otherwise多分支选择相当于Java的switch-case。前端框架Bootstrap引入了Bootstrap的CSS和JS用极少的代码就能做出美观、响应式的表格和按钮。这对于后端开发者快速搭建可用的管理界面非常友好。删除操作的确认使用javascript:confirm()在真正发起删除请求前给用户一次确认机会这是一个良好的用户体验设计。4. 项目环境搭建与详细配置实操4.1 开发环境与工具链准备工欲善其事必先利其器。一个顺手的开发环境能极大提升效率。JDK确保安装JDK 8或以上版本。在命令行输入java -version和javac -version验证。IDEIntelliJ IDEA Ultimate社区版对JavaWeb支持有限或Eclipse for Enterprise Java Developers。IDEA在智能提示、代码分析和部署方面体验更佳。我以IDEA为例。应用服务器Apache Tomcat 9.x。这是最流行的Servlet容器轻量、稳定、文档齐全。从官网下载ZIP包解压即可。数据库MySQL 5.7或8.0。同时需要一个图形化管理工具如MySQL Workbench、Navicat或DBeaver用于执行建表SQL和查看数据。4.2 在IDEA中创建并配置Web项目这是从零开始的关键一步很多新手在这里卡住。新建项目打开IDEA选择File - New - Project。选择项目类型左侧选择Java Enterprise右侧确保Web Application被勾选并选择正确的Java EE version如Java EE 7或8。点击Next。配置Tomcat在下一步点击Application Server旁边的New...选择你解压的Tomcat目录。IDEA会自动识别。完成创建输入项目名如UserManagementSystem选择项目位置点击Finish。项目创建后你会看到标准的目录结构UserManagementSystem ├── src │ └── (你的Java包和类放在这里如 com.yourdomain.usermgr) ├── web │ ├── WEB-INF │ │ ├── web.xml (部署描述符Servlet 3.0后可选) │ │ └── lib (存放项目依赖的Jar包如数据库驱动) │ └── index.jsp (欢迎页面) └── External Libraries (IDEA管理的库)添加依赖Jar包这是最容易出错的地方。你需要手动下载以下Jar包并放入web/WEB-INF/lib/目录下mysql-connector-java-8.0.xx.jarMySQL数据库驱动。jstl-1.2.jar和taglibs-standard-impl-1.2.5.jarJSTL标签库的实现。可选druid-1.2.8.jar如果你打算使用阿里巴巴的Druid连接池。关键操作在IDEA中选中这些Jar包右键选择Add as Library...将它们添加到项目的类路径中。否则代码编译会报错“找不到类”。4.3 数据库连接工具类DBUtil实现直接在每个DAO方法里写连接代码会导致大量重复且资源管理混乱。我们需要一个工具类来集中管理数据库连接。package com.yourdomain.usermgr.util; import java.io.IOException; import java.io.InputStream; import java.sql.*; import java.util.Properties; public class DBUtil { private static String url; private static String user; private static String password; private static String driver; // 静态代码块在类加载时执行一次用于读取配置文件并注册驱动 static { try { // 1. 读取配置文件 db.properties Properties props new Properties(); // 使用类加载器读取资源文件确保无论在IDE还是服务器上都能找到 InputStream in DBUtil.class.getClassLoader().getResourceAsStream(db.properties); props.load(in); // 2. 获取配置信息 url props.getProperty(jdbc.url); user props.getProperty(jdbc.username); password props.getProperty(jdbc.password); driver props.getProperty(jdbc.driver); // 3. 注册驱动 (对于MySQL Connector/J 6.0这步可以省略但写上更稳妥) Class.forName(driver); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); throw new ExceptionInInitializerError(数据库配置初始化失败); } } // 获取数据库连接 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } // 重载的关闭方法用于关闭Connection和Statement public static void close(Statement stmt, Connection conn) { close(null, stmt, conn); } // 通用的资源关闭方法注意关闭顺序ResultSet - Statement - Connection public static void close(ResultSet rs, Statement stmt, Connection conn) { if (rs ! null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt ! null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }对应的配置文件src/db.properties# 数据库连接配置 jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/user_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.passwordyour_password工具类设计精讲为什么用静态代码块数据库连接参数只需要在程序启动时加载一次放在静态代码块中最合适。为什么用Properties文件将配置与代码分离。当数据库地址、密码变更时无需重新编译Java代码只需修改配置文件。这在部署到不同环境开发、测试、生产时至关重要。Class.forName(driver)的作用早期JDBC规范要求显式加载驱动类。对于MySQL Connector/J 6.0以上版本由于SPI机制这行代码可以省略。但保留它是个好习惯能明确指明所使用的驱动且兼容旧版本。关闭资源的顺序必须按照ResultSet - Statement - Connection的逆序关闭。Connection是最底层的资源如果先关闭它由它产生的Statement和ResultSet可能无法正常关闭。异常处理关闭资源时发生的SQLException通常不重要比如连接早已超时断开所以我们只打印日志不向上抛出避免掩盖主逻辑的异常。5. 功能扩展与高级特性实现一个基础CRUD做完后可以尝试加入以下特性让项目更接近实用。5.1 用户登录与会话管理Session管理系统必须有权限控制。我们增加一个登录功能。创建登录Servlet (LoginServlet)WebServlet(/login) public class LoginServlet extends HttpServlet { private UserDao userDao new UserDao(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); // 这里应该验证密码实际是验证密码哈希值 User user userDao.findUserByUsernameAndPassword(username, password); if (user ! null) { // 登录成功将用户信息存入Session HttpSession session req.getSession(); session.setAttribute(loginUser, user); // 跳转到主页或用户列表页 resp.sendRedirect(req.getContextPath() /user/list); } else { // 登录失败返回错误信息 req.setAttribute(errorMsg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }创建登录页面 (login.jsp)一个简单的表单提交到/login。添加过滤器 (AuthFilter)保护所有需要登录才能访问的页面如/user/*。WebFilter(/*) // 过滤所有请求 public class AuthFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; String uri req.getRequestURI(); // 放行登录相关、静态资源等请求 if (uri.endsWith(login.jsp) || uri.endsWith(login) || uri.contains(/css/) || uri.contains(/js/)) { chain.doFilter(request, response); return; } // 检查Session中是否有登录用户 HttpSession session req.getSession(false); // 不创建新session if (session null || session.getAttribute(loginUser) null) { // 未登录重定向到登录页 resp.sendRedirect(req.getContextPath() /login.jsp); return; } // 已登录放行 chain.doFilter(request, response); } }过滤器原理它像一道安检门在所有Servlet处理请求之前和之后执行。我们在doFilter方法中判断用户是否登录未登录则拦截并跳转。5.2 密码加密存储明文存储密码是严重的安全漏洞。使用BCrypt算法是当前的最佳实践。添加依赖下载bcrypt-xxx.jar并放入lib目录。创建密码工具类import org.mindrot.jbcrypt.BCrypt; public class PasswordUtil { // 生成加密后的密码 public static String hashPassword(String plainPassword) { return BCrypt.hashpw(plainPassword, BCrypt.gensalt()); } // 验证密码 public static boolean checkPassword(String plainPassword, String hashedPassword) { return BCrypt.checkpw(plainPassword, hashedPassword); } }修改DAO层在addUser方法中调用PasswordUtil.hashPassword(user.getPassword())得到密文再存入数据库。在登录验证的findUserByUsernameAndPassword方法中先根据用户名查出用户包含密文密码然后调用PasswordUtil.checkPassword(用户输入的明文, 数据库中的密文)进行验证。5.3 分页查询功能当用户数据成千上万时一次性查询所有数据是不可接受的。必须实现分页。修改DAO接口增加一个方法ListUser getUsersByPage(int pageNo, int pageSize)。修改SQL使用LIMIT子句。SELECT * FROM user ORDER BY id DESC LIMIT ?, ?。第一个?是偏移量(pageNo-1) * pageSize第二个?是pageSize。修改Servlet接收前端传递的page参数默认为1调用DAO的分页方法。修改JSP在页面底部生成分页导航条计算总页数并生成类似[上一页] 1 2 3 ... [下一页]的链接每个链接都指向自己并带上page参数。6. 项目部署、调试与常见问题排查6.1 将项目打包成WAR并部署到独立Tomcat在IDEA中开发调试很方便但最终项目需要部署到独立的服务器上。打包在IDEA中点击Build - Build Artifacts...选择你的项目然后Build。这会在out/artifacts/目录下生成一个YourProjectName.war文件。部署将war文件复制到独立Tomcat的webapps/目录下。启动运行Tomcat的bin/startup.bat(Windows) 或bin/startup.sh(Linux/Mac)。访问打开浏览器访问http://localhost:8080/YourProjectName/。6.2 开发与调试中的高频问题及解决方案以下是我在带新人过程中他们最容易踩的坑问题现象可能原因排查步骤与解决方案404错误页面找不到1. URL路径写错。2. 项目未成功部署。3.web.xml或WebServlet配置错误。1. 检查浏览器地址栏URL是否包含项目上下文路径Context Path。2. 查看Tomcat日志logs/catalina.out确认项目是否启动成功。3. 检查Servlet的注解路径或web.xml映射是否正确。500错误服务器内部错误1. Java代码运行时异常空指针、类找不到等。2. JSP编译错误。1.这是最重要的线索来源立即查看Tomcat日志logs/localhost.yyyy-MM-dd.log或控制台输出的完整异常堆栈信息。2. 根据堆栈信息定位到具体的Java文件和行号逐行检查。数据库连接失败1.db.properties文件未找到或路径错误。2. 数据库服务未启动。3. 连接参数IP、端口、用户名、密码错误。4. 驱动Jar包未正确添加到类路径。1. 确认db.properties文件在src目录下且编译后会在WEB-INF/classes中。2. 用命令行或图形工具测试是否能连上MySQL。3. 检查DBUtil静态代码块中的日志看配置是否加载成功。4. 确认mysql-connector-java.jar在WEB-INF/lib下且被Add as Library。JSTL标签或EL表达式不生效1. 未引入JSTL的Jar包。2. Jar包版本冲突或未添加到类路径。3. 页面未声明taglib指令。1. 确保jstl.jar和standard.jar或对应的实现包在lib目录下。2. 检查IDEA的Project Structure - Modules - Dependencies确认这些Jar包被包含。3. 检查JSP文件头部的% taglib %指令的URI是否正确。中文乱码1. 数据库、连接、页面三处字符集不统一。2. Servlet未设置请求/响应编码。终极解决方案四步曲1.数据库/表创建时指定CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci。2.连接字符串在JDBC URL中加入?characterEncodingutf8useUnicodetrue。3.Servlet在doGet/doPost方法第一行加上request.setCharacterEncoding(UTF-8);和response.setContentType(text/html;charsetUTF-8);。4.JSP页面在文件顶部加上% page pageEncodingUTF-8 contentTypetext/html; charsetUTF-8%。删除或更新后页面数据没变化1. 数据库操作成功但页面缓存了旧数据。2. Servlet操作成功后跳转方式错误应使用重定向而非转发。1. 在浏览器中按CtrlF5强制刷新。2.重要原则对于POST请求如添加、删除、更新处理成功后应该使用response.sendRedirect(list)重定向到列表页。这能防止用户刷新页面时重复提交表单Post/Redirect/Get模式。6.3 性能与安全考量进阶当这个项目跑通后你可以思考如何让它变得更好数据库连接池现在的DBUtil每次操作都新建连接性能极差。应引入Druid或HikariCP连接池在应用启动时创建一批连接用时取出用完归还。日志框架将代码中的System.out.println和e.printStackTrace()替换为Log4j2或SLF4J可以灵活控制日志级别和输出目的地。输入验证在Servlet接收参数后必须进行验证非空、长度、格式等防止无效或恶意数据进入系统。这既是健壮性要求也是安全防线如防止XSS脚本注入。异常统一处理可以编写一个全局的异常处理器实现Servlet的ErrorPage配置或使用过滤器将未捕获的异常转化为友好的错误页面而不是暴露堆栈信息给用户。前端验证在JSP中使用JavaScript进行初步验证如必填项可以提高用户体验但切记前端验证不可靠后端必须进行二次验证。这个“用户管理系统”项目就像一副完整的骨架。你现在已经学会了如何把每一块骨头技术点拼接起来。接下来要做的就是在这个骨架上增加肌肉和皮肤更多的业务功能、更好的UI、更优的架构。当你能够不参考任何教程独立从头到尾实现它并且能清晰解释每一行代码的作用时你的JavaWeb入门阶段才算真正圆满毕业了。

相关新闻

揭秘网站建设包括哪些内容从0到1的全流程解析与避坑指南

揭秘网站建设包括哪些内容从0到1的全流程解析与避坑指南

2026/8/14 4:12:13

很多人以为,建个网站就像搭积木,找个模板套一下,买个域名填进去,完事了。如果真这么简单,那世界上早就没有烂尾项目了。作为在这个行业里摸爬滚打多年的老兵,我见过太多老板或市场负责人,兴冲冲地拿着几万块钱预算来找我,结果做出来的东西惨不忍睹,不仅没用,反而成了…

一招告别卡顿:Mem Reduct 3.5.2 免费内存清理工具完整使用教程

一招告别卡顿:Mem Reduct 3.5.2 免费内存清理工具完整使用教程

2026/8/14 4:12:13

一招告别卡顿:Mem Reduct 3.5.2 免费内存清理工具完整使用教程 【免费下载链接】memreduct Lightweight real-time memory management application to monitor and clean system memory on your computer. 项目地址: https://gitcode.com/gh_mirrors/me/memreduct…

SQL进阶:掌握WHERE、GROUP BY与HAVING,实现高效数据查询与分析

SQL进阶:掌握WHERE、GROUP BY与HAVING,实现高效数据查询与分析

2026/8/14 4:12:13

如果你刚开始学 SQL,可能会觉得它很简单——不就是 SELECT * FROM table 吗?但当你真正面对一个业务系统,需要从海量数据里快速、准确地找到关键信息,或者需要设计一个既能支撑业务增长又易于维护的数据库结构时,你会…

【TongHttpServer抓包工具-nettool的使用】

【TongHttpServer抓包工具-nettool的使用】

2026/8/14 5:22:16

nettool的使用 netool是一个小巧的抓包工具,总共大小只有20K,不依赖任何第三方库,能解析HTTP请求。可以用来抓取浏览器->THS的HTTP请求或THS->TongWeb的请求。 经常有客户问单独访问TongWeb正常,通过THS代理后就不正常了…

数据库应用程序的可视化设计

数据库应用程序的可视化设计

2026/8/14 5:22:16

学习目标: C#数据库应用程序的开发环境的构成服务器资源管理器类型化数据集创建简单的数据库应用程序水晶报表 Notes: 类型化数据集利用服务器资源管理器建立数据连接 利用服务器资源管理器可执行的任务如下: 1)打开数据连接。 2)…

Google Pixel 11 Pro Fold 与三星 Galaxy Z Fold 8 大比拼,谁更值得买?

Google Pixel 11 Pro Fold 与三星 Galaxy Z Fold 8 大比拼,谁更值得买?

2026/8/14 5:22:16

Google 与三星夏季新品盘点如今 Google 和三星都举办了年度夏季活动,是时候盘点一下两家公司各自推出的产品了。两家都发布了新一代的书本式折叠屏手机,但三星推出了与众不同的 Samsung Galaxy Z Fold 8,它比以往的机型更短更宽。这款手机配置…

主数据同步、用户同步等

主数据同步、用户同步等

2026/8/14 5:22:16

文章目录通用方案细节同步任务标志这是个常见的需求,用户、机构等源头在主数据,各业务系统自行拉取。有两种获取方式:1、接口拉取 #2、mq实时分发 #通用方案为: 初始化时接口全量拉取后续mq实时接收 当然,mq也可以全量…

数据结构优化:从二叉树遍历到线索化实现与避坑指南

数据结构优化:从二叉树遍历到线索化实现与避坑指南

2026/8/14 5:22:16

1. 从“遍历”到“线索”:为什么我们需要线索二叉树?如果你写过二叉树的遍历代码,无论是递归还是非递归,一定对那种“走一步,退一步”的感觉印象深刻。为了访问某个节点的左子树,你得先记住它的右子树还没看…

真空回流炉工艺方案定制:流程解析与参数优化实践

真空回流炉工艺方案定制:流程解析与参数优化实践

2026/8/14 5:12:15

真空回流炉工艺方案定制的核心流程拆解 在半导体封测环节中,真空回流炉工艺方案定制服务并非简单的设备参数调校,而是基于产品结构、焊料特性与可靠性要求反向推导的工程化过程。完整的定制流程通常从前期的热预算评估开始,工程师需结合基板厚…

比较好的亚太EMBA,问了6位校友师资差别真的挺大

比较好的亚太EMBA,问了6位校友师资差别真的挺大

2026/8/13 11:01:28

比较好的亚太EMBA核心差异先看什么?对于希望兼顾工作与系统管理能力提升的亚太区高管而言,筛选匹配度高的EMBA项目时,师资配置是决定学习体验与实际收获的核心要素之一。我们结合3-4个公开信息透明、办学历史较长的亚太区主流EMBA项目特点&am…

备考3个月对比6份资料 海外游学的亚洲EMBA面试注意点

备考3个月对比6份资料 海外游学的亚洲EMBA面试注意点

2026/8/11 8:44:43

备考海外游学的亚洲EMBA面试,核心要围绕项目国际化设计逻辑、个人跨文化管理经验匹配度两个维度准备,避免把游学模块等同于普通旅游参访的认知偏差。不少备考者花3个月对比6份资料,却容易忽略面试官对“国际视野落地能力”的考察——比如香港…

比较好的国内EMBA,问了二十位校友聊透人脉价值

比较好的国内EMBA,问了二十位校友聊透人脉价值

2026/8/13 17:17:06

比较好的国内EMBA核心差异体现在哪些方面?比较好的国内EMBA的核心长期价值,很大程度上依托于校友网络的连接质量与资源生态的活跃度,这也是不少高管在择校时优先考量的因素。我们结合3-4个市场关注度较高的项目公开信息,从课程、师…

大连网站建设找简维科技:为您打造懂业务更懂用户的数字化转型引擎

大连网站建设找简维科技:为您打造懂业务更懂用户的数字化转型引擎

2026/8/14 0:01:53

在这个数字化浪潮席卷全球的今天,企业想要在激烈的市场竞争中站稳脚跟,拥有一张好看的“数字名片”已经远远不够了。很多老板在刚开始接触互联网业务时,都有一个共同的困惑:为什么我花了钱建的网站,就像是在真空中自嗨?访客进来转了两圈就跑了,线索石沉大海,甚至连客服…

临沂网站建设铭镇:深耕本土数字生态,以匠心铸就企业品牌核心竞争力

临沂网站建设铭镇:深耕本土数字生态,以匠心铸就企业品牌核心竞争力

2026/8/14 0:01:54

在这个流量为王、视觉至上的互联网时代,对于临沂乃至整个山东乃至全国的传统中小企业来说,拥有一张精美的“数字名片”早已不再是可选项,而是生存的必答题。每当夜幕降临,沂河两岸灯火辉煌,物流之都的喧嚣逐渐沉淀为对未来的思考。我们常常听到老板们在茶余饭后探讨:为什…

Flutter与OpenHarmony实现剧本杀组队表单开发实战

Flutter与OpenHarmony实现剧本杀组队表单开发实战

2026/8/14 0:01:54

1. 项目概述在移动应用开发领域,跨平台框架Flutter因其高效的开发体验和出色的性能表现,已经成为众多开发者的首选。而OpenHarmony作为新兴的操作系统平台,其开放性和灵活性为开发者提供了全新的可能性。本文将聚焦于一个实际应用场景——剧本…

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

2026/8/8 5:07:31

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…

导师推荐!2026最新AI论文工具测评与实用推荐

导师推荐!2026最新AI论文工具测评与实用推荐

2026/8/9 13:42:46

2026年真正好用的AI论文工具,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

告别游戏崩溃:XCOM 2模组管理器的智能革命

告别游戏崩溃:XCOM 2模组管理器的智能革命

2026/8/8 2:30:15

告别游戏崩溃:XCOM 2模组管理器的智能革命 【免费下载链接】xcom2-launcher The Alternative Mod Launcher (AML) is a replacement for the default game launchers from XCOM 2 and XCOM Chimera Squad. 项目地址: https://gitcode.com/gh_mirrors/xc/xcom2-lau…