基于JSP+Servlet的图书商城与库存管理系统 - 源码深度解析

JavaJavaScriptHTMLCSSMySQLJSP+Servlet
2026-03-144 浏览

文章摘要

本项目是一款基于JSP与Servlet技术栈构建的综合性图书管理与在线销售平台,旨在解决中小型书店或图书馆在传统手工或半信息化管理模式下存在的效率低下、数据孤岛及销售渠道单一的核心痛点。系统将前台图书商城与后台库存管理无缝集成,实现了从图书采购、入库、上架到在线展示、订单处理、库存同步的全流程数字化...

在传统图书行业数字化转型的浪潮中,一个高效、集成的管理解决方案显得尤为重要。本文介绍的系统正是针对这一需求,采用经典的JSP+Servlet技术栈,构建了一个集前台在线商城与后台库存管理于一体的综合性平台。该系统将图书的采购、入库、上架、销售、订单处理等业务流程进行了数字化整合,实现了全流程的闭环管理。

技术架构与设计模式

系统严格遵循MVC设计模式,确保了代码的良好分层与可维护性。Servlet作为控制器层,负责拦截所有客户端请求,进行业务逻辑处理和数据路由。模型层由一系列JavaBean构成,封装了核心的业务实体和数据处理逻辑。视图层则使用JSP技术,结合JSTL标签库和EL表达式,实现动态页面的渲染,有效避免了在页面中嵌入过多的Java代码。

数据持久化层基于JDBC直接连接MySQL数据库,通过编写高效的SQL语句和执行事务管理,保证了数据操作的准确性和一致性。整个系统部署于Tomcat等Servlet容器中,无需依赖复杂的框架,结构清晰,易于理解和二次开发。

数据库设计剖析

数据库设计是系统的基石,共设计了6张核心表来支撑业务运转。以下重点分析其中几个关键表的设计。

1. 图书信息表

图书表的设计充分考虑了图书商品的各类属性,并建立了与分类表的外键关联。

CREATE TABLE books (
    book_id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    author VARCHAR(255) NOT NULL,
    publisher VARCHAR(255),
    publish_date DATE,
    isbn VARCHAR(20) UNIQUE,
    category_id INT,
    price DECIMAL(10, 2) NOT NULL,
    stock_quantity INT NOT NULL DEFAULT 0,
    description TEXT,
    image_url VARCHAR(500),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (category_id) REFERENCES categories(category_id) ON DELETE SET NULL
);

该表的亮点在于:

  • 使用AUTO_INCREMENT自增主键,确保每本图书的唯一标识。
  • isbn字段设置唯一约束,符合图书行业的规范,防止重复录入。
  • price字段采用DECIMAL(10,2)类型,精确存储货币值。
  • stock_quantity字段实时反映库存数量,是库存管理的核心。
  • 包含created_atupdated_at两个时间戳字段,用于审计和数据追踪。
  • 通过category_id外键与分类表关联,实现图书的分类管理。

2. 订单表

订单表的设计体现了电子商务的核心,它记录了交易的关键信息,并与用户和订单明细表关联。

