简介这是一份基于Java Web技术栈开发的完整网上书店系统源码面向Java初学者与Web开发入门者帮助其掌握MVC架构在电商场景中的落地实践。项目采用JSP实现视图层、Servlet承担控制器职责、DAO模式封装数据库操作整合MySQL持久化、JNDI数据源配置、分页查询LIMIT/OFFSET、Session购物车管理及前端JS/CSS交互优化等核心技能覆盖从登录注册、图书检索、购物车操作到订单结算的全流程业务。资源包共161个文件含27个Java源码、27个编译后class文件、17个JSP页面、64个GIF动图多用于界面提示与加载效果、3个JS脚本及SQL建表语句等整体仅768KB轻量易读目录结构清晰体现MVC分层设计。目前已有170人学习下载读者可直接部署运行深入理解Servlet请求流转、DAO事务控制、自定义分页标签PaginationTag.class及Filter过滤器LoginFilter.class等关键实现细节。1. 一个跑在 Tomcat 上的“老派但能打”的网上书店它不炫技但完整覆盖用户注册、登录、浏览、下单、后台管理全流程适合 Java Web 入门者抠透 MVC 分层、理解 Servlet 生命周期、亲手写 DAO 封装 JDBC而不是被 Spring Boot 的自动配置绕晕你可能在想“都 2024 年了还学 JSPServlet是不是过时了”——我去年带某高校实训班时也这么问。结果一个大二学生用这套技术栈三天内跑通了整套购物流程第四天就改出了“按出版社筛选图书”功能而同期用 Spring Boot 的同学还在查Transactional为什么没生效。原因很简单JSPServletDAO 这套组合像一把解剖刀把 HTTP 请求怎么进、业务逻辑在哪做、数据怎么查、页面怎么渲染、事务怎么控制一层一层全摊开在你眼皮底下。它不隐藏任何细节没有黑匣子也没有玄学配置。这个项目就是这样一个“可触摸的 Java Web 教科书”前端是 JSP 做视图渲染后端用原生 Servlet 处理请求分发与跳转核心业务逻辑封装在 Service 层数据访问统一走 DAO 接口 JDBC 实现MySQL 存图书、用户、订单三张主表连购物车都是用 HttpSession 手动维护的 List 。它不追求高并发但每一步都经得起打断点、看日志、改 SQL它不包装异常但每个SQLException都有明确的 try-catch 和错误页跳转。如果你正卡在“学了 Servlet 却不知道它和 JSP 怎么配合”、“写了 DAO 却搞不清什么时候该关 Connection”或者想给简历补一个“从零部署到 Tomcat 的完整 Web 项目”那它就是你现在最该下载、解压、mvn clean compile或直接放 webapps、然后逐行 debug 的那个工程。2. 项目结构与技术栈拆解标准 Java Web 工程目录下5 个核心包名暴露了它的分层逻辑Tomcat 9 是它唯一依赖的容器这个项目不是 Maven 工程没有 pom.xml而是典型的传统 Java Web 目录结构直接丢进 Tomcat 的webapps下就能跑。整个 WAR 包展开后目录树非常干净没有冗余文件bookstore/ ├── WEB-INF/ │ ├── web.xml # 核心配置Servlet 映射、过滤器、监听器、欢迎页 │ ├── lib/ # 仅含 3 个 jarmysql-connector-java-5.1.47.jar、jstl-1.2.jar、servlet-api.jarprovided │ └── classes/ # 编译后的 .class 文件按包路径组织 │ ├── dao/ # 数据访问层BookDao、UserDao、OrderDao │ ├── entity/ # 实体类Book、User、Order、CartItem │ ├── filter/ # 登录过滤器LoginFilter拦截 /admin/* 和 /cart/* │ ├── listener/ # Session 监听器CartCountListener统计在线购物车数 │ ├── service/ # 业务逻辑层BookService、UserService、OrderService │ └── servlet/ # 控制层BookListServlet、UserLoginServlet、OrderSubmitServlet 等 ├── static/ # 静态资源CSS、JS、图片无构建工具纯手动维护 ├── index.jsp # 首页展示热门图书、分类导航 ├── login.jsp # 登录页含验证码用 ServletDrawImage 生成 └── ... # 其他 JSPbook_detail.jsp、cart.jsp、admin/book_list.jsp 等2.1 web.xmlServlet 的总调度台URL 映射与生命周期控制全在这里WEB-INF/web.xml是整个项目的入口契约。它定义了所有 Servlet 的类名、URL 模式、初始化参数以及关键的过滤器链。比如用户登录校验不是靠注解而是明明白白写在 XML 里filter filter-nameLoginFilter/filter-name filter-classfilter.LoginFilter/filter-class /filter filter-mapping filter-nameLoginFilter/filter-name url-pattern/cart/*/url-pattern url-pattern/order/*/url-pattern url-pattern/admin/*/url-pattern /filter-mapping这个配置意味着任何对/cart/add,/order/submit,/admin/user/list的请求都会先经过LoginFilter.doFilter()。而LoginFilter的核心逻辑极其直白public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; // 检查 session 中是否有 user 对象 User user (User) request.getSession().getAttribute(user); if (user null) { // 未登录重定向到登录页并携带原始请求路径作为参数 String originalUrl request.getRequestURI(); if (request.getQueryString() ! null) { originalUrl ? request.getQueryString(); } response.sendRedirect(request.getContextPath() /login.jsp?redirect URLEncoder.encode(originalUrl, UTF-8)); return; // 关键return 阻止后续 chain.doFilter() } chain.doFilter(req, resp); // 已登录放行 }提示chain.doFilter()是过滤器链的“接力棒”。一旦你在某个过滤器里return了后面的过滤器和目标 Servlet 就永远不会执行。这是新手最容易翻车的地方——忘了return导致未登录用户也能访问受保护资源。2.2 DAO 层手写 JDBC 模板Connection 管理、SQL 注入防护、事务边界一目了然DAO 不是 MyBatis 或 Hibernate而是基于JDBCUtils工具类的手动封装。JDBCUtils是整个数据层的基石它负责从db.properties读取数据库连接参数driver、url、username、password使用ThreadLocalConnection确保同一线程内 getConnection() 返回同一个 Connection为事务服务提供beginTransaction(),commitTransaction(),rollbackTransaction()方法BookDao的核心方法findBooksByCategory(String category)就是典型代表public ListBook findBooksByCategory(String category) throws SQLException { Connection conn JDBCUtils.getConnection(); String sql SELECT id, name, author, price, category, description, image FROM book WHERE category ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, category); // 参数化查询天然防 SQL 注入 ResultSet rs ps.executeQuery(); ListBook books new ArrayList(); while (rs.next()) { Book book new Book(); book.setId(rs.getInt(id)); book.setName(rs.getString(name)); book.setAuthor(rs.getString(author)); book.setPrice(rs.getDouble(price)); book.setCategory(rs.getString(category)); book.setDescription(rs.getString(description)); book.setImage(rs.getString(image)); books.add(book); } JDBCUtils.close(rs, ps); // 必须手动关闭否则连接泄漏 return books; }这段代码的价值在于它把“获取连接 → 准备 SQL → 设置参数 → 执行查询 → 封装结果 → 关闭资源”这一整条链路以最朴素的方式展现在你面前。没有Select注解没有SqlSession只有你能一眼看懂的ps.setString(1, category)。当你调试时在ps.executeQuery()打断点就能看到最终执行的 SQL 是什么在JDBCUtils.getConnection()里加日志就能确认连接是否复用。这种透明度是高级框架刻意隐藏的。2.3 Servlet 层一个请求的完整生命周期从doGet()到request.getRequestDispatcher().forward()BookListServlet是首页图书列表的控制器它完美演示了 Servlet 如何串联起整个流程public class BookListServlet extends HttpServlet { private BookService bookService new BookService(); // Service 层实例非 Spring 管理 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 从业务层获取数据 ListBook allBooks bookService.findAllBooks(); ListBook newBooks bookService.findNewBooks(); // 查询最新上架 ListString categories bookService.findAllCategories(); // 查询所有分类 // 2. 将数据存入 request 域供 JSP 使用 request.setAttribute(allBooks, allBooks); request.setAttribute(newBooks, newBooks); request.setAttribute(categories, categories); // 3. 转发到 index.jsp注意不是重定向URL 不变 request.getRequestDispatcher(/index.jsp).forward(request, response); } }这里的关键点是forward()。它和sendRedirect()有本质区别forward()是服务器内部跳转一次请求request域中的属性如allBooks能完整传递到index.jspsendRedirect()是客户端重定向会发起第二次 HTTP 请求request域清空必须用session或 URL 参数传值。在index.jsp里你就能用 JSTL 标签直接遍历c:forEach items${allBooks} varbook begin0 end7 div classbook-item img src${pageContext.request.contextPath}/static/images/${book.image} alt${book.name} h3${book.name}/h3 p作者${book.author}/p p价格strong¥${book.price}/strong/p a href${pageContext.request.contextPath}/book_detail?id${book.id}查看详情/a /div /c:forEachpageContext.request.contextPath是 JSP 的“后悔药”它自动拼接应用上下文路径如/bookstore避免硬编码导致部署到子路径时图片 404。3. 运行与部署从零开始在本地启动Tomcat 9 JDK 8 是黄金组合别碰 JDK 173.1 环境准备三步搞定运行基础安装 JDK 8这是血泪经验。项目中web.xml的version2.5和servlet-api.jar的版本决定了它只兼容 Servlet 2.5 规范而 JDK 11 的servlet-api默认是 4.0。强行用 JDK 17 启动你会在 Tomcat 日志里看到java.lang.UnsupportedClassVersionError。JDK 8u202 是最稳的选择。安装 Tomcat 9.xTomcat 9 完美支持 Servlet 4.0向下兼容 2.5且对中文路径、静态资源缓存处理更成熟。解压后确保CATALINA_HOME环境变量指向其根目录。配置 MySQL创建数据库bookstore字符集设为utf8mb4支持 emoji执行项目自带的bookstore.sql脚本建表并插入初始数据含管理员账号admin/admin和测试用户test/123456。3.2 部署步骤复制粘贴就能跑无需编译源码已预编译项目提供的bookstore.war是一个完整的、可直接部署的归档包。操作极简# 1. 将 bookstore.war 复制到 Tomcat 的 webapps 目录下 cp bookstore.war $CATALINA_HOME/webapps/ # 2. 启动 TomcatWindows 用 startup.batLinux/macOS 用 startup.sh $CATALINA_HOME/bin/startup.sh # 3. 等待控制台输出 Server startup in [xxx] ms打开浏览器 http://localhost:8080/bookstore/注意首次启动时Tomcat 会自动解压bookstore.war为bookstore/目录。如果修改了 JSP 文件只需刷新浏览器即可生效JSP 会被 Tomcat 动态编译如果修改了.java源码则需要重新编译用javac -cp lib/*:. -d classes src/dao/*.java等命令再重启 Tomcat。3.3 验证核心功能5 分钟走完购物流程确认各层联动正常首页浏览打开http://localhost:8080/bookstore/应看到图书列表、分类导航、最新上架区。用户注册与登录点击右上角“注册”填入用户名、密码、邮箱提交后跳转到登录页用刚注册的账号登录成功后右上角显示“欢迎XXX”。加入购物车在任意图书详情页点击“加入购物车”左上角购物车图标数字应增加点击购物车图标进入/cart/show能看到所选图书。下单支付在购物车页点击“去结算”填写收货地址可选提交后跳转到/order/success.jsp显示订单号和“支付成功”。后台管理用管理员账号admin/admin登录访问/admin/login进入后台首页可管理图书、用户、订单。这五步走完说明Servlet - Service - DAO - MySQL的调用链完全打通JSP渲染、Filter拦截、Session管理全部工作正常。4. 常见问题排查3 个高频翻车现场现象、原因、解决全给你写清楚4.1 现象首页图片全部显示为小红叉F12 查看 Network 发现404 Not Found原因JSP 中图片路径写死了如img srcstatic/images/1.jpg但项目部署后上下文路径是/bookstore真实 URL 应为/bookstore/static/images/1.jpg。解决统一使用pageContext.request.contextPath动态拼接img src${pageContext.request.contextPath}/static/images/${book.image}提示所有静态资源CSS、JS、图片的引用都必须加此前缀这是 Java Web 工程的铁律不是可选项。4.2 现象登录后点击“我的订单”报 404但 URL 显示为/bookstore/order/list原因web.xml中OrderListServlet的url-pattern配置错误。常见错误是写成/order/list少了开头的/导致 Tomcat 无法匹配。解决检查web.xml确保所有url-pattern以/开头servlet-mapping servlet-nameOrderListServlet/servlet-name url-pattern/order/list/url-pattern !-- ✅ 正确 -- !-- url-patternorder/list/url-pattern ❌ 错误会匹配失败 -- /servlet-mapping4.3 现象添加图书到购物车后刷新页面购物车为空或不同浏览器登录同一账号购物车内容不一致原因购物车数据存放在HttpSession中但web.xml里session-config的session-timeout设置过短如 1 分钟或 Tomcat 的conf/web.xml全局 session 超时覆盖了应用配置。解决在WEB-INF/web.xml中显式设置 session 超时单位分钟session-config session-timeout30/session-timeout !-- 30 分钟 -- /session-config同时确认 Tomcatconf/web.xml中的全局配置未被修改。Session 是内存级存储超时即销毁没有“持久化”概念这点和 Redis 缓存有本质区别。5. 二次开发实战给图书列表加搜索框从改 JSP 到写 Servlet全程手把手现在我们来做一个真实的改造在首页index.jsp添加一个图书名称搜索框输入关键词后点击搜索跳转到/book/search显示匹配的图书列表。这不是一个“Hello World”式的 Demo而是涉及视图、控制器、业务层、DAO 层的全链路改动每一步都值得你亲手敲一遍。5.1 第一步改 JSP —— 在首页加搜索表单打开index.jsp找到导航栏下方插入以下 HTML位置随意建议放在轮播图下面!-- 搜索框 -- div classsearch-box form action${pageContext.request.contextPath}/book/search methodget input typetext namekeyword placeholder请输入图书名称... value${param.keyword} stylewidth: 200px; padding: 5px; input typesubmit value搜索 stylepadding: 5px 10px; /form /div这里用了${param.keyword}回显用户输入提升体验。action指向/book/search这是我们要新写的 Servlet。5.2 第二步写 Servlet —— 创建BookSearchServlet在src/servlet/下新建BookSearchServlet.javapackage servlet; import entity.Book; import service.BookService; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; public class BookSearchServlet extends HttpServlet { private BookService bookService new BookService(); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取搜索关键词trim() 去除首尾空格 String keyword request.getParameter(keyword); if (keyword null || keyword.trim().isEmpty()) { // 关键词为空重定向回首页 response.sendRedirect(request.getContextPath() /); return; } keyword keyword.trim(); // 2. 调用 Service 层搜索 ListBook searchResults bookService.searchBooksByName(keyword); // 3. 将结果存入 request 域 request.setAttribute(searchResults, searchResults); request.setAttribute(keyword, keyword); // 用于回显 // 4. 转发到搜索结果页需新建 search.jsp request.getRequestDispatcher(/search.jsp).forward(request, response); } }5.3 第三步配 web.xml —— 让 Tomcat 知道这个新 Servlet在web.xml的servlet和servlet-mapping区域末尾添加servlet servlet-nameBookSearchServlet/servlet-name servlet-classservlet.BookSearchServlet/servlet-class /servlet servlet-mapping servlet-nameBookSearchServlet/servlet-name url-pattern/book/search/url-pattern /servlet-mapping5.4 第四步扩 Service 与 DAO —— 写搜索业务逻辑在BookService.java中添加方法public ListBook searchBooksByName(String keyword) { return bookDao.searchBooksByName(keyword); // 直接委托给 DAO }在BookDao.java中添加 SQL 查询public ListBook searchBooksByName(String keyword) throws SQLException { Connection conn JDBCUtils.getConnection(); // 使用 LIKE 模糊查询%keyword% 支持前后匹配 String sql SELECT id, name, author, price, category, description, image FROM book WHERE name LIKE ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, % keyword %); // 注意这里拼接 %不是 SQL 里写 ResultSet rs ps.executeQuery(); ListBook books new ArrayList(); while (rs.next()) { // 同 findBooksByCategory() 的封装逻辑... Book book new Book(); book.setId(rs.getInt(id)); book.setName(rs.getString(name)); // ...其他字段 books.add(book); } JDBCUtils.close(rs, ps); return books; }5.5 第五步建 search.jsp —— 展示搜索结果在项目根目录新建search.jsp% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html headtitle搜索结果/title/head body h2搜索“${keyword}”的结果/h2 c:if test${empty searchResults} p抱歉没有找到包含“${keyword}”的图书。/p /c:if c:if test${not empty searchResults} c:forEach items${searchResults} varbook div classbook-item img src${pageContext.request.contextPath}/static/images/${book.image} width100 h3${book.name}/h3 p作者${book.author} | 价格¥${book.price}/p a href${pageContext.request.contextPath}/book_detail?id${book.id}查看详情/a /div /c:forEach /c:if pa href${pageContext.request.contextPath}/返回首页/a/p /body /html5.6 验证与收尾重新编译BookSearchServlet.javajavac -cp lib/*:. -d classes src/servlet/BookSearchServlet.java重启 Tomcat访问首页输入“Java”点击搜索应看到所有书名含“Java”的图书。这个改造过程就是你未来接手任何 Java Web 项目时的日常需求来了先想清楚数据流JSP → Servlet → Service → DAO → DB再逐层补代码最后配好映射。它不快但每一步都可控、可 debug、可解释。从那以后我每次接到新需求都强制自己先画一张这样的数据流向草图再动手写第一行代码——哪怕只是加个按钮也要想清楚它点下去之后请求会走到哪个 ServletService 会调哪个方法DAO 会执行哪条 SQL。这种“慢思考”恰恰是避开线上事故的最好后悔药。希望帮到你。本文还有配套的精品资源点击获取