JavaWeb商城源码实操:Servlet+JSP+MySQL配置与部署全指南

发布时间:2026/10/7 20:44:44
JavaWeb商城源码实操:Servlet+JSP+MySQL配置与部署全指南 简介这是一份JavaWeb网上购物商城项目的完整源码配套SQL数据库脚本主要面向计算机、通信、人工智能、自动化等专业学生适用于期末课程设计、课程大作业及毕业设计。项目代码经过调试与测试可正常运行覆盖商城前台展示、购物车、订单处理、后台管理等典型功能能帮助学习者理解JavaWeb项目的整体结构与开发流程。资源包共252个文件包含Java源文件、JSP动态页面、JavaScript脚本、CSS样式表、XML配置文件、JAR依赖库、SQL数据库脚本以及页面图片等压缩包大小26.35MB目录清晰方便按模块查找和使用。已有1969人浏览学习适合具有一定JavaWeb基础、希望快速上手完整商城项目的读者。通过参考源码可掌握前后端交互、数据库设计、会话管理等核心点能力较强的读者还可在此基础上修改功能完成个性化毕业设计或课程作业。1. JavaWeb商城购买源码课设救急模板能不能跑全看这三处配置期末旺季一到JavaWeb 课程设计群里问得最多的就是“有没有商城项目源码”。这份 JavaWeb 购物商城源码就是为这个场景准备的Java 语言编写源码和数据库脚本打包在一起从用户注册、商品浏览、购物车、下单支付到后台的商品管理和订单处理完整闭环。它适合毕业设计、期末大作业也适合想拿 Servlet JSP MySQL 练手的人。需要说明的是这类源码包能不能跑起来不取决于代码而取决于三处配置JDK 与 Tomcat 版本匹配、数据库连接参数、部署方式。这篇笔记就把这三处拆开讲清楚顺带把最常见的几个坑也列出来。2. 技术选型与项目结构ServletJSPMySQL 这套经典组合好在哪实话讲现在外面 JavaWeb 课设的代码包十有八九是 Spring Boot 写的。这个项目不一样它是原生的 Servlet JSP JDBC没有 Spring 全家桶也就没有那一大堆需要靠 Maven 拉取的依赖。课设场景下这反而是优点工程结构直观请求处理链路短跑起来对机器和网络环境的要求低答辩时老师问“你这个请求是怎么从页面到数据库的”你能把每一层都讲清楚。数据库用 MySQL连接方式走的是连接池工程里自带 C3P0 或类似的数据源配置。页面是 JSP配合 EL 表达式和 JSTL 渲染数据浏览器端只用了少量 jQuery没有前后端分离也没有 Node 环境的要求。这一整套组合下来只要电脑上装了 JDK 8、Tomcat 9、MySQL 5.7 或 8.0半小时之内就能看到登录页。2.1 三层架构目录划分controller、service、dao 各管什么工程内部是典型的 JavaWeb 三层架构这个分层直接影响答辩时你怎么讲清楚“高内聚低耦合”controller 层也叫 servlet 层负责接收浏览器请求、取参数、调用 service、决定跳转哪个页面service 层负责业务逻辑比如登录校验、下单时库存扣减、订单状态变更dao 层负责 JDBC 访问数据库执行 SQL把 ResultSet 封装成对象拿登录这个功能举例三层分别长这样。controller 层就是登录请求对应的 ServletWebServlet(/login) public class LoginServlet extends HttpServlet { private final UserService userService new UserService(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 取页面参数登录表单里两个字段的 name String username req.getParameter(username); String password req.getParameter(password); // 2. 调 service 层做业务校验 User user userService.login(username, password); if (user ! null) { // 登录成功把用户对象放进 session后续页面靠它识别身份 req.getSession().setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /index); } else { // 登录失败带上提示信息转发回登录页 req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }这段代码里值得注意两个点一是req.getParameter取的字段名必须和 JSP 表单里的name属性一致二是登录成功后用的是sendRedirect而不是forward。原因是 redirect 走的是浏览器重新发起请求地址栏变成/index刷新不会出现“表单重复提交”的浏览器报警而 forward 是服务器内部跳转地址栏不变刷新会把登录动作再执行一遍。这个细节答辩时讲出来老师会觉得你真写过代码。service 层和 dao 层的配合核心是“service 不碰 SQLdao 不碰业务”public class UserService { private final UserDao userDao new UserDao(); public User login(String username, String password) { // 业务层做数据校验或处理比如把明文密码再做一次 MD5 // 如果数据库里存的不是明文这里就要做同样的算法再传给 dao return userDao.selectByUsernameAndPassword(username, password); } }public class UserDao { public User selectByUsernameAndPassword(String username, String password) { // 用 JDBC 写预编译 SQL? 占位符防止 SQL 注入 String sql select * from user where username ? and password ?; // 走 DBUtil 拿连接PreparedStatement 绑定参数 // 执行后把 ResultSet 里的 uid、username、nickname 等字段封装成 User 对象 // 查不到记录就返回 nullService 层根据 null 判断登录失败 return null; } }DBUtil是工程里自带的数据库连接工具类一般都在util包下用了 C3P0 或 Druid 连接池。连接池的作用是避免每次数据库访问都重新建立 TCP 连接课设规模下性能差异感觉不出来但它能避免一种典型的翻车数据库连接没关运行一段时间后报Too many connections。所以看代码时重点看一眼 dao 层有没有在 finally 里关 ResultSet、Statement、Connection。2.2 前端页面体系JSP 页面与静态资源分工页面这块工程的逻辑是 JSP 渲染动态数据css、js、images目录放静态资源。商品列表页是典型的“JSP EL JSTL”组合。Servlet 把查询结果放进 request 后转发给 JSPJSP 里用${}直接取数据c:forEach items${page.list} varp div classitem a hrefdetail?pid${p.pid} img src${pageContext.request.contextPath}/${p.image} / p classname${p.pname}/p p classprice${p.price}/p /a /div /c:forEach这里有一个新手特别容易踩的坑${page.list}里的page是 Servlet 里req.setAttribute(page, pageBean)设置的对象名它必须和 JSP 里写的一致改一处就得同步改另一处。${p.pname}是 EL 表达式调用的p.getPname()方法所以实体类的属性名和 getter 方法的命名必须遵循 JavaBean 规范否则页面上直接显示空字符串而且不报任何异常。这种“数据没渲染出来但不报错”的问题是最难排查的我一般会先看商品实体类有没有getPname()方法。2.3 依赖与运行环境匹配JDK、Tomcat 版本决定了你能不能跑起来这个项目是原生 JavaWeb 工程没有 Maven 的pom.xml依赖靠的是 Tomcat 自带的servlet-api.jar所以版本匹配是第一步。常见做法是 JDK 1.8 配 Tomcat 9.0.x。如果你电脑上装的是 Tomcat 10 及以上那就要注意了Tomcat 10 把javax.servlet包名改成了jakarta.servlet代码里所有import javax.servlet.*和WebServlet注解都会报红编译直接失败。解决办法是卸载 Tomcat 10换回 Tomcat 9.0不要在代码层面硬改几十个 Servlet 逐个改 import 没什么性价比。数据库驱动这块MySQL 5.7 用mysql-connector-java-5.1.xMySQL 8.0 用mysql-connector-java-8.0.x。判断工程用的是哪个版本直接看lib目录下的 jar 包文件名就行了。如果连接串里写的是jdbc:mysql://localhost:3306/shopdb?useSSLfalseserverTimezoneAsia/Shanghai那必然是 8.x 的驱动如果连接串很干净没有serverTimezone多半是 5.1.x。这个细节直接关系下一章的数据库配置能不能一次连上。3. 数据库设计与核心功能订单状态机和库存扣减体现业务深度商城项目的数据库表设计是有规律可循的。拿到shopdb.sql之后别急着往 Navicat 里拖先打开脚本扫一遍表结构。这么做有两个好处一是答辩时被问到“表之间什么关系”你能张口就来二是能在导数据之前发现脚本里有没有明显的问题。这个工程的表设计走的是经典商品系统。3.1 四张核心表怎么建从用户表到订单明细表用户表和商品表是基础购物车表和订单表是业务核心。建表脚本大概长这样CREATE TABLE user ( uid int(11) NOT NULL AUTO_INCREMENT, username varchar(20) NOT NULL, password varchar(32) NOT NULL, nickname varchar(20) DEFAULT NULL, phone varchar(11) DEFAULT NULL, address varchar(255) DEFAULT NULL, PRIMARY KEY (uid), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8;用户表的password字段长度是 32 位对应的是 MD5 加密后的 32 位十六进制字符串。如果注册时密码长度超过 20 位要么是业务层做了加密后存储要么是明文的限制。课程设计里用 MD5 就够了但答辩时老师可能会问“MD5 安全吗”你得准备一句MD5 不可逆但容易撞库生产环境会用加盐或者 BCrypt课设场景用 MD5 是为了演示加密流程。CREATE TABLE product ( pid int(11) NOT NULL AUTO_INCREMENT, pname varchar(100) NOT NULL, price decimal(10,2) NOT NULL, stock int(11) DEFAULT 0, image varchar(255) DEFAULT NULL, pdesc varchar(500) DEFAULT NULL, cid int(11) DEFAULT NULL, status tinyint(1) DEFAULT 1, PRIMARY KEY (pid), KEY idx_cid (cid) ) ENGINEInnoDB DEFAULT CHARSETutf8;商品表里关键的字段是status和stock。status控制上下架1 表示上架0 表示下架前台列表只查status 1的商品后台管理员可以修改这个值。stock是库存下单时要扣减后面专门讲这个逻辑。price必须用decimal(10,2)而不是 float 或 doublefloat 在电商场景下算总价容易产生小数误差这是很多老项目里价格对不上的根源。CREATE TABLE orders ( oid int(11) NOT NULL AUTO_INCREMENT, uid int(11) NOT NULL, order_no varchar(32) NOT NULL, total_price decimal(10,2) NOT NULL, status tinyint(1) DEFAULT 0, create_time datetime DEFAULT CURRENT_TIMESTAMP, pay_time datetime DEFAULT NULL, consignee varchar(20) DEFAULT NULL, phone varchar(11) DEFAULT NULL, address varchar(255) DEFAULT NULL, PRIMARY KEY (oid), KEY idx_orders_uid (uid), CONSTRAINT fk_orders_uid FOREIGN KEY (uid) REFERENCES user (uid) ) ENGINEInnoDB DEFAULT CHARSETutf8;订单表把收货人姓名、电话、地址冗余进了订单而不是通过外键去关联用户表的地址。这是刻意的设计下单那一刻的收货信息是快照用户日后改了默认地址已经生成的订单不受影响。答辩时能说出“冗余快照”这四个字老师会认为你想过真实业务。CREATE TABLE order_item ( item_id int(11) NOT NULL AUTO_INCREMENT, oid int(11) NOT NULL, pid int(11) NOT NULL, pname varchar(100) DEFAULT NULL, price decimal(10,2) DEFAULT NULL, num int(11) DEFAULT 1, PRIMARY KEY (item_id), KEY idx_item_oid (oid) ) ENGINEInnoDB DEFAULT CHARSETutf8;订单明细表的存在是为了记录“下单那一刻买了什么、什么价格”。它不设外键关联商品表pname和price直接冗余在明细里。原因很实际商品改名、改价之后历史订单还是要能打出当时的商品名和成交价。这个“历史快照”思路也是订单系统和普通增删改查明显的分水岭。3.2 订单状态流转从待付款到已完成的一条状态机订单表里status字段用数字表示状态常见做法是status含义对应操作0待付款用户下单后默认状态1已付款待发货用户点击支付或后台模拟支付后进入2已发货待收货后台订单管理里点击发货3已完成用户确认收货或后台强制完成-1已取消用户取消或超时未支付状态流转不是随便 update 一个数字正常代码里会做一个状态变更方法限定“从什么状态能变成什么状态”。比如发货操作必须要求当前状态是 1如果订单处于待付款状态后台点击发货应该被拦截。代码示意public boolean ship(int oid) { // 发货操作把订单从待发货(1)改为已发货(2) // SQL 里带上当前状态条件防止并发下状态被错误覆盖 String sql update orders set status 2 where oid ? and status 1; int rows orderDao.update(sql, oid); // 返回影响行数0 说明订单状态不是待发货属于非法操作 return rows 0; }这条 update 语句带上了and status 1课设规模下看不出必要性但它是状态机正确性的关键防线。不带这个条件的话哪怕订单已经发货变成 2再点一次发货按钮也会成功执行界面上的状态就被打回去了。加了条件之后第二次点击影响行数为 0业务层拦截掉不给用户重复操作留机会。后台订单列表页一般会给管理员一个下拉框或者一组按钮去切换订单状态切换时调用对应的 Service 方法。你在跑这个项目时可以专门测试一下“乱序切换”——比如跳过付款直接发货看看代码能不能拦住。能拦住说明工程质量不错拦不住答辩前自己改掉这一个点就够你写进“系统不足与改进”了。3.3 商品上下架与库存扣减两个容易写错的地方先说库存。最简单也最容易出错的下单逻辑是这样先查出商品库存判断库存够不够够就 update 库存再插入订单。这个顺序在并发场景下是错的价格、超卖问题比如一件商品只剩 1 件两个用户同时下单两个线程都查出库存是 1都认为可以买然后都执行了 update库存就变成了 -1。防超卖扣减的标准写法是把判断和扣减合在一条 SQL 里-- 下单扣库存必须带上 stock ? 条件影响行数为 0 说明库存不足 update product set stock stock - 1 where pid ? and stock 1;这条语句的巧妙之处在于MySQL 的行锁保证同一时间只有一个事务能执行成功update 的where条件里包含了库存判断影响行数是 0 就说明已经被别人抢走了。业务层在 Java 代码里判断public boolean decreaseStock(int pid, int num) { String sql update product set stock stock - ? where pid ? and stock ?; return productDao.update(sql, num, pid, num) 0; }库存不足时返回 false然后整个下单流程回滚事务商品不会进入订单表。这是电商系统的红线问题聊天里常说的“超卖”就是这么来的。课设项目里没有高并发压测但代码写不写这个条件识货的人一眼就能看出来。再说上下架。前台商品列表在 service 层或 SQL 里都强制拼上status 1public ListProduct findOnSaleList(int cid) { // 前台列表只查上架商品下架商品不会出现在首页和分类页 String sql select * from product where cid ? and status 1; return productDao.queryList(sql, cid); }后台管理端在编辑商品时维护status字段前台不需要知道这个字段的存在直接过滤掉。很多跑不起来或者页面异常的代码包就是在这里混了前台查询漏掉状态过滤导致后台下架的商品仍然显示答辩演示删商品时当场翻车。4. 本地跑起来IDEA 导入、数据库初始化、Tomcat 配置三步走拿到压缩包解压之后你会看到源码目录、shopdb.sql数据库脚本、README.txt说明文件。接下来要做的就是把源码导进 IDEA把数据库建起来然后配置 Tomcat 启动。这三步每一步都有版本陷阱按顺序操作基本一次过。4.1 IDEA 导入工程JDK 版本和模块识别是第一步打开 IDEAFile - Open选择解压出来的源码根目录。如果工程里有pom.xmlIDEA 会识别成 Maven 工程并自动下载依赖如果工程没有 Maven 结构IDEA 会把它当成普通目录这时需要手动配模块。shop-web/ ├── src/ │ ├── main/ │ │ ├── java/ // 所有 .java 源码 │ │ ├── webapp/ // JSP、css、js │ │ └── resources/ // db.properties 等配置文件 ├── lib/ // mysql-connector jar 包 └── shopdb.sql导入后的第一步不是写代码而是确认三处设置File - Project Structure - Project SDK确认是 1.8不是 17 或 21。版本太高容易把代码里过时的语法标红。Project Structure - Modules - 选中你的模块 - Dependencies点击加号把lib目录下的 jar 包数据库驱动加连接池 jar逐个添加进去。漏掉这一步运行时会报ClassNotFoundException: com.mysql.cj.jdbc.Driver。File - Settings - Build, Execution, Deployment - Compiler - Java Compiler确认字节码版本是 1.8。如果是 Maven 工程逻辑一样只是依赖从中央仓库拉。课设网络环境经常拉一半超时这里我建议切到国内镜像源在settings.xml里加阿里云镜像能省不少等待时间。4.2 数据库初始化建库、导表、改数据库连接配置数据库初始化用命令行或者图形化工具都行。命令行是mysql -u root -p create database shopdb default character set utf8; use shopdb; source C:/Users/你的目录/shopdb.sql;source后面跟的是shopdb.sql的绝对路径路径里有中文或空格会报错直接放 D 盘根目录最省事。导入完成后执行show tables;能看到 user、product、orders、order_item 这几张表就说明脚本执行成功。除了表脚本里一般还有几条测试数据比如管理员账号和几件商品方便你启动后直接登录后台。数据库建好之后改连接配置。连接参数集中在src/resources/db.properties也有工程放在src/db.properties按实际位置找jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/shopdb?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 jdbc.usernameroot jdbc.password你的数据库密码 jdbc.maxPoolSize10 jdbc.minPoolSize3serverTimezoneAsia/Shanghai是 MySQL 8.0 的强制要求不加会报时区错误。characterEncodingutf8保证写入数据库的中文不是乱码。useSSLfalse是关闭 SSL 连接警告MySQL 8.0 默认开启 SSL本地开发关闭就好否则控制台会刷一堆 SSL 告警日志。jdbc.password改成你自己本机 MySQL 的密码。这个文件改完记得确认 IDEA 有没有把它复制到 target 或 out 目录经常有人改完源文件但构建输出目录里还是旧配置启动后报连接失败找半天才发现是这个问题。4.3 配置 Tomcat 并启动从 Add Configuration 到浏览器看到首页IDEA 里配置 Tomcat 的路径是Run - Edit Configurations - 左上角加号 - Tomcat Server - Local。在Application server下拉框里选你本机安装的 Tomcat没有的话先New指向 Tomcat 解压目录。然后切到Deployment标签页点加号选Artifact...选中带war exploded的那个选项。这里要特别说明课设工程在 IDEA 里启动用war exploded解压目录模式是常见做法它不需要每次改代码都重新打 war 包热部署快。Application context填/shop或者保持空和/一致取决于你想用什么路径访问。填了/shop之后浏览器访问地址就是http://localhost:8080/shop/index。配置完成后点 Debug 或 Run 启动。第一次启动可以从下方Tomcat Log控制台观察日志看到下面这行就说明启动成功INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [C:\...\apache-tomcat-9.0.x\webapps\shop]打开浏览器输入http://localhost:8080/shop/index看到商品列表页面整个项目就跑起来了。如果页面报 404先检查Application context和浏览器地址是否一致如果报 500 且提示数据库连接失败回头检查 4.2 的配置如果页面渲染出来但不显示数据多半是 url 路径里的上下文没带上或者 JSP 里的${pageContext.request.contextPath}写漏了。5. 避坑排查导入即红、连不上库、乱码、登录失效四件事以下是整理自大量实操的踩坑记录每条都按照“现象 - 原因 - 解决”来写。代码跑不起来的 90% 原因都集中在这几条里排查顺序也按这个来。5.1 导入即红javax.servlet找不到现象IDEA 里所有import javax.servlet.*和WebServlet注解全部红色报错编译不通过。原因本机安装的是 Tomcat 10 及以上版本而 Tomcat 10 的 Servlet API 包名从javax.servlet改成了jakarta.servlet旧代码无法直接使用。Tomcat 9 及以下版本仍然是javax.servlet。解决卸载 Tomcat 10下载并安装 Tomcat 9.0.xApache 官网的 Binary Distributions 目录选择core下的 zip 包然后在 IDEA 的Run - Edit Configurations里重新指定 Application server。注意替换之后要把 Artifact 里的依赖也重新刷新一下右键模块Open Module Settings - Dependencies确认 servlet-api 已经指向新的 Tomcat。从那以后我每次提醒别人都会多说一句这个坑不用改代码换版本是唯一后悔药。5.2 连不上数据库CommunicationsException与serverTimezone现象Tomcat 启动时或浏览器第一次访问数据库时报Communications link failure或The server time zone value is unrecognized页面 500。原因有两层。第一层是驱动版本与 MySQL 版本不匹配MySQL 8.x 的认证方式默认是caching_sha2_password老驱动mysql-connector-java-5.1.x根本连不上。第二层是 MySQL 8.0 之后默认时区不是 UTC连接串没加serverTimezone就报错。解决确认lib目录或 Maven 依赖里的驱动是mysql-connector-java-8.0.x连接串按 4.2 里的完整写法配齐。如果用的 MySQL 是 8.0还要注意用户认证方式可以在 MySQL 命令行执行-- 如果你的 root 用户是 caching_sha2_password改成 mysql_native_password 兼容老驱动 ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;改完再启动 Tomcat数据库连接就通了。顺带说明本地开发时 MySQL 8.0 的驱动类名是com.mysql.cj.jdbc.Driver不要少写中间的cj这也是个容易让人反复横跳的细节。5.3 页面中文乱码从 db.properties 到 JSP 编码一个都不能少现象商品名称、分类名称在页面上显示成???或者 JSP 页面本身出现乱码。原因有三处编码不一致。一是数据库连接串里缺characterEncodingutf8JDBC 向 MySQL 发送中文时按默认编码转换。二是 JSP 页面头部没设置pageEncoding。三是 Tomcat 接收 POST 请求参数时没有做编码处理默认按 ISO-8859-1 解码。解决逐项检查。db.properties连接串加上characterEncodingutf8每个 JSP 文件第二行写% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8%然后在工程里新建一个过滤器把所有请求和响应统一处理WebFilter(/*) public class EncodingFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 请求参数统一按 UTF-8 解码 request.setCharacterEncoding(UTF-8); // 响应内容统一按 UTF-8 输出 response.setContentType(text/html;charsetUTF-8); chain.doFilter(request, response); } }这个过滤器是工程级的全局配置放进filter包后所有 Servlet 和 JSP 自动生效。测试方法是注册一个新用户用户名写成中文登录后看个人信息页的显示。之前见过一个项目首页商品是从 SQL 脚本里查出来的所以正常但用户注册的中文名全乱加了这个过滤器立刻就好了。5.4 登录状态刷新就丢session 与重定向的配合问题现象登录成功后跳转首页用户状态还在但刷新一次页面或点击二级链接后页面右上角又没有“欢迎 xxx”了回到未登录状态。原因登录成功时把用户对象放错了作用域常见有两种错误写法一是放在 request 里req.setAttribute(loginUser, user)request 生命周期在一次转发或重定向结束时就完了二是登录 Servlet 里创建了一个新的UserService或UserDao对象这种跟状态没关系真正的根源多半是 session 的isNew()行为——每次请求都新建 session说明浏览器禁用了 Cookie 或者服务器 Session 配置不对。解决确认登录成功后放的是 session且跳转路径正确// 正确写法把用户放进 session req.getSession().setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /index);同时检查 JSP 里判断登录状态的方式用${not empty sessionScope.loginUser}而不是${not empty loginUser}。还要确认浏览器没有禁用 CookieTomcat 默认通过JSESSIONIDCookie 维护会话禁用后每次请求都是新 session表现出来就是“永远处于未登录状态”这种玄学问题在课程设计演示时常遇到提前把浏览器 Cookie 设置检查一遍。5.5 分页 totalCount 对不上count(*) 和列表查询条件不一致现象数据库有 20 条商品每页显示 5 条分页组件显示 3 页但点击第三页是空页面或者报错。原因分页的核心是两条 SQL一条是count(*)查总记录数一条是带limit查当前页数据。两条 SQL 的where条件不一致时就会出现这种情况。常见的是列表查询带了status 1过滤但 count 查询没带数据库 20 条里有 8 条下架商品列表显示 12 条分 3 页但 count 返回 20 显示 4 页。解决把总记录数和列表查询封装在同一个 dao 方法或 service 方法里保证 WHERE 子句完全一致public PageBeanProduct findPage(int currentPage, int pageSize) { PageBeanProduct page new PageBean(); // 总记录数SELECT count(*) FROM product WHERE status 1 page.setTotalCount(productDao.countOnSale()); // 当前页数据SELECT * FROM product WHERE status 1 LIMIT ?, ? // limit 的第一个参数是 offset (currentPage - 1) * pageSize int offset (currentPage - 1) * pageSize; page.setList(productDao.findOnSaleByPage(offset, pageSize)); return page; }PageBean里计算总页数用的是Math.ceil(totalCount * 1.0 / pageSize)向上取整避免出现第 0 页。改动时你会发现一个问题后台管理列表和前台列表的过滤条件本来就不同后台要看全部商品前台只看上架的。所以分页查询方法最好传一个status参数进去这样两种列表都调同一个方法条件只差一个参数不会再出现数据对不上的问题。6. 答辩自测与改模块动手前先想清楚这三件事项目能跑起来只是第一步答辩能不能过看的是你有没有系统地测过它。有一个笨但有效的自测顺序先走一遍完整购物流程——注册新用户、登录、首页浏览商品、加购物车、结算下单、去后台发货、重新登录前台确认收货。这串操作覆盖了用户、商品、购物车、订单四张表的最核心链路中间任何一步在 JSP 页面报错或者数据没落库都是答辩前必须处理掉的问题。想加分的话改三个小点。第一是商品搜索在原来“分类查看”基础上加一个按名称模糊查询的入口SQL 用where pname like concat(%, ?, %)这个改动直接展示你懂动态查询而不是只会写死条件。第二是密码加盐数据库里把密码字段从md5(明文)改成md5(明文 随机盐)多一个盐值字段注册时生成盐登录时先按用户名查出盐再比对这个点可以讲出一段“为什么明文 MD5 不安全”的完整论述。第三是给订单列表加一个简单的多条件筛选比如按订单状态查一个下拉框加一个查询按钮就够了。演示顺序也很重要。先进后台管理给某个商品改价或者下架保存后切到前台刷新列表看到变化证明后台功能和前后台数据联动正常。再走前台购物主链路从注册到下单结账然后切到后台把订单状态改成已发货。这比上来就演示登录注册有说服力得多因为老师看到的是一条完整的、有业务逻辑的操作链路而不是单个功能的堆砌。从那以后我每次帮别人看课设项目都强制走完这条链路再做演示至少能提前暴露出一半的隐藏问题。希望帮到你。本文还有配套的精品资源点击获取