CREATE TABLE orders (
    order_id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    total_amount DECIMAL(10, 2) NOT NULL,
    status ENUM('pending', 'confirmed', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
    shipping_address TEXT NOT NULL,
    payment_method VARCHAR(50),
    payment_status ENUM('unpaid', 'paid', 'refunded') DEFAULT 'unpaid',
    FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE
);

该表的亮点在于:

  • 使用ENUM类型定义订单状态和支付状态,确保状态值的规范性和有效性,如'pending'(待处理)、'shipped'(已发货)等。
  • total_amount字段记录订单总金额,由订单明细项计算得出。
  • user_id外键关联用户表,清晰记录订单归属。
  • ON DELETE CASCADE约束确保当用户被删除时,其关联的订单也被自动清理,维护数据一致性。

3. 订单明细表

此表是订单与图书的关联表,解决了订单与商品的多对多关系,是业务逻辑的关键。

CREATE TABLE order_items (
    item_id INT AUTO_INCREMENT PRIMARY KEY,
    order_id INT NOT NULL,
    book_id INT NOT NULL,
    quantity INT NOT NULL CHECK (quantity > 0),
    unit_price DECIMAL(10, 2) NOT NULL,
    FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE,
    FOREIGN KEY (book_id) REFERENCES books(book_id)
);

该表的亮点在于:

  • 记录了购买时图书的单价(unit_price),这是一个重要的历史快照。即使后续图书价格变动,订单中的价格也不会改变。
  • quantity字段设置了CHECK约束,确保购买数量为正数。
  • 通过order_idbook_id两个外键,将订单、图书、购买数量和价值紧密联系起来。

核心功能实现解析

1. 用户登录与会话管理

用户认证是系统安全的第一道屏障。登录Servlet负责处理认证逻辑,并创建用户会话。

// LoginServlet.java
@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");
        String role = request.getParameter("role"); // "user" or "admin"

        UserService userService = new UserService();
        User user = userService.authenticate(username, password, role);

        if (user != null) {
            HttpSession session = request.getSession();
            session.setAttribute("user", user);
            session.setAttribute("role", role);

            if ("admin".equals(role)) {
                response.sendRedirect("admin/dashboard.jsp");
            } else {
                response.sendRedirect("user/home.jsp");
            }
        } else {
            request.setAttribute("errorMessage", "Invalid username, password, or role.");
            request.getRequestDispatcher("login.jsp").forward(request, response);
        }
    }
}

管理员登录界面

此段代码的关键点在于:

  • 使用@WebServlet注解配置Servlet映射,简化部署描述符配置。
  • 根据role参数区分普通用户和管理员,导向不同的主页。
  • 认证成功后,将完整的User对象和角色信息存入HttpSession,便于后续请求中识别用户身份和权限。
  • 认证失败时,通过request.setAttribute设置错误信息,并转发回登录页显示。

2. 购物车功能实现

购物车是电商系统的核心功能,采用Session来临时存储用户的选购商品。

// CartServlet.java
@WebServlet("/cart/*")
public class CartServlet extends HttpServlet {
    private BookService bookService = new BookService();

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String pathInfo = request.getPathInfo();
        HttpSession session = request.getSession();
        Map<Integer, CartItem> cart = (Map<Integer, CartItem>) session.getAttribute("cart");
        if (cart == null) {
            cart = new HashMap<>();
            session.setAttribute("cart", cart);
        }

        if ("/add".equals(pathInfo)) {
            int bookId = Integer.parseInt(request.getParameter("bookId"));
            int quantity = Integer.parseInt(request.getParameter("quantity"));

            Book book = bookService.getBookById(bookId);
            if (book != null) {
                CartItem item = cart.get(bookId);
                if (item != null) {
                    item.setQuantity(item.getQuantity() + quantity);
                } else {
                    item = new CartItem(book, quantity);
                    cart.put(bookId, item);
                }
            }
            response.sendRedirect(request.getContextPath() + "/book/list.jsp");
        } else if ("/update".equals(pathInfo)) {
            // ... 更新购物车商品数量逻辑
        } else if ("/remove".equals(pathInfo)) {
            // ... 移除商品逻辑
        }
    }
}

购物车项CartItem是一个标准的JavaBean,封装了商品信息和数量。

// CartItem.java
public class CartItem {
    private Book book;
    private int quantity;

    public CartItem(Book book, int quantity) {
        this.book = book;
        this.quantity = quantity;
    }

    // 计算此项目总价
    public BigDecimal getTotalPrice() {
        return book.getPrice().multiply(new BigDecimal(quantity));
    }

    // Getters and Setters
    public Book getBook() { return book; }
    public void setBook(Book book) { this.book = book; }
    public int getQuantity() { return quantity; }
    public void setQuantity(int quantity) { this.quantity = quantity; }
}

查看购物车

此设计的特点:

  • 购物车以Map<Integer, CartItem>形式存储在Session中,键为图书ID,值为购物车项,便于通过ID快速查找和更新。
  • CartItem类不仅存储数量,还持有Book对象的引用,方便在页面上显示完整的图书信息。
  • 业务逻辑如计算单项总价封装在CartItem中,符合面向对象设计原则。

