当前位置: 首页 > news >正文

用JSP+Servlet实现图书管理系统:从登录验证到CRUD完整流程

基于JSP+Servlet的图书管理系统实战开发指南

在当今企业级应用开发中,Java Web技术栈依然是构建稳健后台系统的首选方案之一。本文将带您从零开始,通过开发一个功能完整的图书管理系统,深入掌握JSP+Servlet的核心技术组合。不同于简单的CRUD示例,我们将重点探讨如何在实际项目中应用会话管理、过滤器、DAO模式等关键概念,并分享一些容易被官方文档忽略的实战技巧。

1. 系统架构设计与技术选型

在开始编码之前,合理的架构设计能够避免后期大量的重构工作。我们采用经典的三层架构模式:

  • 表示层:JSP页面负责展示数据,结合JSTL标签库简化前端逻辑
  • 控制层:Servlet处理业务逻辑和请求路由
  • 数据访问层:DAO模式封装所有数据库操作

技术栈配置清单

组件版本要求作用说明
JDK1.8+Java运行环境
Tomcat8.5+Web应用服务器
MySQL5.7+关系型数据库
JSTL1.2JSP标准标签库

提示:实际开发中建议使用Maven或Gradle管理依赖,避免手动添加jar包带来的版本冲突问题。

2. 数据库设计与DAO层实现

良好的数据库设计是系统稳定性的基石。我们的图书管理系统主要包含以下核心表:

