Jsp+Bean+Servlet网上购书系统课程设计:架构拆解与避坑指南
简介基于JSP、Bean、Servlet实现的简单网上购书系统源码及配套文档面向Java Web初学者和课程设计人群可用于理解浏览器/服务器模式下页面、业务逻辑与控制层之间的协作方式。系统实现了用户注册、登录、信息查询与信息浏览等基础功能适合作为入门级Web项目的实践蓝本。压缩包共55个文件包含15个Java源文件、15个class编译文件、6个JSP页面、2个SQL数据库脚本以及项目配置、README说明、课程综合训练PDF等体积仅1.21MB。目录按源码、配置、文档分层便于导入IDE查看运行和二次修改。资源已有173人学习下载借助SQL脚本与说明文档可快速搭建本地数据库环境对照分析注册登录及信息管理流程。1. 基于 JspBeanServlet 的网上购书系统课程设计资源拆解与复现指南我先说结论这份资源适合正在做 Java Web 课程设计、或者想用 JspBeanServlet 把 B/S 结构完整跑一遍的从业者。它实现了一个典型的网上购书系统功能上有用户注册、用户登录、信息查询、信息浏览四块看起来简单但把浏览器、Servlet、JavaBean、数据库之间的请求链路展现得很完整。如果你已经掌握 Java 基础语法想找一个能在 Tomcat 里直接跑、能改、能加功能的工程这个 zip 值得花一个下午拆开看。它不只是裸代码包还有任务书、配置目录和说明文档适合照着任务书一步步复现也适合在复现之后继续往上加功能。2. 项目结构与请求链路JSP、Bean、Servlet 在这个系统里各管哪一段2.1 先看资源包里有什么目录与文件定位zip 解压后一级目录里有 booksy、src、WebRoot、config 几个核心部分还有 README.md、LICENSE、Java Web 课程综合训练.pdf、HomeWork、.classpath、.settings、.project 和 config 等文件。.project和.classpath的存在表明这是一个 Eclipse 工程可以直接 Import.settings里是项目级配置config 目录存放数据库初始化和环境配置相关内容课程综合训练 PDF 是任务书里面会写明功能要求和评分点。我拆这类资源向来先读任务书和 README再动代码。任务书决定你要交付到什么程度README 告诉你项目怎么初始化config 目录帮你确认数据库要提前建好哪几张表。很多用户拿到 zip 就直接 Import 进 Eclipse不读说明结果因为数据库没初始化、Tomcat 版本不匹配在环境问题上耗掉大半天最后反过来怀疑资源有问题。这不是资源的问题是拆包顺序的问题。2.2 JSP、JavaBean、Servlet 三者分工谁负责展示谁负责控制谁负责数据这个项目采用了 JSP JavaBean Servlet 的分层模型。JSP 放在 WebRoot 下负责页面展示和收集用户输入Servlet 类放在 src 里负责接收请求、调用逻辑、决定跳转JavaBean 类同样放在 src 里负责业务处理和数据访问。三者之间形成一条单向依赖链JSP 依赖 Servlet 传过来的数据Servlet 依赖 Bean 的处理结果Bean 依赖 JDBC 和 MySQL 交互。为什么要这样分而不是直接在 JSP 里写 JDBC因为纯 JSP 方案在页面少的时候确实能跑一旦注册、登录、查询、浏览这些功能叠加在一起JSP 页面就会被 Java 逻辑和 HTML 混排填满后续加一个字段都要在满屏的脚本里找位置。这个项目虽然定位是“简单的网上购书系统”但它依然选择了 Servlet 转发、Bean 封装的路径目的就是让学习者建立分层意识。这套思路在后续学习 Spring MVC 时非常占便宜Spring MVC 的 DispatcherServlet 模型本质上就是把 Servlet 入口、业务 Bean、模板渲染再工程化了一遍。组件职责在本项目中的位置JSP页面渲染、收集表单输入、循环展示数据WebRoot 下的.jsp文件Servlet接收请求、解析参数、调用 Bean、控制跳转src下的*Servlet.javaJavaBean封装数据、JDBC 查询、注册登录等业务逻辑src下的*Bean.java或*Dao.java2.3 web.xml 与 Servlet 映射一个请求如何找到处理它的类这个项目的 Servlet 通过 WEB-INF 下的 web.xml 注册路径映射。web.xml 是 JSP/Servlet 项目里必须看懂的配置文件它把 URL 与处理类之间的对应关系固定下来。以下是一段典型的配置servlet servlet-nameLoginServlet/servlet-name servlet-classcom.bookstore.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login.do/url-pattern /servlet-mapping这段配置定义了两个关键信息servlet-class是处理类的完整限定名Tomcat 启动时会在 classpath 里找这个类并加载成单例url-pattern是外部访问的路径映射这里意味着所有指向/login.do的请求都会进入 LoginServlet 处理。*.do这种命名方式在传统 Java Web 项目里非常常见也有项目用/loginServlet或直接按类名映射语义上都是一回事。在 JSP 表单里提交地址要和这个映射保持一致form actionlogin.do methodpost input typetext nameusername / input typepassword namepassword / input typesubmit value登录 / /form这里有一个初学者高频踩点表单actionlogin.do是相对路径浏览器会基于当前页面 URL 的目录去拼接。如果 login.jsp 放在顶层目录最终请求地址是http://localhost:8080/booksy/login.do能正确命中映射如果 JSP 被放在了子目录里最终请求地址就会变成http://localhost:8080/booksy/subdir/login.do直接 404。遇到这种问题我一般会先看浏览器地址栏实际请求的 URL再回 web.xml 比对映射基本一眼定位。2.4 从请求到响应的完整链路forward 与 redirect 的语义差别把整条链路串起来看用户打开 login.jspTomcat 的 JSP 引擎先把 JSP 编译成 Servlet 类并输出 HTML用户在表单里输入账号密码后提交请求进入 LoginServlet 的 doPost 方法Servlet 取出参数构造 UserBean 去数据库校验校验结果被放进 request 或 session最终通过 forward 或 redirect 回到 JSP 渲染。forward 和 redirect 是这里最容易混淆的一对概念。forward 是服务器内部跳转浏览器地址栏保持不变request 里存的数据在跳转后的页面仍可访问适合登录失败时需要把错误提示带回 JSP 的场景。redirect 是客户端重定向浏览器重新向新地址发一次新请求request 里的数据全部丢失适合登录成功后跳回主页、防止用户在地址栏重复刷新提交的场景。判断标准很直接数据是否要跨越这一次请求继续存在。错误提示这类一次性的消息用 forward登录成功这类需要“断掉旧请求”的场景用 redirect。这两个动作里选错一个就能制造出“登录失败有提示登录成功反而看着像没跳转”的奇怪现象。3. 把系统跑起来环境搭建、部署步骤与配置参数3.1 版本选型JDK 1.8 配 Tomcat 9最省心的一组组合这个项目的代码基于javax.servlet命名空间对应的是 Tomcat 8 到 9 的时代。如果你本机装的是 Tomcat 10 或 11项目直接编译不过因为从 Tomcat 10 开始包名整体迁移到了jakarta.*。为了避免无谓的折腾推荐的组合是 JDK 1.8 Tomcat 9 Eclipse 2020 之后版本。这套组合兼容性经过了大量课程设计项目的检验遇到报错基本都能在网上找到现成答案。MySQL 版本也需要提前确认。如果你的环境是 MySQL 8.0 及以上驱动类名要换成com.mysql.cj.jdbc.DriverJDBC URL 要加时区参数MySQL 5.7 则相对省心用com.mysql.jdbc.Driver即可。很多新机器默认装的就是 MySQL 8所以看到连接失败或 SSL 相关报错时先别怀疑代码先确认驱动版本对不对。3.2 Eclipse 导入与 Tomcat 部署分步操作与验证既然工程里带着.project和.classpath导入方式直接用 Eclipse 的 Existing Projects into Workspace 就够了不需要新建 Dynamic Web Project 再拷贝源码。建议按下面顺序走File - Import - General - Existing Projects into Workspace选择解压目录。导入后右键项目 - Properties - Targeted Runtimes勾选你建好的 Tomcat 9 运行时。在 Servers 面板新建 Tomcat v9.0 Server把项目 Add 进去。启动 Tomcat看 Console 日志里有无 SEVERE 异常。右键项目 - Properties - Web Project Settings把 Context Root 改成简短路径比如booksy。浏览器访问http://localhost:8080/booksy/看到首页即部署成功。把 Context Root 改成短路径这件事看起来只是少打几个字符实际上能减少一大堆相对路径计算错误。Web 应用上下文变短之后JSP 里所有基于上下文拼接的资源路径和 action 路径都更好排查。# 查看 Tomcat 部署目录下项目是否完整 ls -la ${CATALINA_HOME}/webapps/ # 实时追踪启动日志 tail -f ${CATALINA_HOME}/logs/catalina.out启动日志是问题定位的第一现场。正常启动时日志末尾会出现Server startup in [xxxx] milliseconds如果出现SEVERE级别的堆栈不要跳过直接看堆栈第一行那基本就是根因。这里尤其提醒一句Tomcat 的 webapps 目录下有时会保留上一次部署的残留目录导致新部署和旧文件混在一起我遇到过的诡异问题里有一半是这个原因造成的。3.3 数据库初始化与 JDBC 配置把每一条参数都对齐config 目录里存放的应该是数据库初始化方案和必要配置。先检查有没有现成建表脚本有的话直接执行没有就按常规需求自己建。一个网上购书系统至少要两张表用户表对应注册登录图书表对应查询浏览。参考建表语句如下CREATE DATABASE IF NOT EXISTS bookstore DEFAULT CHARACTER SET utf8mb4; USE bookstore; CREATE TABLE IF NOT EXISTS t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(50) NOT NULL, email VARCHAR(100) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE IF NOT EXISTS t_book ( id INT PRIMARY KEY AUTO_INCREMENT, book_name VARCHAR(100) NOT NULL, author VARCHAR(50), price DECIMAL(8,2), description VARCHAR(500) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有两个设计细节值得注意。UNIQUE约束加在 username 上是给注册功能的“用户名不重复”校验提供数据库层面的最后防线即使代码里漏掉了唯一性检查数据库层也会拦截重复插入。DEFAULT CHARSETutf8mb4这个设置解决了中文乱码的底层问题utf8mb4 是完整 UTF-8 支持比历史遗留的 utf8 字符集覆盖面更广。这两点放在课程设计里是评分老师会关注的细节。JDBC 连接代码通常集中在 Bean 类里典型的写法如下String driver com.mysql.jdbc.Driver; String url jdbc:mysql://localhost:3306/bookstore?useSSLfalsecharacterEncodingutf8; String username root; String password 123456; Class.forName(driver); Connection conn DriverManager.getConnection(url, username, password);三行配置对应三个变量driver 是驱动类名url 是数据库地址username 和 password 是数据库账号。characterEncodingutf8保证 JDBC 层和数据库之间的中文传输编码一致。useSSLfalse是开发环境的省心选项跳过 SSL 证书校验避免本地连接时处理证书配置生产环境不建议这样。MySQL 8 环境下把 driver 换成com.mysql.cj.jdbc.Driver同时在 url 末尾追加serverTimezoneAsia/Shanghai否则会报时区错误。3.4 构建路径与部署配置编译期不报错运行时崩溃的根源导入工程后如果看到好几处红叉第一反应不要是改源码而是检查构建路径。右键项目 - Properties - Java Build Path看三件事JRE System Library 是否指向 JDK 而不是 JREServer Runtime 有没有把 Tomcat 加进来WEB-INF/lib 里的 jar 是否都在。缺了 Server Runtime所有javax.servlet相关的引入会全部标红。另一个隐蔽的问题是 Deployment Assembly。Eclipse 编译时能找到 jar不代表 Tomcat 运行时有这个 jar。项目部署到 Tomcat 时Deployment Assembly 决定了哪些目录会被打包进 WEB-INF/lib。如果这里没有包含 WebContent/WEB-INF/lib哪怕源码里 import 不报错运行时也会抛 ClassNotFoundException。验证方式很简单部署后去 Tomcat 的 webapps 对应目录下检查项目的 WEB-INF/lib 里有无 jar 文件没有就说明打包规则不对。我的一般原则是让包含 jar 的目录只属于项目自身而不是把所有 jar 丢到 Tomcat 的全局 lib 里后者虽然能跑通但多个项目共存时非常容易引发版本冲突。4. 核心功能模块拆解注册、登录、查询、浏览的代码实现路径4.1 用户注册Bean 封装、唯一性校验与防重复提交注册功能解决的核心问题是表单提交后系统如何接收数据、如何判断用户名是否已存在、如何存储数据、失败后如何把错误信息送回页面。注册 Servlet 的 doPost 流程如下String username request.getParameter(username); String password request.getParameter(password); String email request.getParameter(email); UserBean userBean new UserBean(); if (userBean.isUsernameExists(username)) { request.setAttribute(registerError, 该用户名已被注册); request.getRequestDispatcher(register.jsp).forward(request, response); } else { boolean result userBean.registerUser(username, password, email); if (result) { response.sendRedirect(login.jsp); } else { request.setAttribute(registerError, 注册失败请稍后重试); request.getRequestDispatcher(register.jsp).forward(request, response); } }这段代码把流程控制放在了 Servlet 层把查询和插入放在 UserBean 里职责边界清晰。isUsernameExists先执行一次查询registerUser再执行插入这两个方法内部用的是 PreparedStatement 预编译?占位符可以防 SQL 注入。在这里我多提醒一句课程设计里的代码哪怕功能再简单也别用字符串拼接把用户输入直接拼进 SQL预编译是底线不是进阶技巧。注册成功之后用的是redirect而不是forward。原因很具体redirect 会让浏览器对 login.jsp 发一次全新的请求之前那个带着表单参数的注册请求就此了断用户无论怎么刷新都不会重复插入记录。这是写操作后的标准动作课程设计的答辩老师很吃这一套。4.2 用户登录Session 管理与登出清理登录流程在第三章给出过路由代码这里补充 Session 管理的完整视角。request.getSession().setAttribute(user, username)这一行背后实际上做了两件事先根据请求携带的 JSESSIONID 查找当前会话找不到就新建一个再把用户名绑定到会话属性上。浏览器通过 Cookie 持有 JSESSIONID从而在后续请求中维持登录状态。web.xml 里可以配置session-timeout单位是分钟默认值是 30 分钟超时不访问服务端会自动放弃 Session。这里最常见的误用是把登录用户存进 request。request 生命周期只覆盖一次请求forward 到 JSP 还能取到浏览器一旦发新请求就全部丢光。判断登录状态必须从 session 里取检查session.getAttribute(user)是否为 null而不是 request 里有没有值。登出时我推荐直接session.invalidate()而不是只removeAttribute(user)。invalidate 会把整个 Session 销毁连带里面的所有属性更彻底也更安全。只删单个属性会让 Session 对象继续存活对于这个只有登录状态、没有其他长生命周期数据的系统直接销毁是最干净的做法。4.3 图书列表与图书详情查询与浏览的路由设计信息查询对应图书列表信息浏览对应图书详情。常见的实现是同一个 Servlet 用action参数分流action 为list时查询全部图书并 forward 到列表页action 为detail时根据bookId查询单本图书并 forward 到详情页。这种路由写法的好处是 Servlet 数量少工程文件紧凑代价是一个类里塞了多个业务动作拆分不够细时阅读体验略差。在 JspBeanServlet 这个资源场景里这算合格从业者最常见的选择。列表页 JSP 是理解这套架构的窗口。循环输出的核心片段如下% ListBook bookList (ListBook) request.getAttribute(bookList); if (bookList ! null !bookList.isEmpty()) { for (Book book : bookList) { % tr td% book.getBookId() %/td tda hrefbookDetail.jsp?bookId% book.getBookId() %% book.getBookName() %/a/td td% book.getPrice() %/td /tr % } } %注意% %输出表达式会直接调用out.print()在循环里每执行一次就输出一行表格。判空条件bookList ! null !bookList.isEmpty()必不可少否则查询结果为空时 JSP 渲染会直接空转页面只有一个表头用户体验很差。合理做法是在 else 分支里输出一行“暂无图书”的提示这个细节在答辩提问时经常被问到。详情页链接用 URL 携带bookId参数详情 Servlet 取到参数后再查询单条记录。这个参数是数字型不存在特殊字符串注入的问题但如果你把这个模式平移到字符串参数的场景一定要做编码处理否则 URL 里一个中文书名就把地址搞坏了。4.4 JSP 内置对象与数据传递边界request 和 session 怎么选这个系统里最常打交道的内置对象有四个它们的存活范围差别很大内置对象生命周期典型用途request一次请求内有效表单参数、查询结果、错误提示response一次响应内有效重定向、设置响应头、输出内容session一次浏览器会话内有效登录状态、购物车数据application整个应用运行期间有效全局计数、公共配置选哪个对象存数据判断标准只有一个这个数据需要活多久。只需要在“当前请求 转发目标页面”里存在就放 request需要跨页面、跨多次请求保持就放 session应用级别共享且重启可以丢才考虑 application。一个反面教材是把登录状态放 application那结果就是一个用户登录后所有用户都变成已登录状态这在任何系统里都是事故级别的缺陷。5. 避坑与常见问题新手跑这个项目最容易翻车的五个地方5.1 页面中文乱码不是加一行配置就能解决现象浏览器里看到一排问号或乱码数据库里手工查询也是乱码。原因乱码问题本质上是“JSP 页面编码、Servlet 请求编码、JDBC 连接编码、数据库表编码”四层不一致。只把 JSP 页面声明为 UTF-8 远远不够Servlet 里如果没有先调用request.setCharacterEncoding(UTF-8)就去取参数表单提交的中文会按 Tomcat 默认字符集解析进内存时就已经错了后面再怎么调整输出都没有意义。解决四层统一设置为 UTF-8。JSP 页面头部确保pageEncodingUTF-8和contentTypetext/html; charsetUTF-8Servlet 里在读取任何参数之前执行request.setCharacterEncoding(UTF-8)JDBC URL 带characterEncodingutf8建表时指定DEFAULT CHARSETutf8mb4。四层全部对齐乱码才会真正消失。5.2 表单提交 404url-pattern 和 action 对不上现象点击登录按钮后地址栏请求了一个看起来正常的 URL结果出现 Tomcat 404 页面。原因表单的action是相对路径浏览器基于当前页面所在目录拼接最终 URL而 web.xml 里配置的 Servlet url-pattern 是绝对路径。当 JSP 不在项目根目录时两者拼出来的地址就对不上。还有一种情况是改了项目名Context Root 变化之后旧地址全部失效。解决先看浏览器开发者工具 Network 面板里实际发出的请求 URL把它复制出来和项目根路径对比。然后检查 web.xml 的url-pattern确认 JSP 表单的 action 是否完整匹配。最稳妥的做法是在所有 JSP 页面的链接和表单 action 前统一带上上下文路径前缀让请求地址不受当前页面所在目录影响。5.3 运行时报 ClassNotFoundException现象Eclipse 里源码没有任何红叉启动 Tomcat 后控制台却报ClassNotFoundException。原因Eclipse 的编译 classpath 和 Tomcat 的运行时 classpath 是两套独立体系。编译时能找到 jar只能说明构建路径正确运行时找不到 jar说明部署产物里没有包含依赖。最常见的原因是 Deployment Assembly 里没有把 WEB-INF/lib 目录加进去打包阶段漏掉了。解决右键项目 - Properties - Deployment Assembly确认 WEB-INF/lib 在列表里打开项目目录确认 jar 文件确实在WebRoot/WEB-INF/lib下执行 Project - Clean 清理旧编译产物重新部署到 Tomcat最后去 Tomcat webapps 下对应项目的 WEB-INF/lib 目录确认文件真实存在。这套流程走完运行时依赖一定不会缺失。5.4 数据库连接失败报错信息已经告诉了你问题方向现象访问任何与数据库交互的页面都报 500控制台里有Access denied for user rootlocalhost或者Communications link failure的字样。原因两条报错指向两个不同的方向。Access denied是账号密码错误或权限不足Communications link failure多半是 MySQL 服务未启动、端口不对或者网络不通。MySQL 8 换了默认认证插件之后旧驱动连不上也是常见原因但这一类报错的信息会比较隐晦。解决先在命令行用mysql -u root -p确认本机能正常连库。这一步通过说明服务和账号没问题再去检查代码里的驱动类名和 URL 参数这一步失败就先解决 MySQL 服务本身。排查顺序我习惯从底层往上走服务是否启动 - 账号密码与权限 - 驱动版本 - URL 参数格式不要一上来就改代码。至于是否需要在 URL 里加serverTimezoneAsia/Shanghai取决于 MySQL 版本加了不影响功能不加在 MySQL 8 下反而会报错。5.5 登录状态丢失Session 生命周期被谁截断了现象登录成功后跳转到主页几秒不操作再刷新又被送回登录页。原因两个可能性。一是web.xml里session-timeout配置得过短服务端提前销毁了 Session二是浏览器禁用或隔离了 Cookie导致 JSESSIONID 没有被保存下来浏览器每次发请求都不带会话 ID服务端每次只能新建 Session于是登录状态自然消失。解决打开 web.xml 确认session-timeout不小于 30 分钟在浏览器开发者工具 Application 面板里查看 Cookie 中 JSESSIONID 是否存在、有效期是否正常。如果项目运行环境确实不允许使用 Cookie那只能在重定向和生成链接时调用response.encodeURL()让 Tomcat 把 JSESSIONID 拼进 URL 里维持会话但这属于最后的退路默认情况优先保证 Cookie 可用。这五条是 JSPBeanServlet 项目里出现频率最高的问题其中乱码和 ClassNotFoundException 最耗时间。还有一个通用建议每次修改 JSP 或 web.xml 之后做一次 Clean 再重新部署不要依赖 Tomcat 热部署JSP 文件更新时热部署经常不重新编译表现出来的就是“我改了代码但页面没变”。6. 在原有系统上扩展加一个购物车模块并通过六个检查点6.1 最小改动的购物车设计这份资源本身不含购物车功能。如果想在答辩时展示“在原项目上做过扩展”加购物车是很合适的方向。我的做法是新增三个文件CartItem JavaBean、CartServlet、cart.jsp原有代码全部不动。购物车本体用MapInteger, CartItem存在 Session 里。往购物车加书的逻辑如下MapInteger, CartItem cart (MapInteger, CartItem) session.getAttribute(cart); if (cart null) { cart new HashMapInteger, CartItem(); session.setAttribute(cart, cart); } CartItem item cart.get(bookId); if (item null) { cart.put(bookId, new CartItem(bookId, 1)); } else { item.setQuantity(item.getQuantity() 1); }这段代码先尝试从 Session 里取购物车取不到就新建并放回 Session再根据bookId判断是已有条目还是新条目已有条目数量加一新条目以数量 1 创建。这个设计把购物车限制在单次会话里Tomcat 重启后购物车数据清空符合课程设计的预期不说跨重启持久化。6.2 六个检查点验证购物车模块是否真的合格购物车写完我给自己定了六个检查点每一条不过都算失败检查点操作预期结果首次加入未登录时点击加入购物车跳转到登录页重复加入同一本书连续点击两次数量累加为 2跨页保持进入详情页后再回购物车购物车数据不丢失清空操作点击清空按钮购物车列表为空登出失效登出后再访问购物车提示未登录非法参数手动改 URL 里的 bookId 为负数或字符串页面不报 500第一个检查点验证的是访问控制第二个验证累加逻辑第三个验证 Session 跨页能力第四个验证清空动作的实现第五个验证登出是否真的销毁了会话第六个则是典型的参数安全验证。这六个点全部通过购物车模块就不只是“能跑”而是“经得起问”。从那以后我每次拿到课程设计资源都会先花半小时做一遍全状态扫描不登录访问受限页面、往 URL 里塞非法参数、连续点两次提交按钮、用两个不同浏览器开两个会话模拟并发。这套走完项目的边界感就清晰了答辩时心里也更有底。希望这个拆解和购物车扩展思路能帮到你让你在动手之前就知道下一步该往哪走。本文还有配套的精品资源点击获取