用JSP+Servlet实现图书管理系统:从登录验证到CRUD完整流程
基于JSPServlet的图书管理系统实战开发指南在当今企业级应用开发中Java Web技术栈依然是构建稳健后台系统的首选方案之一。本文将带您从零开始通过开发一个功能完整的图书管理系统深入掌握JSPServlet的核心技术组合。不同于简单的CRUD示例我们将重点探讨如何在实际项目中应用会话管理、过滤器、DAO模式等关键概念并分享一些容易被官方文档忽略的实战技巧。1. 系统架构设计与技术选型在开始编码之前合理的架构设计能够避免后期大量的重构工作。我们采用经典的三层架构模式表示层JSP页面负责展示数据结合JSTL标签库简化前端逻辑控制层Servlet处理业务逻辑和请求路由数据访问层DAO模式封装所有数据库操作技术栈配置清单组件版本要求作用说明JDK1.8Java运行环境Tomcat8.5Web应用服务器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 i0; iparams.length; i){ preparedStatement.setObject(i1, params[i]); } return preparedStatement.executeUpdate(); } // 释放资源 protected void closeAll() { // 关闭逻辑... } }图书DAO实现示例public class BookDao extends BaseDao { public ListBook findAll() throws SQLException { ListBook 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的方式会导致类爆炸问题。我们采用命令模式实现统一的入口ServletWebServlet(/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?actionlist); } 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 prefixc urihttp://java.sun.com/jsp/jstl/core % table classtable thead tr th图书ID/th th图书名称/th th作者/th th价格/th /tr /thead tbody c:forEach items${bookList} varbook 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安全防护措施XSS防护使用JSTL的c:out标签或自定义过滤器CSRF防护为关键操作添加Token验证SQL注入防护始终使用PreparedStatement权限控制基于角色的访问控制过滤器实现示例WebFilter(/*) public class SecurityFilter implements Filter { private static final SetString 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 PageBook findBooksByPage(int pageNum, int pageSize) throws SQLException { PageBook 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(); ListBook 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(); } }缓存策略使用Ehcache缓存热点数据对静态资源设置HTTP缓存头考虑使用Redis作为分布式缓存6. 项目部署与监控Tomcat优化配置server.xml片段Connector port8080 protocolHTTP/1.1 connectionTimeout20000 maxThreads200 minSpareThreads10 acceptCount100 compressionon compressableMimeTypetext/html,text/xml,text/css,application/javascript redirectPort8443 /监控指标使用JMX监控JVM状态记录慢查询日志优化SQL使用PrometheusGrafana搭建可视化监控7. 常见问题排查指南典型问题1中文乱码解决方案确保数据库连接字符串包含useUnicodetruecharacterEncodingUTF-8添加字符编码过滤器JSP页面设置% page contentTypetext/html;charsetUTF-8 %典型问题2静态资源404解决方案将静态资源放在/static目录下配置Tomcat的defaultservlet处理静态资源确保web.xml中没有拦截静态资源路径典型问题3事务管理解决方案使用JDBC事务管理考虑引入Spring框架管理事务确保连接池配置正确// 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. 扩展功能与未来演进当系统规模扩大时可以考虑以下演进路线前后端分离保留Servlet作为API服务前端采用Vue/React微服务化将用户服务、图书服务等拆分为独立服务引入ORM框架如MyBatis或Hibernate简化数据访问层容器化部署使用Docker打包应用Kubernetes编排技术演进对比表架构阶段优势适用场景JSPServlet简单直接学习成本低小型内部系统教学示例SSM框架功能完善社区支持好中型企业应用微服务架构高可扩展性独立部署大型分布式系统在实际项目开发中我经常发现开发者容易忽视连接泄露问题。一个实用的技巧是在DAO层使用try-with-resources语句确保资源释放public ListBook findAll() throws SQLException { try (Connection conn DataSourceUtil.getConnection(); PreparedStatement stmt conn.prepareStatement(SELECT * FROM books); ResultSet rs stmt.executeQuery()) { ListBook 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.coloradmin.cn/o/2490305.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!