3. 订单生成与库存同步

提交订单是系统最关键的事务性操作,必须确保库存减少和订单创建的一致性。

// OrderServlet.java
@WebServlet("/order/place")
public class OrderServlet extends HttpServlet {
    private OrderService orderService = new OrderService();

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("user");
        Map<Integer, CartItem> cart = (Map<Integer, CartItem>) session.getAttribute("cart");

        if (user == null) {
            response.sendRedirect(request.getContextPath() + "/login.jsp");
            return;
        }

        if (cart == null || cart.isEmpty()) {
            request.setAttribute("errorMessage", "Your cart is empty.");
            request.getRequestDispatcher("/cart/view.jsp").forward(request, response);
            return;
        }

        String shippingAddress = request.getParameter("shippingAddress");
        String paymentMethod = request.getParameter("paymentMethod");

        try {
            // 调用Service层方法创建订单,该方法包含事务管理
            Order order = orderService.createOrder(user.getUserId(), cart, shippingAddress, paymentMethod);

            // 清空购物车
            session.removeAttribute("cart");

            request.setAttribute("order", order);
            request.getRequestDispatcher("/order/confirmation.jsp").forward(request, response);

        } catch (Exception e) {
            e.printStackTrace();
            request.setAttribute("errorMessage", "Failed to create order: " + e.getMessage());
            request.getRequestDispatcher("/cart/checkout.jsp").forward(request, response);
        }
    }
}

订单生成的核心业务逻辑在Service层,它需要在一个数据库事务中完成多个操作。

// OrderService.java
public class OrderService {
    private OrderDAO orderDAO = new OrderDAO();
    private BookDAO bookDAO = new BookDAO();