CREATE TABLE books ( book_id INT AUTO_INCREMENT PRIMARY KEY, book_name VARCHAR(100) NOT NULL, author VARCHAR(50), price DECIMAL(10,2), publisher VARCHAR(100), type_id VARCHAR(20), create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE users ( user_id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role VARCHAR(20) DEFAULT 'user' );

DAO层的实现采用模板方法模式减少重复代码:

public abstract class BaseDao { protected Connection connection; protected PreparedStatement preparedStatement; protected ResultSet resultSet; // 获取数据库连接 protected void getConn() throws SQLException { connection = DataSourceUtil.getConnection(); } // 公共的增删改方法 protected int executeUpdate(String sql, Object... params) throws SQLException { preparedStatement = connection.prepareStatement(sql); for(int i=0; i<params.length; i++){ preparedStatement.setObject(i+1, params[i]); } return preparedStatement.executeUpdate(); } // 释放资源 protected void closeAll() { // 关闭逻辑... } }

图书DAO实现示例

public class BookDao extends BaseDao { public List<Book> findAll() throws SQLException { List<Book> list = new ArrayList<>(); try { getConn(); String sql = "SELECT * FROM books"; preparedStatement = connection.prepareStatement(sql); resultSet = preparedStatement.executeQuery(); while(resultSet.next()) { Book book = new Book(); book.setBookId(resultSet.getInt("book_id")); book.setBookName(resultSet.getString("book_name")); // 其他字段设置... list.add(book); } } finally { closeAll(); } return list; } }

3. 控制层设计与Servlet最佳实践

传统的每个请求对应一个Servlet的方式会导致类爆炸问题。我们采用命令模式实现统一的入口Servlet:

@WebServlet("/book") public class BookServlet extends HttpServlet { private BookDao bookDao = new BookDao(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action = request.getParameter("action"); switch(action) { case "add": addBook(request, response); break; case "delete": deleteBook(request, response); break; // 其他操作... } } private void addBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 参数验证 String bookName = request.getParameter("bookName"); if(bookName == null || bookName.trim().isEmpty()) { request.setAttribute("error", "图书名称不能为空"); request.getRequestDispatcher("/add.jsp").forward(request, response); return; } // 构建对象并保存 Book book = new Book(); book.setBookName(bookName); // 其他字段设置... try { bookDao.save(book); response.sendRedirect(request.getContextPath() + "/book?action=list"); } catch(SQLException e) { throw new ServletException("数据库操作失败", e); } } }

会话管理的正确姿势

// 登录成功后保存用户信息 HttpSession session = request.getSession(); session.setAttribute("currentUser", user); // 其他页面检查登录状态 User currentUser = (User)session.getAttribute("currentUser"); if(currentUser == null) { response.sendRedirect("login.jsp"); return; }

4. 表示层优化与安全防护

JSP页面应尽量减少Java代码,使用JSTL和EL表达式:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <table class="table"> <thead> <tr> <th>图书ID</th> <th>图书名称</th> <th>作者</th> <th>价格</th> </tr> </thead> <tbody> <c:forEach items="${bookList}" var="book"> <tr> <td>${book.bookId}</td> <td>${book.bookName}</td> <td>${book.author}</td> <td>¥<fmt:formatNumber value="${book.price}" pattern="#,##0.00"/></td> </tr> </c:forEach> </tbody> </table>

安全防护措施

  1. XSS防护:使用JSTL的<c:out>标签或自定义过滤器
  2. CSRF防护:为关键操作添加Token验证
  3. SQL注入防护:始终使用PreparedStatement
  4. 权限控制:基于角色的访问控制

过滤器实现示例

@WebFilter("/*") public class SecurityFilter implements Filter { private static final Set<String> ALLOWED_PATHS = Set.of("/login", "/register"); public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; String path = request.getRequestURI().substring(request.getContextPath().length()); if (ALLOWED_PATHS.contains(path) || path.startsWith("/static/")) { chain.doFilter(request, response); return; } HttpSession session = request.getSession(false); if (session == null || session.getAttribute("currentUser") == null) { response.sendRedirect(request.getContextPath() + "/login"); return; } chain.doFilter(request, response); } }

5. 高级功能实现与性能优化

分页查询实现

public Page<Book> findBooksByPage(int pageNum, int pageSize) throws SQLException { Page<Book> page = new Page<>(); page.setPageNum(pageNum); page.setPageSize(pageSize); // 查询总数 String countSql = "SELECT COUNT(*) FROM books"; preparedStatement = connection.prepareStatement(countSql); resultSet = preparedStatement.executeQuery(); if(resultSet.next()) { page.setTotal(resultSet.getInt(1)); } // 查询分页数据 String dataSql = "SELECT * FROM books LIMIT ?, ?"; preparedStatement = connection.prepareStatement(dataSql); preparedStatement.setInt(1, (pageNum-1)*pageSize); preparedStatement.setInt(2, pageSize); resultSet = preparedStatement.executeQuery(); List<Book> data = new ArrayList<>(); while(resultSet.next()) { Book book = new Book(); // 设置属性... data.add(book); } page.setData(data); return page; }

连接池配置(以HikariCP为例):

public class DataSourceUtil { private static HikariDataSource dataSource; static { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/book_db"); config.setUsername("root"); config.setPassword("password"); config.setMaximumPoolSize(20); config.setMinimumIdle(5); config.addDataSourceProperty("cachePrepStmts", "true"); config.addDataSourceProperty("prepStmtCacheSize", "250"); config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); dataSource = new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }

缓存策略

  1. 使用Ehcache缓存热点数据
  2. 对静态资源设置HTTP缓存头
  3. 考虑使用Redis作为分布式缓存

6. 项目部署与监控

Tomcat优化配置(server.xml片段):

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" maxThreads="200" minSpareThreads="10" acceptCount="100" compression="on" compressableMimeType="text/html,text/xml,text/css,application/javascript" redirectPort="8443" />

监控指标

  • 使用JMX监控JVM状态
  • 记录慢查询日志优化SQL
  • 使用Prometheus+Grafana搭建可视化监控

7. 常见问题排查指南

典型问题1:中文乱码

解决方案:

  1. 确保数据库连接字符串包含useUnicode=true&characterEncoding=UTF-8
  2. 添加字符编码过滤器
  3. JSP页面设置<%@ page contentType="text/html;charset=UTF-8" %>

典型问题2:静态资源404

解决方案:

  1. 将静态资源放在/static目录下
  2. 配置Tomcat的defaultservlet处理静态资源
  3. 确保web.xml中没有拦截静态资源路径

典型问题3:事务管理

解决方案:

  1. 使用JDBC事务管理
  2. 考虑引入Spring框架管理事务
  3. 确保连接池配置正确
// JDBC事务示例 Connection conn = null; try { conn = DataSourceUtil.getConnection(); conn.setAutoCommit(false); // 执行多个DAO操作... conn.commit(); } catch(SQLException e) { if(conn != null) { try { conn.rollback(); } catch(SQLException ex) {} } throw e; } finally { if(conn != null) { try { conn.setAutoCommit(true); conn.close(); } catch(SQLException e) {} } }

8. 扩展功能与未来演进

当系统规模扩大时,可以考虑以下演进路线:

  1. 前后端分离:保留Servlet作为API服务,前端采用Vue/React
  2. 微服务化:将用户服务、图书服务等拆分为独立服务
  3. 引入ORM框架:如MyBatis或Hibernate简化数据访问层
  4. 容器化部署:使用Docker打包应用,Kubernetes编排

技术演进对比表

架构阶段优势适用场景
JSP+Servlet简单直接,学习成本低小型内部系统,教学示例
SSM框架功能完善,社区支持好中型企业应用
微服务架构高可扩展性,独立部署大型分布式系统

在实际项目开发中,我经常发现开发者容易忽视连接泄露问题。一个实用的技巧是在DAO层使用try-with-resources语句确保资源释放:

public List<Book> findAll() throws SQLException { try (Connection conn = DataSourceUtil.getConnection(); PreparedStatement stmt = conn.prepareStatement("SELECT * FROM books"); ResultSet rs = stmt.executeQuery()) { List<Book> books = new ArrayList<>(); while(rs.next()) { books.add(mapRow(rs)); } return books; } }

另一个值得注意的细节是密码存储安全。永远不要明文存储用户密码,应该使用BCrypt等强哈希算法:

public class PasswordUtil { private static final int STRENGTH = 12; public static String hashPassword(String plainPassword) { return BCrypt.hashpw(plainPassword, BCrypt.gensalt(STRENGTH)); } public static boolean checkPassword(String plainPassword, String hashedPassword) { return BCrypt.checkpw(plainPassword, hashedPassword); } }
http://www.jsqmd.com/news/598761/

相关文章:

  • 双馈风机次同步振荡抑制策略(一) 含 基于转子侧附加阻尼控制(SDC)的双馈风机次同步振荡抑制...
  • 如何为 Scala.js 编写自定义链接器插件:从零开始的完整指南
  • RWKV7-1.5B-G1A入门实操:GitHub代码仓库分析与总结生成
  • 基于Django的农场管理系统_5c4c39so_zl071
  • Android Init 系列专题【篇二:Selinux启动流程】
  • 如何高效解析小程序包?wxappUnpacker技术指南
  • 别再只会用了!PowerBI中CONCATENATEX函数实战:从动态标签到多值筛选器
  • PathPicker终极调试指南:快速解决5大常见错误与性能优化
  • 【CEEMDAN-VMD-GRU】完备集合经验模态分解-变分模态分解-门控循环单元预测研究附Python代码
  • 2026 BJ省选游记+题解
  • 01 飞腾 S5000C 服务器环境搭建实战:PyTorch + CUDA + RTX 4090D 安装与验证
  • NextFaster 电商数据库设计深度解析:从集合到产品的完整架构指南
  • 【3-5-3多项式】基于改进麻雀算法ISSA(混沌映射和粒子群PSO优化机械臂轨迹运行时间,机械臂规划轨迹研究附Matlab代码
  • Microsoft Agent Framework + Kimi API 实战:控制台应用跑通单次与多轮 Agent 对话
  • FPGA-图像处理实战:基于Sobel算子的实时边缘检测系统构建
  • 避开Trace API的坑:Android方法耗时统计的正确姿势与实战技巧
  • Blender 3MF插件:重新定义3D打印数据工作流
  • XUnity.AutoTranslator技术指南:从环境搭建到高级应用
  • 26年4月5日响课创始人李波在直播中针对GEO服务商避坑指南:主流机构优劣对比与选型测评做出详解 - 速递信息
  • 数据挖掘
  • 告别SCP!用trzsz+iTerm2实现服务器文件秒传(CentOS/Homebrew全流程实录)
  • Cocos使用firebase C++ SDK实现google登录
  • 终极实战指南:Godot PCK解包器深度解析与高效资源提取
  • 如何快速开始Cucumber.js:新手5步搭建第一个BDD测试项目
  • 学习日记
  • 2026年4月6日响课科技创始人李波首次披露响课GEO系统获多行业验证,无需专属技术团队也能高效实现全域流量占位 - 速递信息
  • Keil MDK调试时Watch窗口变量不刷新?别急,这3个设置项你检查了吗?
  • IDMPhotoBrowser:iOS开发者的终极照片浏览器解决方案
  • A*算法保姆级教程:从原理到Python实现,5分钟搞定最短路径问题
  • 基于粒子群的PMU优化配置 软件:MATLAB 介绍:电力系统PMU优化配置,为了使电力系统达...