资讯详情

资讯详情

Java Web基础:JDBC+Servlet+JSP数据流转全链路解析

简介这是一套基于JDBCJSPServlet技术栈开发的完整图书管理系统实战项目面向Java Web初学者及高校课程设计学生解决数据库连接、前后端交互与业务逻辑分层实现等核心教学难点。资源包含141个文件涵盖24个Java源码如BookDaoImpl、LoginServlet、48个编译后class文件、10个JSP页面、11个XML配置文件、5个CSS样式文件及1个SQL建库脚本结构清晰体现MVC分层思想压缩包仅5.12MB轻量易部署。已有100人学习下载项目已通过实测验证可直接运行无需修改代码或配置配套详细项目说明文档覆盖环境搭建、数据库导入、功能模块说明与常见问题提示特别适合作为高分课程设计或期末大作业交付方案。1. 这不是过时的“老古董”而是理解 Web 层数据流转的最简可靠路径当你在毕业设计选题列表里看到“基于 JDBCJSPServlet 的图书管理系统”第一反应可能是这技术栈是不是该进博物馆了但现实恰恰相反——它仍是 Java Web 基础能力的“压力测试仪”不依赖 Spring Boot 自动装配、不隐藏连接池和请求生命周期、不抽象掉HttpServletRequest到RequestParam的映射逻辑。一个能跑通增删改查、带登录校验、分页展示图书列表的完整系统恰恰暴露了现代框架底层真正封装了什么。它适合两类人刚学完《Java Web 编程》想验证知识闭环的在校生以及需要快速搭建轻量管理后台、又不愿引入复杂依赖的运维或 DBA。本项目不是为生产环境而生而是为“看懂 request 怎么变成 SQL、SQL 结果怎么变成 table 行、table 行怎么被 JSPc:forEach渲染”提供最小可执行证据链。核心价值不在功能多炫而在每一步都可打断、可调试、可替换——比如把 MySQL 换成达梦DM、把 JSP 换成纯 HTMLAJAX、把 Servlet 手动注册换成web.xml声明式配置全都不需要重写业务逻辑。2. 从数据库建模到 JDBC 连接让数据层真正“活”起来2.1 图书管理系统典型表结构设计与字段语义对齐图书管理系统虽小但必须体现关系型数据库建模基本功。常见三张核心表需满足事务一致性与查询效率平衡表名主键关键外键核心字段说明设计要点bookid(BIGINT AUTO_INCREMENT)—isbn(VARCHAR(17), UNIQUE),title,author,publisher,price(DECIMAL(8,2)),stock(INT)isbn必须加唯一索引避免重复录入price用 DECIMAL 防止浮点精度丢失userid(BIGINT AUTO_INCREMENT)—username(VARCHAR(50), UNIQUE),password(VARCHAR(100), 存储 BCrypt 加密后字符串),role(ENUM(admin,librarian,reader))密码绝不存明文role用 ENUM 而非 INT提升可读性与约束力borrow_recordid(BIGINT AUTO_INCREMENT)book_id→book.id,user_id→user.idborrow_date(DATE),return_date(DATE NULLABLE),status(TINYINT, 0未归还, 1已归还)外键必须设 ON DELETE RESTRICT防止误删图书导致借阅记录悬空提示若使用达梦数据库DM需将ENUM替换为VARCHAR(20)并在应用层校验AUTO_INCREMENT改为IDENTITY(1,1)所有表名和字段名建议用双引号包裹如book避免达梦关键字冲突。2.2 JDBC 连接池初始化与驱动加载的健壮写法单纯Class.forName(com.mysql.cj.jdbc.Driver)已是历史。现代 JDBC 实践要求显式管理连接生命周期并规避No suitable driver found错误。以下是在ServletContextListener中初始化 HikariCP 连接池的标准做法web.xml中声明监听器// src/main/java/com/example/listener/DBConnectionListener.java public class DBConnectionListener implements ServletContextListener { private static HikariDataSource dataSource; Override public void contextInitialized(ServletContextEvent sce) { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/library_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8); config.setUsername(root); config.setPassword(your_password); config.setDriverClassName(com.mysql.cj.jdbc.Driver); // 显式指定驱动类避免自动发现失败 config.setMaximumPoolSize(10); config.setMinimumIdle(2); config.setConnectionTimeout(30000); // 30秒超时 config.setIdleTimeout(600000); // 空闲10分钟回收 config.setMaxLifetime(1800000); // 连接最大存活30分钟 config.addDataSourceProperty(cachePrepStmts, true); config.addDataSourceProperty(prepStmtCacheSize, 250); config.addDataSourceProperty(prepStmtCacheSqlLimit, 2048); dataSource new HikariDataSource(config); sce.getServletContext().setAttribute(dataSource, dataSource); System.out.println(✅ HikariCP 连接池初始化成功JDBC URL: config.getJdbcUrl()); } Override public void contextDestroyed(ServletContextEvent sce) { if (dataSource ! null !dataSource.isClosed()) { dataSource.close(); } } public static DataSource getDataSource() { return dataSource; } }关键参数说明useSSLfalse开发环境关闭 SSL生产环境必须启用serverTimezoneAsia/Shanghai强制时区对齐避免java.sql.Timestamp插入时间错乱cachePrepStmtstrue开启预编译语句缓存显著提升重复 SQL 执行效率prepStmtCacheSize250缓存 250 条预编译语句需根据实际 SQL 复杂度调整connectionTimeout30000连接获取超时单位毫秒避免线程长期阻塞。注意若遇到No suitable driver found for jdbc:dm://...确认达梦 JDBC 驱动DmJdbcDriver18.jar已放入WEB-INF/lib/目录且setDriverClassName(dm.jdbc.driver.DmDriver)正确设置达梦 URL 格式为jdbc:dm://192.168.102.161:30184端口默认 5236但需与服务端实际监听端口一致。2.3 封装通用 DAO 层避免每个 Servlet 重复写 try-catch 和资源释放直接在 Servlet 中写Connection conn dataSource.getConnection()是反模式。应提取为BaseDAO统一处理连接获取、事务控制与异常转换// src/main/java/com/example/dao/BaseDAO.java public abstract class BaseDAO { protected Connection getConnection() throws SQLException { ServletContext sc getServletContext(); DataSource ds (DataSource) sc.getAttribute(dataSource); return ds.getConnection(); } protected void closeResources(Connection conn, PreparedStatement ps, ResultSet rs) { if (rs ! null) try { rs.close(); } catch (SQLException e) { /* ignore */ } if (ps ! null) try { ps.close(); } catch (SQLException e) { /* ignore */ } if (conn ! null) try { conn.close(); } catch (SQLException e) { /* ignore */ } } // 事务模板方法子类只需实现 doInTransaction protected T T executeInTransaction(TransactionCallbackT callback) throws SQLException { Connection conn getConnection(); boolean autoCommit conn.getAutoCommit(); try { conn.setAutoCommit(false); T result callback.doInTransaction(conn); conn.commit(); return result; } catch (Exception e) { conn.rollback(); throw new SQLException(事务执行失败, e); } finally { conn.setAutoCommit(autoCommit); closeResources(conn, null, null); } } }// 使用示例BookDAO 中的批量插入 public class BookDAO extends BaseDAO { public int batchInsert(ListBook books) throws SQLException { String sql INSERT INTO book (isbn, title, author, publisher, price, stock) VALUES (?, ?, ?, ?, ?, ?); return executeInTransaction(conn - { PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); for (Book book : books) { ps.setString(1, book.getIsbn()); ps.setString(2, book.getTitle()); ps.setString(3, book.getAuthor()); ps.setString(4, book.getPublisher()); ps.setBigDecimal(5, book.getPrice()); ps.setInt(6, book.getStock()); ps.addBatch(); } int[] results ps.executeBatch(); ps.close(); return Arrays.stream(results).sum(); }); } }为什么必须封装避免SQLException泄漏到 Servlet 层统一转为RuntimeException或自定义业务异常executeInTransaction强制事务边界防止部分更新成功、部分失败导致数据不一致closeResources确保ResultSet/PreparedStatement/Connection严格按顺序关闭杜绝连接泄漏这是could not open client transport类错误的主因。3. Servlet 请求路由与 JSP 渲染构建清晰的 MVC 数据流3.1 Servlet 分发机制从 web.xml 到注解式映射的演进与兼容早期项目依赖web.xml声明式配置但现代开发更倾向WebServlet注解。二者本质相同只是注册时机不同。以下是两种方式的对比与迁移要点方式一web.xml 声明兼容老项目!-- WEB-INF/web.xml -- servlet servlet-nameBookListServlet/servlet-name servlet-classcom.example.servlet.BookListServlet/servlet-class /servlet servlet-mapping servlet-nameBookListServlet/servlet-name url-pattern/book/list/url-pattern /servlet-mapping方式二WebServlet 注解推荐新开发// src/main/java/com/example/servlet/BookListServlet.java WebServlet(/book/list) public class BookListServlet extends HttpServlet { private BookDAO bookDAO new BookDAO(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { ListBook books bookDAO.findAll(); // 查询全部图书 req.setAttribute(bookList, books); // 绑定到 request scope req.getRequestDispatcher(/WEB-INF/jsp/book/list.jsp).forward(req, resp); } catch (SQLException e) { req.setAttribute(errorMessage, 查询图书列表失败 e.getMessage()); req.getRequestDispatcher(/WEB-INF/jsp/error.jsp).forward(req, resp); } } }关键差异说明web.xml方式需重启服务器才能生效WebServlet在热部署时可即时识别需 IDE 支持WebServlet的urlPatterns支持通配符如/book/*但需注意路径匹配优先级若项目混合使用两种方式web.xml中的servlet会覆盖同名注解造成意外交互。3.2 JSP 页面安全渲染防 SQL 注入与 XSS 的基础防线JSP 不是“前端模板”而是服务器端动态生成 HTML 的引擎。常见错误是直接拼接用户输入!-- ❌ 危险直接输出未经处理的 request.getParameter -- % request.getParameter(searchKeyword) % !-- ✅ 安全使用 JSTL c:out 转义 HTML 特殊字符 -- c:out value${param.searchKeyword} / !-- ✅ 更佳结合 EL 表达式与 JSTL 标签 -- c:forEach items${bookList} varbook tr tdc:out value${book.isbn} //td tdc:out value${book.title} //td tdc:out value${book.author} //td tdfmt:formatNumber value${book.price} pattern¥#,##0.00 //td tdc:out value${book.stock} //td /tr /c:forEach必须启用的 JSTL 支持在WEB-INF/web.xml中声明 JSTL 1.2 版本Tomcat 8 默认支持jsp-config jsp-property-group url-pattern*.jsp/url-pattern el-enabledtrue/el-enabled scripting-invalidfalse/scripting-invalid include-prelude/WEB-INF/jsp/common/header.jspf/include-prelude include-coda/WEB-INF/jsp/common/footer.jspf/include-coda /jsp-property-group /jsp-config并在 JSP 顶部引入标签库% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt %提示若出现javax.servlet.ServletException: java.lang.NoClassDefFoundError: javax/servlet/jsp/jstl/core/Config说明 JSTL JAR 缺失。Maven 项目需添加dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency3.3 登录状态拦截用 Filter 实现跨 Servlet 的权限控制不能每个 Servlet 都写if (session.getAttribute(user) null)。标准做法是编写LoginFilter统一拦截/admin/*路径// src/main/java/com/example/filter/LoginFilter.java WebFilter(/admin/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); if (session null || session.getAttribute(user) null) { request.setAttribute(errorMessage, 请先登录); request.getRequestDispatcher(/WEB-INF/jsp/login.jsp).forward(request, response); return; } // 用户已登录放行 chain.doFilter(req, resp); } }Filter 链执行顺序WebFilter的dispatcherTypes默认为REQUEST即只拦截客户端直接请求若需拦截forward或include需显式设置WebFilter(dispatcherTypes {DispatcherType.REQUEST, DispatcherType.FORWARD})多个 Filter 按WebFilter的filterName字典序或web.xml中声明顺序执行。4. 项目说明文档落地让源码真正可复现、可验证4.1 数据库初始化脚本的幂等性设计与执行验证项目说明中常忽略“第一次运行如何建库”。正确做法是提供init-db.sql并确保其可重复执行-- init-db.sql -- 创建数据库若不存在 CREATE DATABASE IF NOT EXISTS library_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 切换数据库 USE library_db; -- 创建 book 表若不存在 CREATE TABLE IF NOT EXISTS book ( id BIGINT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(17) UNIQUE NOT NULL, title VARCHAR(200) NOT NULL, author VARCHAR(100) NOT NULL, publisher VARCHAR(100), price DECIMAL(8,2) NOT NULL DEFAULT 0.00, stock INT NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 插入初始测试数据仅当表为空时 INSERT INTO book (isbn, title, author, publisher, price, stock) SELECT * FROM (SELECT 978-7-04-050694-1, 深入理解计算机系统, Randal E. Bryant, 机械工业出版社, 129.00, 50) AS tmp WHERE NOT EXISTS (SELECT 1 FROM book WHERE isbn 978-7-04-050694-1);执行验证命令# Linux/macOS 下一键初始化 mysql -u root -p init-db.sql # 验证表结构 mysql -u root -p -e DESCRIBE library_db.book; # 验证初始数据 mysql -u root -p -e SELECT COUNT(*) FROM library_db.book;注意若使用 Navicat 连接达梦数据库需在连接属性中勾选“使用 DM 兼容模式”并手动执行CREATE SCHEMA IF NOT EXISTS library_db;替代CREATE DATABASE。4.2 项目说明文档必备的 5 个可验证检查点一份合格的README.md或项目说明.txt必须包含以下可立即验证的条目缺一不可检查点验证命令/操作预期结果常见失败原因JDK 版本java -version输出1.8.0_XXX或11.0.X使用 JDK 17 运行 JSP 会报org.apache.jasper.JasperException: Unable to compile class for JSPTomcat 版本查看apache-tomcat-x.x/bin/version.shTomcat 8.5 或 9.0Tomcat 10 默认使用 Jakarta EE 9 命名空间javax.servlet.*包需升级为jakarta.servlet.*数据库连接启动 Tomcat 后访问http://localhost:8080/library/book/list返回图书列表 HTML 页面dataSource未注入ServletContext或web.xml中未注册DBConnectionListener登录功能输入admin/admin登录跳转至/admin/dashboard.jspUserDAO.authenticate()中密码比对逻辑错误或BCrypt.checkpw()参数顺序颠倒增删改查闭环在/admin/book/add.jsp提交新书 → 查看列表 → 点击删除 → 刷新列表新书消失数据库book表记录数减 1BookDAO.delete()未调用executeUpdate()或 SQL 语句缺少WHERE id ?特别提醒若启动时报java.lang.ClassNotFoundException: org.springframework.boot.web.servlet.support.SpringBootServletInitializer说明项目被误当成 Spring Boot 工程导入。请确认pom.xml中无spring-boot-starter-web依赖且web.xml存在且正确配置listener和servlet。5. 达梦/Oracle 兼容性改造一次修改多库适配5.1 SQL 语法层适配用数据库无关的 JDBC 方法替代方言不同数据库对分页、字符串函数、空值处理存在差异。最佳实践是用 JDBC 标准 API 代替 SQL 方言场景MySQL 写法达梦/Oracle 问题JDBC 标准方案代码示例分页查询SELECT * FROM book LIMIT 10 OFFSET 20达梦用ROWNUMOracle 用ROW_NUMBER()PreparedStatement.setFetchSize()ResultSet.absolute()ps.setFetchSize(10); rs.absolute(21);获取当前时间NOW()达梦用SYSDATEOracle 用SYSDATEjava.time.LocalDateTime.now()LocalDateTime now LocalDateTime.now(); ps.setObject(1, now);空值判断IS NULL通用但达梦对CHAR字段NULL和空字符串区分严格ResultSet.wasNull()String name rs.getString(name); if (rs.wasNull()) name ;分页增强版 DAO 示例public ListBook findBooksByPage(int page, int pageSize) throws SQLException { String sql SELECT id, isbn, title, author, publisher, price, stock FROM book ORDER BY id; return executeInTransaction(conn - { PreparedStatement ps conn.prepareStatement(sql); ps.setFetchSize(pageSize); ResultSet rs ps.executeQuery(); // 跳过前 (page-1)*pageSize 行 if (rs.absolute((page - 1) * pageSize 1)) { ListBook list new ArrayList(); int count 0; while (rs.next() count pageSize) { list.add(new Book( rs.getLong(id), rs.getString(isbn), rs.getString(title), rs.getString(author), rs.getString(publisher), rs.getBigDecimal(price), rs.getInt(stock) )); count; } return list; } return Collections.emptyList(); }); }5.2 驱动与连接参数的动态切换策略硬编码 JDBC URL 是维护噩梦。应通过ServletContext初始化参数或System.getProperty()动态选择// 在 web.xml 中配置数据库类型 context-param param-namedb.type/param-name param-valuemysql/param-value !-- 可改为 dm 或 oracle -- /context-param// DBConnectionListener 中读取并构建 URL String dbType sc.getInitParameter(db.type); switch (dbType.toLowerCase()) { case mysql: config.setJdbcUrl(jdbc:mysql://localhost:3306/library_db?...); config.setDriverClassName(com.mysql.cj.jdbc.Driver); break; case dm: config.setJdbcUrl(jdbc:dm://192.168.102.161:5236?schemaSYSDBA); config.setDriverClassName(dm.jdbc.driver.DmDriver); break; case oracle: config.setJdbcUrl(jdbc:oracle:thin:localhost:1521:orcl); config.setDriverClassName(oracle.jdbc.driver.OracleDriver); break; }达梦连接关键参数schemaSYSDBA指定默认模式避免ORA-00942: table or view does not existuseUnicodetruecharacterEncodingUTF-8强制 UTF-8 编码loginTimeout10登录超时 10 秒防止网络抖动卡死。提示Navicat 连接达梦时若提示“连接被拒绝”检查达梦服务是否启动./DmServiceDMSERVER start防火墙是否开放 5236 端口以及dm.ini中ENABLE_MONITOR1是否启用。5.3 项目打包与部署验证清单最终交付的library.war必须通过以下 5 步验证解压检查jar -tf library.war | grep -E (WEB-INF/lib/|WEB-INF/web.xml|jsp/)→ 确认HikariCP.jar、mysql-connector-java.jar或DmJdbcDriver18.jar、jstl.jar存在类路径扫描jar -tf library.war | grep \.class$ | head -10→ 确保com/example/servlet/BookListServlet.class等核心类在正确包路径web.xml 验证jar -xf library.war WEB-INF/web.xml cat WEB-INF/web.xml | grep -A5 listener→ 确认DBConnectionListener和LoginFilter已声明JSP 编译检查部署后访问http://localhost:8080/library/jsp/test.jsp内容% OK %→ 若返回 500 错误检查tomcat/work/Catalina/localhost/library/org/apache/jsp/下.java和.class文件生成情况数据库连通性在 Tomcat 日志中搜索✅ HikariCP 连接池初始化成功→ 若无此日志检查DBConnectionListener.contextInitialized()是否被调用或web.xml中listener是否遗漏完成以上全部步骤你得到的就不再是一个“过时的毕业设计模板”而是一套可审计、可替换、可演进的 Java Web 基础能力验证体系——它不承诺高并发但保证每一行代码都经得起javap -c反编译推敲它不追求新潮框架却让HttpServletRequest到ResultSet的数据旅程清晰可见。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →