JSP+Servlet+MySQL三层架构实战:蛋糕商城项目部署与避坑指南
简介一套基于 JSP、Servlet 与 MySQL 开发的蛋糕甜品店购物网站源码包适合 Java Web 课程设计、毕业设计或自学练手。项目已经过本地编译并运行验证评分达 95 分以上难度适中能帮助理解从用户浏览、购物车、下单支付到管理员后台管理的完整电商闭环。压缩包共 221 个文件、约 7.23MB包含 Java 源文件与编译后的 class、JSP 页面、SQL 数据库脚本、JAR 依赖库、JS/CSS 样式及图片素材按源码、文档、数据库、前端素材分层存放目录结构清晰可导入 Eclipse、MyEclipse、IDEA 配合 Tomcat8/9 快速部署。功能上管理员端涵盖登录、订单、客户、类目管理用户端涵盖首页、分类、热销新品、注册登录、个人中心、购物车、提交订单与模拟支付等附有使用文档和数据库文件便于边读代码边调试。也能直接作为高分项目模板参考。已有 186 人学习下载管理员与用户账号密码均已给出。1. 一份能直接跑起来的 JSPServletMySQL 蛋糕甜品店商城毕业设计季最怕的不是没思路而是源码下下来跑不动。这份基于 jspservletmysql 的蛋糕甜品店购物网站源码是我近几年见过的少有的「拿到手就能编译」的老技术栈项目——JDK1.8 配 Tomcat8/9导入数据库改个密码就能登录评审分 95管理员和用户双角色功能都是完整闭环。它能解决的核心问题就一个让 JavaWeb 新手在最短时间内看到一套真实的 MVC 项目长什么样而不是对着教程里的碎片代码拼不出来。适合正在做课程设计、毕业设计或者想复习 servletDAO 分层的老开发。下面我从项目结构讲起把运行步骤和最容易翻车的地方一次说清。2. 先看懂三层结构DAO、Service、Servlet 之间怎么流转2.1 从类名反推项目骨架拿到源码别急着部署先扫一眼项目里的类整个架构就清楚了大半。这份资源里同时出现了GoodsDao.class、GoodsService.class、AdminGoodsEditServlet.class、OrderService.class这就是典型的 JSPServletDAO 三层结构——Servlet 当控制器收请求Service 层放业务逻辑DAO 层直接怼数据库。最直接的证据是这组类的命名规律Goods、Order、User是实体类GoodsDao、OrderDao、UserDao是数据访问层GoodsService、OrderService是业务层AdminGoodsAddServlet、AdminGoodsEditServlet是后台管理的控制器。我一般会先用 IDE 打开 src 目录按这四组把类归类看一遍每个类的 import 和字段基本就知道整套代码的调用关系了。这里有一个对新手特别有用的习惯把类名当成地图读。看到AdminGoodsAddServlet就往里面看它调了哪个 Service 方法看到OrderService就查它依赖哪个 DAO。老项目不像 Spring 有注解能一眼看出注入关系全凭手动 new 和传参所以类名命名规范不乱项目就好读一半。2.2 一次典型商品购买请求的完整链路用户在前台点「购买」之后发生了什么这是理解整套 jspservlet 项目的钥匙。以这份蛋糕商城为例常见做法是 JSP 页面里的表单或 Ajax 请求先打到某个 Servlet 的 doGet/doPost 上Servlet 解析参数后转到 Service 层做业务校验和价格计算最后 Service 调 DAO 完成对 order 表、order_item 表的写入。// BuyServlet.java —— 前端购买按钮提交到这里常见源码写法 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 从 session 拿当前登录用户 User user (User) request.getSession().getAttribute(loginUser); if (user null) { response.sendRedirect(login.jsp); return; } // 2. 接收前端传来的商品 id 和购买数量 String goodsId request.getParameter(goodsId); String buyNum request.getParameter(buyNum); // 3. 交给 Service 层去处理查商品、算总价、生成订单 OrderService orderService new OrderService(); int orderId orderService.createOrder(user.getId(), Integer.parseInt(goodsId), Integer.parseInt(buyNum)); // 4. 带订单号跳转到确认页 response.sendRedirect(orderConfirm.jsp?orderId orderId); }这段代码把控制器的职责说得很明白只做参数接收、用户状态检查和页面跳转真正的业务逻辑都藏在OrderService.createOrder()里。注意第 2 步的request.getParameter取出来全是字符串所以必须手动Integer.parseInt转 int——这是 JSPServlet 老项目最常见的类型转换现场。除非前端传的是合法数字不然这里就是第一个容易报NumberFormatException的地方。对应的 Service 层我一般会这样拆// OrderService.java —— 生成订单的业务逻辑 public int createOrder(int userId, int goodsId, int buyNum) { // 1. 查商品信息拿到单价 GoodsDao goodsDao new GoodsDao(); Goods goods goodsDao.findById(goodsId); if (goods null) { throw new RuntimeException(商品不存在); } // 2. 计算订单总金额 double totalPrice goods.getPrice() * buyNum; // 3. 插入 order 表返回自增主键 OrderDao orderDao new OrderDao(); Order order new Order(); order.setUserId(userId); order.setTotalPrice(totalPrice); order.setStatus(0); // 0待支付 int orderId orderDao.insert(order); // 4. 再往 order_item 表写一条明细略 return orderId; }注意这段代码里status被编码成 0 或 1 这样的 int而不是字符串「未支付」「已支付」。老项目里用数字状态位是常态方便数据库排序和统计但阅读体验因人而异。你在跑通后想改业务逻辑多半就是改这里的判断分支比如加一个「已发货」状态就把status的取值范围从 0/1 扩成 0/1/2同时去订单管理页面改对应的文字映射。2.3 DAO 层的 JDBC 写法与参数细节DAO 层是最没技术含量但最容易写错的地方。看这份项目的GoodsDao.class核心就是封装的 JDBC 操作getConnection()从工具类拿连接PreparedStatement预编译 SQLexecuteQuery()查出来后再手动映射到Goods对象。// GoodsDao.java —— 按 id 查询商品 public Goods findById(int id) { Goods goods null; Connection conn null; PreparedStatement ps null; ResultSet rs null; try { // 1. 从 JDBCUtil 拿数据库连接读取 db.properties 配置 conn JDBCUtil.getConnection(); // 2. 预编译 SQL? 占位符防止 SQL 注入 String sql SELECT * FROM goods WHERE id ?; ps conn.prepareStatement(sql); ps.setInt(1, id); rs ps.executeQuery(); // 3. 手动把 ResultSet 转成实体对象 if (rs.next()) { goods new Goods(); goods.setId(rs.getInt(id)); goods.setName(rs.getString(name)); goods.setPrice(rs.getDouble(price)); goods.setStock(rs.getInt(stock)); } } catch (Exception e) { e.printStackTrace(); } finally { // 4. 依次关闭 rs、ps、conn不能漏 JDBCUtil.close(rs, ps, conn); } return goods; }这里有三个常被忽略的参数细节。第一setInt(1, id)的下标从 1 开始不是 0新手在这里翻车的概率非常高。第二finally里的资源关闭顺序必须是 ResultSet → PreparedStatement → Connection倒着关漏掉任何一步在 Windows 上长时间跑都会因连接不释放报Too many connections。第三SELECT *在 JSP 老项目里到处都是能跑但别学等这张表字段一多光映射代码就得改半天。3. 跑起来再说JDK、Tomcat、MySQL 三步部署一次过3.1 环境版本的匹配原则先说环境这是老项目第一个大坑。这份蛋糕甜品店商城的运行环境标注是jdk1.8/jdk1.9 Tomcat8.x/9.x MySQL 5.x/8.xIDE 用 Eclipse、Myeclipse、IDEA 都行。我的建议是直接上 JDK1.8 Tomcat8.5 MySQL 5.7这是最稳的组合网上资料最多踩了坑也容易搜到答案。JDK 版本决定了编译字节码的版本Tomcat 版本决定了它能加载的字节码上限。JDK1.8 编译出来的 class 文件Tomcat8/9 都能认但你用 JDK9 编译后扔给 Tomcat7 就报UnsupportedClassVersionError这属于版本向后兼容问题别在这个上面花时间硬调换版本最省事。3.2 数据库导入的完整操作步骤数据库是这套系统的心脏。先启动 MySQL用命令行或 Navicat 创建数据库并导入项目里自带的.sql文件。项目文件夹里通常会有一个db或sql目录里面放的就是建库建表语句加初始数据。# 1. 登录 MySQLWindows 下如果配了环境变量可以直接执行 mysql -uroot -p # 2. 创建数据库注意字符集要和项目里表结构的定义一致 CREATE DATABASE IF NOT EXISTS cake_shop DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; # 3. 导入项目自带的 sql 文件 USE cake_shop; SOURCE D:/cake_shop.sql;这里我必须强调一下字符集。第 2 步建库我强制写了utf8是因为国内 JSP 老项目几乎清一色 UTF-8 编码如果你建库用了默认的 latin1导入中文数据后页面会出现满屏问号和乱码。SOURCE命令后面跟的是绝对路径Windows 下用正斜杠/或转义\\都可以路径别带中文否则 MySQL 命令行可能识别不了。导入完成后顺手验证一下表是否齐全执行SHOW TABLES;至少应该看到与前台商品、用户、订单相关的表。看到user、goods、order、order_item这些名字说明库文件导入成功。有些版本的 sql 文件里还会把管理员账号admin/admin直接写进admin表后面登录就是靠它。3.3 修改数据库连接配置数据库导入只是第一步项目代码里的连接配置不改成你本机的账号密码部署一百次也连不上。这份源码里的数据库配置文件通常是src/db.properties或src/jdbc.properties内容大致长这样# db.properties —— 数据库连接配置部署前必改 jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/cake_shop?characterEncodingutf8 jdbc.usernameroot jdbc.password123456把jdbc.password改成你自己 MySQL 的 root 密码jdbc.url里的cake_shop改成你实际创建的库名。注意 url 结尾的characterEncodingutf8参数千万别删删了的话 Servlet 往数据库写中文用户名和订单备注时十有八九落库就变乱码。如果你装的是 MySQL 8.x驱动类名就不是com.mysql.jdbc.Driver了要换成com.mysql.cj.jdbc.Driver同时 url 里一般还需要追加serverTimezoneAsia/Shanghai才能消除时区警告。项目自带的mysql-connector-java.jar版本是跟着原作者的 MySQL 走的连新版数据库报时区或认证错误第一反应别改代码先看 jar 版本够不够新。3.4 部署到 Tomcat 并启动验证数据库搞定后就差最后一步——把项目塞进 Tomcat。用 IDEA 或 Eclipse 的同学直接配置 Tomcat 运行环境直接用 Tomcat 的同学把项目打成 war 包扔进webapps目录。更稳妥的另一种做法是把整个项目文件夹复制到webapps下但老项目里依赖的 jar 包路径、web.xml 配置很敏感不熟悉的不建议手动复制直接在 IDE 里一把梭最省心。# D:\apache-tomcat-8.5.xx\bin 目录下启动Windows startup.bat # 启动后看命令行日志找这一行 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [xxx] milliseconds看到Server startup in就说明 Tomcat 起来了但这不代表项目部署成功。还要去logs/catalina.out或 IDE 控制台翻有没有Exception和Caused by。我见过太多人看到 Tomcat 起来了就以为万事大吉结果浏览器一访问直接 404——Tomcat 启动成功和 webapp 部署成功是两回事webapps下的项目如果 web.xml 写错Tomcat 会照样启动但项目静默失败。浏览器访问http://localhost:8080/cake_shop/端口和项目名按你实际配置来能跳到首页说明部署链路通了。这时候先用管理员账号admin/admin登录后台再去前台用user1/123456走一遍完整的购买流程确认没有白屏和 500 报错这就算彻底跑起来了。4. 表结构里看业务数据怎么串起管理员和用户双端4.1 核心表清单与职责划分跑通第一遍之后就该静下心来看表结构了。这套蛋糕甜品店购物网站的数据模型不算复杂但胜在典型几乎所有电商课程设计项目都能套这个模板。我把表按业务模块拆开读思路会清晰很多核心表大致如下表名归属端核心字段职责admin管理员id, username, password后台登录凭证user用户端id, username, password, phone前台注册登录与个人信息category双端id, name商品类目蛋糕、甜点、饮品等goods双端id, name, price, stock, category_id, is_hot, is_new商品信息含热销/新品标记order双端id, user_id, total_price, status, create_time订单主表状态字段控制流程order_item双端id, order_id, goods_id, goods_name, price, num订单明细一份订单对应多行cart用户端id, user_id, goods_id, num购物车临时数据这个表结构里的关键点在于order和order_item的主从关系。order表存的是订单整体信息包括总金额、下单用户、当前状态但具体买了哪几块蛋糕、每种蛋糕买了几个全在order_item里。这正好和前面 Service 层代码里的createOrder方法对应上先插 order 主表拿到自增 id再循环插 order_item 明细。4.2 订单状态字段的设计思路订单是这套系统里最值得细看的表因为它的status字段就是整个业务流程的状态机。管理员后台的「订单管理」、用户前台的「查看订单」本质上都是按这个字段做条件查询和文字映射。常见的状态定义是0待支付、1已支付模拟支付成功后由 0 改成 1这也是这份资源里「模拟支付成功」功能的落点。-- order 表建表语句常见源码写法字段按需调整 CREATE TABLE order ( id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL COMMENT 下单用户ID, total_price decimal(10,2) NOT NULL COMMENT 订单总金额, status int(11) NOT NULL DEFAULT 0 COMMENT 订单状态 0待支付 1已支付, create_time datetime DEFAULT NULL COMMENT 下单时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8;这段建表语句里有三个细节值得单独说。第一total_price用的是decimal(10,2)而不是double这是老项目和新手代码的分水岭——金额用浮点类型算到最后必然出现 0.10.2≠0.3 的精度问题decimal 才是给钱用的类型。第二表名order是 MySQL 的保留字正规写法要加反引号包裹或者干脆改名orders这也是一会儿避坑章里要细说的高频报错来源。第三user_id上没写外键约束老项目绝大多数都靠代码层保证一致性不建物理外键图的是插入删除省事。读表结构的时候我习惯顺手画一遍数据流用户注册后把信息写进user表管理员在后台往goods表加蛋糕同时指定category_id决定它挂在哪个类目下、is_hot/is_new决定它是否进首页热销区或新品区用户把商品加进cart表结算时把 cart 里选中的商品生成order和order_item两条数据并清空购物车支付按钮把 order 的status置 1。这套流程就是整个项目的全部骨架。5. 避坑指南JSPServlet 项目最常见的 5 个翻车点5.1 现象Tomcat 启动时报 UnsupportedClassVersionError这个报错的完整信息是java.lang.UnsupportedClassVersionError: xxxServlet : Unsupported major.minor version 52.0第一次见的人会以为是代码写错了其实跟代码一点关系都没有。原因项目里的 class 文件是用比你当前 JDK 更高的版本编译的Tomcat 运行时 JDK 版本太老读不懂新的字节码。比如源码作者用 JDK9 编译你的 Tomcat 跑在 JDK8 上就会报这个错。解决统一 JDK 版本。确认java -version的版本和 IDE 里项目编译级别一致建议直接全部换成 JDK1.8再在 IDE 里执行一次Project → Clean让所有 class 重新生成这一步能解决九成以上的版本错误。5.2 现象页面能开但登录后报 500控制台打印 ClassNotFoundException日志里出现java.lang.ClassNotFoundException: com.mysql.jdbc.Driver但项目里明明有mysql-connector-java.jar。原因jar 包没被部署到 Tomcat 的WEB-INF/lib目录。IDE 里编译不报错是因为类路径能看到 jar但 Tomcat 运行时只认WEB-INF/lib下的包。新手最常犯的就是把 jar 放到了src根目录或自己新建的lib文件夹没有打进最终部署产物。解决在 IDEA 里右键 jar 包选择Add as Library同时确认File → Project Structure → Artifacts里Output Layout的WEB-INF/lib下能看到它。Eclipse 则右键项目 →Properties → Deployment Assembly添加 jar。改完重新部署再启动。5.3 现象注册新用户成功但数据库里中文名字全是问号前台页面输入中文用户名提交后跳转成功打开 MySQL 一看user表里存的是???。原因三个环节的字符集没串联起来。JSP 页面本身是 UTF-8 但数据库连接 url 没带characterEncodingutf8或 MySQL 表结构默认字符集是 latin1导致插入时中文字节被转义。解决按顺序排查三处。第一数据库建库和建表都要用DEFAULT CHARSETutf8第二JDBC url 末尾追加characterEncodingutf8第三在 Servlet 入口加request.setCharacterEncoding(UTF-8)防止 POST 提交的中文参数在读取时就已乱码。三处都对齐后把表里已有的脏数据删掉重新注册一遍验证。5.4 现象提交订单时提示 SQL 语法错误syntax error near order后台打印出来的 SQL 长这样INSERT INTO order (user_id, total_price, status) VALUES (1, 99.00, 0)怎么看都没错但 MySQL 就是报语法错误。原因order是 MySQL 的保留关键字用于ORDER BY排序直接当表名用必须加反引号转义否则 MySQL 解析器会直接报错。解决改表结构一劳永逸——把order表改名成orders同时去OrderDao.class或OrderDao.java里把对应的 SQL 全部替换。如果嫌改代码麻烦临时救急就把 SQL 里的订单表名统一写成反引号order但项目里不止一处 SQL逐个替换很容易漏我的习惯还是改名orders从源头告别这个坑。5.5 现象后台管理页的商品图片裂开或点击商品分类没有反应图片全裂、点分类菜单 URL 协商失败看起来像前端问题其实根子在 Web 应用的访问路径上。原因JSP 页面里用了绝对路径但不带项目名比如src/images/cake1.jpg浏览器解析时会自动加上当前 IP 和端口却丢了context path也就是/cake_shop这一段导致请求打到http://localhost:8080/images/...而不是http://localhost:8080/cake_shop/images/...。解决统一在 JSP 页面顶部用 JSTL 的c:set varctx value${pageContext.request.contextPath} /声明根路径然后把所有资源路径写成img src${ctx}/images/cake1.jpg链接和表单 action 同理。这是个纯体力活但改完就再也不会出现部署到带项目名的路径下页面资源全丢的情况。6. 从模拟支付加深理解订单状态流转与二次开发入口整个项目的精髓我觉得在「模拟支付成功」这个功能上——它不只是一句「支付成功」的提示背后是订单状态从 0 到 1 的一次真实更新。找到处理支付的 Servlet核心逻辑就是按订单 id 更新status字段用 SQL 表达就是UPDATE orders SET status 1 WHERE id ?。想验证自己是不是真读懂了这套系统最好的方法就是给订单状态机加一个新状态。比如增加「已发货」先在数据库里把status的注释改成0待支付 1已支付 2已发货再到管理员订单管理页面找到状态文字映射的代码加一行 2 对应「已发货」的显示最后在商家管理操作里加一个「发货」按钮调一个updateStatus(orderId, 2)的 DAO 方法。做完这三个改动你对 ServletDAO 的理解就比单纯跑通项目深一层了。如果还想继续往深了改我建议把注意力放在cart表到order_item的转换上。很多新手会把购物车数据直接当成订单明细提交但标准做法是结算时读取 cart 表 → 按商品聚合生成 order_item → 清空 cart这个流程里涉及事务处理。项目原本大概率没加事务你可以尝试用conn.setAutoCommit(false)把主表插入和明细插入包在同一个事务里中途失败就回滚这也是从「能跑」到「敢上生产」的关键一步。我从接手第一个老项目开始就养成了一个强迫自己的习惯任何 JSPServlet 项目跑通后第一件事不是截图交差而是打开db.properties核对字符集和驱动版本再进数据库把所有表结构过一遍确认状态字段的取值范围和代码里写死的数字对得上。这套流程救了我太多次每次踩坑都出在这两个地方。希望帮到你祝你把这份蛋糕商城的代码吃透答辩或面试被问到任何一层都能接得住。本文还有配套的精品资源点击获取