用JSP+Servlet实现图书管理系统:从登录验证到CRUD的完整案例
基于JSPServlet的图书管理系统实战开发指南在当今企业级应用开发中掌握完整的Web开发技术栈至关重要。本文将带您从零开始构建一个功能完备的图书管理系统涵盖从用户认证到数据管理的全流程实现。不同于简单的功能演示我们将采用标准的MVC架构结合Session管理、过滤器权限控制等企业级开发必备技术并特别讲解Ajax在现代化交互中的应用。1. 系统架构设计与环境准备一个健壮的图书管理系统需要合理的分层架构。我们采用经典的MVC模式Model层使用DAO(Data Access Object)模式封装数据库操作View层JSP页面负责展示数据Controller层Servlet处理业务逻辑和请求分发开发环境需要准备JDK 1.8Apache Tomcat 9.xMySQL 5.7IntelliJ IDEA/Eclipse提示建议使用Maven管理项目依赖便于jar包版本控制和项目构建数据库表设计是系统的基础图书管理系统主要包含以下表结构CREATE TABLE books ( book_id INT PRIMARY KEY AUTO_INCREMENT, book_name VARCHAR(100) NOT NULL, author VARCHAR(50), price DECIMAL(10,2), publisher VARCHAR(100), type_id VARCHAR(20) ); CREATE TABLE users ( user_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role VARCHAR(20) DEFAULT user );2. 用户认证与Session管理安全的用户认证系统是管理类应用的第一道防线。我们采用Session机制来跟踪用户登录状态。2.1 登录功能实现登录Servlet核心代码示例WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); UserDao userDao new UserDao(); User user userDao.findByUsernameAndPassword(username, password); if(user ! null) { HttpSession session request.getSession(); session.setAttribute(currentUser, user); response.sendRedirect(main.jsp); } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); } } }2.2 权限控制过滤器为防止未授权访问我们需要实现过滤器进行全局权限检查WebFilter(/*) public class AuthFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String path request.getRequestURI().substring(request.getContextPath().length()); // 放行登录相关资源和静态资源 if(path.startsWith(/login) || path.startsWith(/static)) { chain.doFilter(req, resp); return; } HttpSession session request.getSession(false); if(session null || session.getAttribute(currentUser) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }3. 图书管理核心功能实现3.1 图书CRUD操作图书DAO层实现示例包含基本的增删改查方法public class BookDao { // 获取数据库连接的方法省略... public ListBook findAll() throws SQLException { ListBook books new ArrayList(); String sql SELECT * FROM books; try (Connection conn getConnection(); PreparedStatement stmt conn.prepareStatement(sql); ResultSet rs stmt.executeQuery()) { while(rs.next()) { Book book new Book(); book.setId(rs.getInt(book_id)); book.setName(rs.getString(book_name)); book.setAuthor(rs.getString(author)); book.setPrice(rs.getBigDecimal(price)); book.setPublisher(rs.getString(publisher)); book.setTypeId(rs.getString(type_id)); books.add(book); } } return books; } public boolean delete(int id) throws SQLException { String sql DELETE FROM books WHERE book_id ?; try (Connection conn getConnection(); PreparedStatement stmt conn.prepareStatement(sql)) { stmt.setInt(1, id); return stmt.executeUpdate() 0; } } // 其他方法类似... }3.2 分页查询优化当数据量较大时分页查询是必备功能。以下是分页查询的DAO实现public ListBook findByPage(int page, int size) throws SQLException { ListBook books new ArrayList(); String sql SELECT * FROM books LIMIT ? OFFSET ?; try (Connection conn getConnection(); PreparedStatement stmt conn.prepareStatement(sql)) { stmt.setInt(1, size); stmt.setInt(2, (page - 1) * size); try (ResultSet rs stmt.executeQuery()) { while(rs.next()) { Book book extractBookFromResultSet(rs); books.add(book); } } } return books; } public int countAll() throws SQLException { String sql SELECT COUNT(*) FROM books; try (Connection conn getConnection(); PreparedStatement stmt conn.prepareStatement(sql); ResultSet rs stmt.executeQuery()) { if(rs.next()) { return rs.getInt(1); } return 0; } }4. 前端交互优化与Ajax应用4.1 使用Ajax实现删除确认传统删除操作会导致页面刷新使用Ajax可以提升用户体验function deleteBook(bookId) { if(confirm(确定要删除这本图书吗)) { $.ajax({ url: book?actiondelete, type: POST, data: {id: bookId}, success: function(result) { if(result.success) { $(#row-bookId).remove(); showMessage(删除成功); } else { showMessage(删除失败: result.message); } }, error: function() { showMessage(请求失败请稍后重试); } }); } }对应的Servlet处理代码protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action request.getParameter(action); if(delete.equals(action)) { int id Integer.parseInt(request.getParameter(id)); boolean success bookDao.delete(id); response.setContentType(application/json); PrintWriter out response.getWriter(); out.print({\success\: success }); out.flush(); } }4.2 表单验证与即时反馈前端验证可以减轻服务器压力并提供更好的用户体验$(document).ready(function() { $(#bookForm).submit(function(e) { e.preventDefault(); let valid true; $(.required).each(function() { if($(this).val().trim() ) { $(this).addClass(is-invalid); valid false; } else { $(this).removeClass(is-invalid); } }); if(!valid) return false; // AJAX提交表单 $.ajax({ url: $(this).attr(action), type: $(this).attr(method), data: $(this).serialize(), success: function(response) { if(response.success) { window.location.href book?actionlist; } else { showErrors(response.errors); } } }); }); });5. 系统安全与性能优化5.1 防止SQL注入使用PreparedStatement是防止SQL注入的基本措施public boolean add(Book book) throws SQLException { String sql INSERT INTO books(book_name, author, price, publisher, type_id) VALUES(?, ?, ?, ?, ?); try (Connection conn getConnection(); PreparedStatement stmt conn.prepareStatement(sql)) { stmt.setString(1, book.getName()); stmt.setString(2, book.getAuthor()); stmt.setBigDecimal(3, book.getPrice()); stmt.setString(4, book.getPublisher()); stmt.setString(5, book.getTypeId()); return stmt.executeUpdate() 0; } }5.2 密码安全存储用户密码绝对不能明文存储应采用哈希算法加盐处理public class PasswordUtil { private static final int SALT_LENGTH 16; private static final int ITERATIONS 10000; private static final int KEY_LENGTH 256; public static String hashPassword(String password) { byte[] salt generateSalt(); byte[] hash pbkdf2(password.toCharArray(), salt); return toHex(salt) : toHex(hash); } public static boolean verifyPassword(String password, String stored) { String[] parts stored.split(:); byte[] salt fromHex(parts[0]); byte[] hash fromHex(parts[1]); byte[] testHash pbkdf2(password.toCharArray(), salt); return slowEquals(hash, testHash); } private static byte[] pbkdf2(char[] password, byte[] salt) { // 使用PBKDF2算法实现... } // 其他辅助方法... }5.3 连接池优化使用数据库连接池可以显著提升性能public class ConnectionPool { private static DataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/library); config.setUsername(root); config.setPassword(password); config.setMaximumPoolSize(20); config.setMinimumIdle(5); config.setConnectionTimeout(30000); config.setIdleTimeout(600000); config.setMaxLifetime(1800000); dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }6. 项目部署与测试6.1 单元测试实践使用JUnit对DAO层进行测试确保数据访问逻辑正确public class BookDaoTest { private BookDao bookDao; Before public void setUp() throws Exception { bookDao new BookDao(); } Test public void testFindAll() throws SQLException { ListBook books bookDao.findAll(); assertNotNull(books); assertTrue(books.size() 0); } Test public void testAddAndDelete() throws SQLException { Book book new Book(); book.setName(测试书籍); book.setAuthor(测试作者); book.setPrice(new BigDecimal(59.99)); book.setPublisher(测试出版社); book.setTypeId(TEST); boolean added bookDao.add(book); assertTrue(added); // 假设我们添加了获取最后插入ID的方法 int id bookDao.getLastInsertId(); boolean deleted bookDao.delete(id); assertTrue(deleted); } }6.2 集成测试要点使用Selenium进行端到端测试public class BookManagementTest { private WebDriver driver; private String baseUrl; Before public void setUp() { System.setProperty(webdriver.chrome.driver, path/to/chromedriver); driver new ChromeDriver(); baseUrl http://localhost:8080/library; } Test public void testLoginAndAddBook() { driver.get(baseUrl /login.jsp); driver.findElement(By.name(username)).sendKeys(admin); driver.findElement(By.name(password)).sendKeys(admin123); driver.findElement(By.tagName(form)).submit(); // 验证登录成功 assertTrue(driver.getCurrentUrl().endsWith(/main.jsp)); // 测试添加图书 driver.findElement(By.linkText(添加图书)).click(); driver.findElement(By.name(book_name)).sendKeys(Selenium测试书籍); // 填写其他字段... driver.findElement(By.tagName(form)).submit(); // 验证添加成功 assertTrue(driver.getPageSource().contains(添加成功)); } After public void tearDown() { driver.quit(); } }7. 项目扩展与进阶思考7.1 功能扩展方向图书借阅管理添加借阅记录表实现借书、还书功能图书分类管理建立分类表支持多级分类读者管理添加读者信息管理模块统计报表使用图表库展示借阅统计、图书分布等数据7.2 技术升级路径框架迁移从原生Servlet/JSP转向Spring Boot前端现代化使用Vue/React替代JSP微服务化将系统拆分为多个微服务容器化部署使用Docker打包应用在开发过程中数据库设计要预留扩展字段代码结构要保持清晰分层这样系统才能随着需求变化而平稳演进。