    public Order createOrder(int userId, Map<Integer, CartItem> cart, String shippingAddress, String paymentMethod) throws Exception {
        Connection conn = null;
        try {
            conn = DatabaseUtil.getConnection();
            conn.setAutoCommit(false); // 开启事务

            // 1. 计算订单总金额并检查库存
            BigDecimal totalAmount = BigDecimal.ZERO;
            for (CartItem item : cart.values()) {
                Book book = item.getBook();
                if (book.getStockQuantity() < item.getQuantity()) {
                    throw new Exception("Insufficient stock for book: " + book.getTitle());
                }
                totalAmount = totalAmount.add(item.getTotalPrice());
            }

            // 2. 插入订单主记录
            Order order = new Order();
            order.setUserId(userId);
            order.setTotalAmount(totalAmount);
            order.setShippingAddress(shippingAddress);
            order.setPaymentMethod(paymentMethod);
            int orderId = orderDAO.insertOrder(conn, order);

            // 3. 插入订单明细并扣减库存
            for (CartItem item : cart.values()) {
                OrderItem orderItem = new OrderItem();
                orderItem.setOrderId(orderId);
                orderItem.setBookId(item.getBook().getBookId());
                orderItem.setQuantity(item.getQuantity());
                orderItem.setUnitPrice(item.getBook().getPrice());

                orderDAO.insertOrderItem(conn, orderItem);

                // 更新图书库存
                int affectedRows = bookDAO.updateStock(conn, item.getBook().getBookId(), -item.getQuantity());
                if (affectedRows == 0) {
                    throw new Exception("Failed to update stock for book ID: " + item.getBook().getBookId());
                }
            }

            conn.commit(); // 提交事务
            order.setOrderId(orderId);
            return order;

        } catch (Exception e) {
            if (conn != null) {
                try {
                    conn.rollback(); // 回滚事务
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            throw e;
        } finally {
            if (conn != null) {
                try {
                    conn.setAutoCommit(true);
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

提交订单界面

此事务处理的关键点:

  • 手动管理数据库连接和事务,通过setAutoCommit(false)开启事务,commit()提交,rollback()回滚。
  • 在事务内依次执行:库存检查、插入订单、插入订单明细、更新库存。任何一步失败,整个事务回滚,防止数据不一致。
  • 库存检查在事务内进行,可以防止高并发下的超卖问题。

4. 后台图书管理

管理员可以对图书进行增删改查操作,以下是更新图书信息的Servlet。

// AdminBookServlet.java
@WebServlet("/admin/book/update")
public class AdminBookServlet extends HttpServlet {
    private BookService bookService = new BookService();

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 权限检查
        HttpSession session = request.getSession();
        if (!"admin".equals(session.getAttribute("role"))) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN);
            return;
        }

        try {
            int bookId = Integer.parseInt(request.getParameter("bookId"));
            String title = request.getParameter("title");
            String author = request.getParameter("author");
            BigDecimal price = new BigDecimal(request.getParameter("price"));
            int stock = Integer.parseInt(request.getParameter("stockQuantity"));
            int categoryId = Integer.parseInt(request.getParameter("categoryId"));

            Book book = new Book();
            book.setBookId(bookId);
            book.setTitle(title);
            book.setAuthor(author);
            book.setPrice(price);
            book.setStockQuantity(stock);
            book.setCategoryId(categoryId);
            // 设置其他字段...

            boolean success = bookService.updateBook(book);
            if (success) {
                response.sendRedirect(request.getContextPath() + "/admin/book/list.jsp?message=Book updated successfully");
            } else {
                request.setAttribute("errorMessage", "Failed to update book.");
                request.getRequestDispatcher("/admin/book/edit.jsp?id=" + bookId).forward(request, response);
            }
        } catch (Exception e) {
            e.printStackTrace();
            request.setAttribute("errorMessage", "Error: " + e.getMessage());
            request.getRequestDispatcher("/admin/book/edit.jsp").forward(request, response);
        }
    }
}

后台图书管理

5. 数据访问层实现

以图书DAO为例,展示基础的CRUD操作和数据库连接管理。

// BookDAO.java
public class BookDAO {
    public Book getBookById(int bookId) {
        String sql = "SELECT * FROM books WHERE book_id = ?";
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql)) {

            stmt.setInt(1, bookId);
            ResultSet rs = stmt.executeQuery();

            if (rs.next()) {
                return extractBookFromResultSet(rs);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    public List<Book> getBooksByCategory(int categoryId) {
        List<Book> books = new ArrayList<>();
        String sql = "SELECT * FROM books WHERE category_id = ?";
        // ... 类似的数据库操作逻辑
        return books;
    }

    private Book extractBookFromResultSet(ResultSet rs) throws SQLException {
        Book book = new Book();
        book.setBookId(rs.getInt("book_id"));
        book.setTitle(rs.getString("title"));
        book.setAuthor(rs.getString("author"));
        book.setPrice(rs.getBigDecimal("price"));
        book.setStockQuantity(rs.getInt("stock_quantity"));
        // 设置其他字段...
        return book;
    }
}

6. 前台图书列表展示

JSP页面使用JSTL和EL表达式展示图书列表,代码清晰易读。

<%-- book-list.jsp --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div class="book-grid">
    <c:forEach var="book" items="${bookList}">
        <div class="book-item">
            <img src="${book.imageUrl}" alt="${book.title}" class="book-cover">
            <h3>${book.title}</h3>
            <p>作者: ${book.author}</p>
            <p class="price">¥${book.price}</p>
            <c:choose>
                <c:when test="${book.stockQuantity > 0}">
                    <span class="stock in-stock">有货 (${book.stockQuantity}本)</span>
                    <form action="cart/add" method="post" class="add-to-cart-form">
                        <input type="hidden" name="bookId" value="${book.bookId}">
                        <input type="number" name="quantity" value="1" min="1" max="${book.stockQuantity}">
                        <button type="submit">加入购物车</button>
                    </form>
                </c:when>
                <c:otherwise>
                    <span class="stock out-of-stock">缺货</span>
                </c:otherwise>
            </c:choose>
        </div>
    </c:forEach>
</
本文关键词
JSPServlet图书商城库存管理系统源码解析

上下篇

上一篇
没有更多文章
下一篇
没有更多文章