ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JavaWeb蛋糕店网站系统课设:从环境配置到订单事务完整解析

JavaWeb蛋糕店网站系统课设:从环境配置到订单事务完整解析 简介一套基于Java Web的蛋糕店网站系统源码面向计算机相关专业的学生与Java Web初学者可满足课程设计、毕业设计及项目初期演示需求。系统前台覆盖推荐商品展示条幅推荐、热销推荐、新品推荐、类型商品展示、商品详情、购物车管理、用户注册登录、个人资料修改、订单查询与关键字搜索后台提供商品及订单管理功能。代码已完整测试并稳定运行附有数据库SQL脚本与详细注释便于理解核心业务逻辑和后续二次开发。资源压缩包共五百八十六个文件体积约十七点四兆包含JPG、PNG、GIF等界面图片素材JSP动态页面JAVA源文件与CLASS编译文件并配备CSS样式、JS交互脚本、JAR依赖库及SQL数据库脚本覆盖常见Web开发所需技术栈目录结构清楚方便按模块查找目前已有六百九十四人学习下载适合需要完整可运行项目作为参考的读者。附带界面演示和文档说明可用于答辩展示、功能验证或教学自学借助详细注释能较快上手并扩展功能。1. 蛋糕店网站系统这套JavaWeb课设能跑通才是它真正的价值拿到一份“基于javaweb的蛋糕店网站系统源码界面演示文档说明数据库sql详细注释(课程设计)”压缩包你第一步最该做的不是打开代码挨个看而是先把环境对齐JDK 1.8、Tomcat 8.5/9.0、MySQL 5.7及以上这三样只要有一个版本对不上后面再完整的推荐位、购物车、订单设计都白搭。这个项目是典型的JSPServletJDBC三层结构前台走完“商品浏览→类型筛选→详情→加购→登录→下单→订单查询”的用户闭环后台覆盖管理员登录、商品维护、订单处理数据库里六张表把用户、商品、订单串成一条完整的数据流。适合正在做JavaWeb课设、但卡在环境配置和跑通这一步的同学也适合想拿一个能跑原型快速改造成毕设初期演示的人。2. 项目结构与数据库设计先拆清分层再动手配环境拿到压缩包别急着往Tomcat里扔先把它的骨架摸清楚。这套系统包名一般以com.cake开头代码量不大但每个层的分工非常清楚——先看清目录后面报错时你才知道该去哪一层找原因。2.1 目录结构拆解src、WebContent、sql和doc各自管哪块我习惯先按目录结构建一张清单避免在压缩包里乱翻目录/文件作用src/com/cake/entity实体类User、Goods、Type、Order等src/com/cake/daoDAO层JDBC访问MySQL每张表对应一个Daosrc/com/cake/servletServlet层接收请求、调DAO、转发或重定向到JSPsrc/com/cake/filter过滤器编码处理、登录校验、后台权限校验WebContent/JSP页面、CSS/JS/图片静态资源WebContent/WEB-INF/lib第三方jar重点是mysql-connector-javasql/cake_shop.sql建库建表脚本加初始化测试数据文档说明部署步骤、账号密码、环境版本说明这个分层是标准的“表现层→控制层→数据访问层”单向依赖JSP只负责展示和收集参数Servlet只做参数解析和流程分发DAO只拼SQL和封装结果集实体类只做内存里的数据载体。这么设计的最大好处是答辩时你能一句话说清楚“请求从哪来到哪去”不用讲任何设计模式评审听着也顺。再说选型这个项目没上SpringBoot而是JSPServletJDBC课设阶段这是最稳的组合。SpringBoot虽然启动快但自动配置和依赖管理把太多细节藏起来了你没法向评审解释“为什么pom.xml加一行就能跑”JSPServlet每一步都是显式的哪怕你只写过100行Java也能把整个请求生命周期讲透。我拆项目时会优先挑这种直白技术栈的课设因为它的兼容面最宽换一台电脑也能跑起来。2.2 核心表设计user、goods、order怎么串起数据流数据库是这套系统的真正核心。我梳理了一下SQL脚本核心表是用户表、类型表、商品表、订单表、订单明细表这五张有的版本会再加一张管理员表。下面挑最关键的四张拆开讲。用户表同时承担登录和收货信息两块职责CREATE TABLE tb_user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 用户主键, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT 密码, real_name VARCHAR(50) COMMENT 收货人姓名, phone VARCHAR(20) COMMENT 联系电话, address VARCHAR(200) COMMENT 收货地址, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;这里有两个课设阶段很典型的取舍。第一主键用自增INT而不是UUID理由是自增ID在订单表里做关联查询时走索引比字符串UUID快而且实现成本为零。第二密码明文存储这是这个项目最大的妥协点因为源码里没有引入加密工具类你要知道这在真实项目里是不行的——答辩时如果被问到直接说“后续可以加一步MD5或BCrypt加密”就能圆过去。商品表是前台展示的数据源头CREATE TABLE tb_goods ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 商品主键, name VARCHAR(100) NOT NULL COMMENT 商品名称, price DECIMAL(10,2) NOT NULL COMMENT 价格单位元, description TEXT COMMENT 商品描述, image VARCHAR(255) COMMENT 图片路径存相对路径, type_id INT COMMENT 所属类型关联tb_type, recommend INT DEFAULT 0 COMMENT 推荐位0普通1条幅2热销3新品, stock INT DEFAULT 100 COMMENT 库存数量, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表;这张表里最值得讲的是recommend字段。它用一个INT列承担了“条幅推荐、热销推荐、新品推荐”三个推荐位的全部逻辑——查询时只要加一个where recommend2就能取热销商品完全不用建三张关联表。这是相当务实的冗余设计课设里用这个字段比做多表关联更容易得分。订单表和订单明细表是快照设计的典型场景CREATE TABLE tb_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 订单号业务编号, user_id INT NOT NULL COMMENT 下单用户, total_price DECIMAL(10,2) NOT NULL COMMENT 订单总价, status INT DEFAULT 0 COMMENT 状态0待付款1已付款2已发货3已完成, receiver_name VARCHAR(50) COMMENT 收货人快照冗余, receiver_phone VARCHAR(20) COMMENT 收货电话快照冗余, receiver_address VARCHAR(200) COMMENT 收货地址快照冗余, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; CREATE TABLE tb_order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL COMMENT 所属订单, goods_id INT NOT NULL COMMENT 商品ID逻辑关联, goods_name VARCHAR(100) COMMENT 下单时的商品名, goods_price DECIMAL(10,2) COMMENT 下单时的单价, count INT NOT NULL COMMENT 数量, subtotal DECIMAL(10,2) COMMENT 小计金额 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;注意订单表里把收货人、电话、地址直接冗余进去而不是下单时去关联用户表实时取。原因很简单用户下单后可能改了地址订单的历史数据不能跟着变否则发货就发错地方。订单明细表里冗余goods_name和goods_price同理——你从商品详情页把某蛋糕改成99元已生成的订单记录不能变。这是“数据快照”思路答辩时提一句“订单是历史事实用户和商品信息是可变数据两者必须解耦”你的设计观感就会明显高于只会CRUD的同学。2.3 SQL导入与db.properties配置十分钟把数据层跑起来SQL脚本的导入顺序是个容易翻车的点正确顺序是建库→指定字符集→导入表结构和测试数据mysql -uroot -p CREATE DATABASE cake_shop DEFAULT CHARACTER SET utf8mb4; USE cake_shop; SOURCE D:/course_design/cake_shop.sql;三步做完后用SHOW TABLES确认表都在。如果提示mysql不是内部或外部命令说明MySQL的bin目录没加进PATH直接cd到MySQL安装目录的bin下面执行或者用Navicat图形化导入都行。我这里用命令行示例逻辑上直白也方便你没装图形工具时应急。数据层跑通的关键配置文件是db.propertiesjdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/cake_shop?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password你的密码参数含义分别是useUnicodetrue告诉驱动按Unicode编码传输characterEncodingutf-8指定具体字符集useSSLfalse关掉SSL握手以减少本地开发连接耗时serverTimezoneAsia/Shanghai固定时区避免MySQL 8.0和JVM默认时区不一致导致的时间报错。这里有两个高频踩点MySQL 8.0以上必须把driver写成com.mysql.cj.jdbc.Driver写旧版驱动会报ClassNotFoundException连接串里少了characterEncodingutf-8页面查出来的中文会全部变成问号。这些参数我建议整段复制不要手删。提示压缩包里的说明文档如果写了Tomcat版本优先按文档来没写的话直接按Tomcat 8.5/9.0处理这是兼容面最宽的区间。3. 前台功能实现商品展示、购物车与订单的生命周期前台是这套系统的主要工作量要打通“浏览→加购→登录→下单→查单”的完整链路。下面按请求流转顺序拆开讲。3.1 首页与商品列表IndexServlet怎么分发推荐位访问项目根路径时welcome-file指到index.jspJSP再发起请求给IndexServlet由Servlet查好推荐位数据再转发回来。这套“JSP激活Servlet”的写法在课设里很常见先看IndexServlet的完整逻辑WebServlet(/index) public class IndexServlet extends HttpServlet { private GoodsDao goodsDao new GoodsDao(); Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 按recommend字段取值查询三组推荐商品 ListGoods bannerList goodsDao.findByRecommend(1, 3); // 条幅推荐最多3个 ListGoods hotList goodsDao.findByRecommend(2, 8); // 热销推荐最多8个 ListGoods newList goodsDao.findByRecommend(3, 8); // 新品推荐最多8个 request.setAttribute(bannerList, bannerList); request.setAttribute(hotList, hotList); request.setAttribute(newList, newList); request.getRequestDispatcher(/index.jsp).forward(request, response); } Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }findByRecommend在DAO里拼的SQL是public ListGoods findByRecommend(int recommend, int limit) { String sql SELECT * FROM tb_goods WHERE recommend ? ORDER BY create_time DESC LIMIT ?; // 用PreparedStatement占位符参数化查询防止SQL注入 }这里有两个关键参数。第一个是recommend取值1、2、3分别对应条幅、热销、新品这个值必须和SQL脚本里插入的商品数据一致如果首页某块区域一直空白多半就是商品表里没有对应recommend值的数据第二个是limit控制每个推荐位最多取多少条条幅位放3张大图热销和新品位放8个商品卡片这是由前端页面布局决定的。整页只查了三次数据库没有循环查库性能上是够用的。3.2 商品详情、搜索与加入购物车一次完整的数据交互点商品卡片进详情页Servlet根据URL上的id参数查单条商品WebServlet(/goodsDetail) public class GoodsDetailServlet extends HttpServlet { private GoodsDao goodsDao new GoodsDao(); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Integer id Integer.parseInt(request.getParameter(id)); Goods goods goodsDao.findById(id); request.setAttribute(goods, goods); request.getRequestDispatcher(/goods_detail.jsp).forward(request, response); } }这里有个隐藏坑request.getParameter(id)返回的是字符串如果URL没传id或传了非数字Integer.parseInt会直接抛NumberFormatException页面变500。稳妥写法是先判空再解析或者用try-catch包一层。课设里大多数项目为了省事不判断但你加上这个判断答辩又多一个可讲的安全点。加入购物车是整个前台的转折点从无状态浏览进入有状态操作。它用Session存一个List CartItem不是数据库表而是内存对象CartItem item new CartItem(); item.setGoods(goods); // 商品对象直接放进去 item.setCount(count); // 用户选择的购买数量 HttpSession session request.getSession(); ListCartItem cart (ListCartItem) session.getAttribute(cart); if (cart null) { cart new ArrayListCartItem(); session.setAttribute(cart, cart); } boolean exists false; for (CartItem ci : cart) { if (ci.getGoods().getId().equals(item.getGoods().getId())) { ci.setCount(ci.getCount() item.getCount()); // 已有则累加数量 exists true; break; } } if (!exists) { cart.add(item); }这段逻辑有三个关键点第一购物车放Session而不是建一张购物车表因为它是临时数据用户没下单就没有持久化价值放Session天然跟着会话走代码量最少第二重复加购时做数量累加而不是插两条记录避免购物车出现两行同样的蛋糕这里用equals而不用因为商品ID是Integer对象第三这里只做了内存操作没有同步库存等提交订单时才做真正校验这是后话。购物车页面的数量修改一般用AJAX局部刷新// 修改购物车数量局部刷新小计 function changeCount(goodsId, count) { if (count 1) { alert(数量不能小于1); return; } $.ajax({ url: cart?actionupdate, data: {goodsId: goodsId, count: count}, dataType: json, success: function (result) { if (result.success) { $(#total_ goodsId).text(result.total); } } }); }data里的两个参数goodsId是商品IDcount是用户改后的数量后台Servlet收到后再遍历Session里的cart列表找到对应商品把小计重算一遍。用AJAX的好处是不用整页刷新体验接近真实商城这段如果跑通了演示时的观感会比普通课设高一个档次。3.3 登录注册与个人信息修改Session管理和表单校验的边界用户下单前必须登录LoginServlet的核心逻辑是WebServlet(/login) public class LoginServlet extends HttpServlet { private UserDao userDao new UserDao(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); User user userDao.findByUsernameAndPassword(username, password); if (user ! null) { request.getSession().setAttribute(user, user); response.sendRedirect(index); // 重定向防止刷新重复提交 } else { request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }注意成功和失败用的响应方式不同成功用sendRedirect跳首页是防止POST后刷新页面重复提交失败用forward回登录页是为了把msg错误提示传递过去。如果你把这两个写反了——成功用forward刷新页面就会重复提交表单失败用sendRedirectmsg就丢了。这就是课设里最常见“登录成功了但提示信息出不来”的根源。注册的查重逻辑在DAO里String sql SELECT COUNT(*) FROM tb_user WHERE username ?;注册前先查一次用户名返回大于0就提示“该用户名已被注册”等于0才继续插入。查重用COUNT(*)PreparedStatement参数化既防注入又能拿到精确的行数。个人信息修改的SQL是String sql UPDATE tb_user SET real_name ?, phone ?, address ? WHERE id ?;这里有个很容易忽略的细节用户改完资料后Session里的User对象还是旧值必须把修改后的user重新setAttribute回Session否则页面右上角一直显示修改前的名字下单时收货地址也是旧的。这行代码只要写漏了就是那种“我明明改了地址可下单时还是老地址”的玄学bug。3.4 订单生成与订单查询多表写入和状态流转下订单不是插一条记录而是事务性多表操作往tb_order插主单循环往tb_order_item插明细最后清空Session里的购物车。一个合格课设会用事务把操作包起来public void submitOrder(Order order, ListCartItem cart) { try { conn.setAutoCommit(false); // 开启事务 int orderId orderDao.insert(order); // 先生成订单主表 for (CartItem item : cart) { orderItemDao.insert(orderId, item); // 再插明细 } conn.commit(); // 全部成功才提交 } catch (Exception e) { conn.rollback(); // 任一步失败都回滚 throw new RuntimeException(订单提交失败, e); } finally { conn.setAutoCommit(true); } }这段代码的意义在于如果订单表插入成功、明细插入失败没有事务就会留下一个没有明细的“幽灵订单”后台订单统计会出问题这属于想补后悔药都难的数据脏账。setAutoCommit(false)之后必须记得在finally里恢复成true否则连接池回收连接后下一次操作会沿用错误的事务状态——这个坑我见过不止一次。订单查询则是把订单表按用户维度取出来SELECT * FROM tb_order WHERE user_id ? ORDER BY create_time DESC;前端用JSTL的c:forEach循环渲染成订单卡片根据status值显示不同按钮和状态文字。订单状态机很简单0待付款→1已付款→2已发货→3已完成后台做的其实就是改这个status值前台只读展示。4. 后台商品与订单管理权限拦截和CRUD的落地姿势后台功能在这套系统里是另一个独立包URL一般统一带admin前缀。它的核心不是功能多而是权限边界要清楚普通用户永远不能访问后台。4.1 管理员登录与Filter权限拦截URL规则决定安全边界后台防护分两层第一层是登录页单独校验第二层是Filter拦截所有admin开头的请求。Filter的实现是WebFilter(/admin/*) public class AdminFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(false); Object admin session null ? null : session.getAttribute(admin); if (admin null) { resp.sendRedirect(req.getContextPath() /admin/login.jsp); return; } chain.doFilter(request, response); } }注意一个细节req.getSession(false)和req.getSession()不同。加false表示当前没有Session就返回null而不是新建一个——避免未登录请求被Filter强制创建无用的Session。然后判断Session里有没有admin对象没有就重定向回登录页。这个Filter是后台安全的省心方案不加它就是裸奔直接访问/admin/goods_list.jspTomcat照常渲染页面里的后台数据全部泄露。这个Filter的URL模式WebFilter(/admin/*)是另一个要点它匹配所有以/admin/开头的URL包括JSP页面和Servlet路径。如果你只在Servlet上做校验绕过Servlet直接访问JSP就能看到页面用Filter统一拦截JSP也被挡在门外安全边界才算封住。4.2 后台商品管理增删改查和推荐位设置商品管理是后台的主要工作量核心是新增、编辑、删除、推荐位设置四个操作。以新增为例表单提交到AdminGoodsServletServlet组装Goods对象后调DAO插入String name request.getParameter(name); String price request.getParameter(price); String typeId request.getParameter(typeId); int recommend Integer.parseInt(request.getParameter(recommend)); Goods goods new Goods(); goods.setName(name); goods.setPrice(new BigDecimal(price)); goods.setDescription(request.getParameter(description)); goods.setTypeId(Integer.parseInt(typeId)); goods.setRecommend(recommend); goodsDao.insert(goods); response.sendRedirect(admin/goods_list);表单里的recommend我特意建议用下拉框而不是手输下拉框选项就是“普通、条幅、热销、新品”四个提交后映射成0到3。这么做能避免手输脏数据——你把recommend填成5前台三个查询都查不到它商品就变成隐形商品。删除操作这里有一个建议如果商品已经产生了订单就不该物理删除否则订单明细里关联的goods_id就成了悬空引用。我的习惯是goods表加一个is_deleted字段做逻辑删除列表查询where is_deleted0删除只是把这个字段置1。这个改动只要一分钟但能从根上避免“订单里的商品在后台没了”这种尴尬。4.3 订单状态处理和用户管理后台对前台数据的反向干预订单管理页面的本质是一张列表加一个状态修改按钮。列表查询按状态条件过滤方便管理员按“待付款、已发货”分段处理SELECT * FROM tb_order WHERE status ? ORDER BY create_time DESC;status参数从列表页的筛选下拉框传过来后台Servlet拿到后做一次范围校验只允许传0到3防止人为构造非法状态值。状态修改本身是一个update但注意要给tb_order加一个update_time字段每次改状态时顺手更新这样出现“显示已发货但不知道什么时候发的”这种问题查时间戳就有答案。用户管理通常是后台最简单的部分列表展示、按用户名搜索、禁用账号。需要注意一个权限边界后台不应该提供修改用户密码的功能管理员没有权利替用户改密码这个边界在课设里经常被混淆。如果评审问起来你就说“密码属于用户私有数据后台只提供禁用开关”这不是代码问题是权限设计意识问题。5. 避坑与排查从导入SQL到部署运行的五个高频问题课设翻车的高峰期不在写代码而在“从压缩包到浏览器正常显示”这一段环境链路。下面五条是我拆这类JavaWeb项目时总结的血泪经验每一条都值得动手前先看一遍。5.1 部署期的三个环境坑版本、字符集、类路径现象一Tomcat 10部署后页面404或500日志里全是ClassNotFoundException: javax.servlet.Filter。原因Tomcat 10把javax.servlet包换成了jakarta.servlet包而这套课设基于Tomcat 8/9编写源码和依赖里全是javax。用Tomcat 10直接跑Servlet和Filter全部加载失败项目等于废了。解决把服务器换成Tomcat 8.5或9.0这是最省事的路。非要用Tomcat 10就得把源码里所有javax.servlet开头的import改成jakarta.servlet工作量不大但完全没必要。选Tomcat 9用默认配置别在这上面耗时间。现象二SQL导入时报错或者导入后页面中文全是问号。原因SQL脚本文件本身不是UTF-8编码。Windows下用记事本打开的SQL文件经常被存成GBK里面的中文表注释和商品名就全乱了。解决导入前用Notepad或VS Code把SQL文件转成UTF-8。最稳的做法是MySQL里先执行CREATE DATABASE cake_shop DEFAULT CHARACTER SET utf8mb4再USE cake_shop再跑source建库、选库、导表三步顺序不能反。现象三报错ClassNotFoundException: com.mysql.jdbc.Driver。原因mysql-connector-java的jar包没复制到WebContent/WEB-INF/lib目录。很多同学只把jar加进了Build Path但项目部署到Tomcat后运行的是WebContent目录结构依赖没打进去类加载器找不到驱动类。解决检查WEB-INF/lib下有没有mysql-connector-java-版本号.jar没有就复制进去然后右键项目→Refresh→重新部署。5.2 代码层的两个隐藏坑Session失效和整数解析现象四登录状态时不时掉线刷新几次就变成未登录后台操作频繁被踢回登录页。原因Session默认超时30分钟但如果你在web.xml里手动配了session-timeout而且值特别小或者浏览器禁用了Cookie就会出现“明明没退出但系统认为你退了”。Session的会话ID是靠Cookie传递的Cookie被拦每次请求都是新Session。解决打开web.xml检查session-config配置session-config session-timeout60/session-timeout /session-configsession-timeout的单位是分钟60对课设演示足够了。同时检查浏览器是否禁用了Cookie。现象五页面报NumberFormatException或者商品ID、数量参数传丢。原因Servlet里直接调用Integer.parseInt(request.getParameter(id))没判断参数是否为null或是否纯数字。前端某个链接少了参数或参数是非数字字符解析就直接炸。解决写一个解析工具方法统一兜底public static Integer parseInt(String str, Integer defaultValue) { if (str null || str.trim().isEmpty()) { return defaultValue; } try { return Integer.parseInt(str.trim()); } catch (NumberFormatException e) { return defaultValue; } }解析失败就返回默认值而不是抛异常。我拆项目时发现超过三分之一的前台500页都集中在字符串转数字这一步这个小函数能挡住绝大多数问题。顺带一个排错技巧别盯着浏览器页面猜原因直接看Tomcat的catalina.out日志。课设阶段的编译错误、类找不到、数据库连接失败都只打印在控制台或日志文件里浏览器只给你一个笼统的500。我的习惯是先用日志尾部定位异常栈第一行再按包名找对应代码大多数问题五分钟能定位。6. 进阶给购物车结算加库存扣减一个让答辩加分的改动如果你已经把这套系统完整跑通想让它在答辩时显得不只是一个“作业”我推荐做一个小改动提交订单时校验库存并扣减。改动量不大但直接连接了事务、并发和业务规则三个话题任何一个都能撑起答辩的提问环节。核心逻辑分三步查库存、扣库存、提交订单。把第3章的订单提交代码扩展一下public void submitOrder(Order order, ListCartItem cart) { try { conn.setAutoCommit(false); for (CartItem item : cart) { int stock goodsDao.getStock(item.getGoods().getId()); if (stock item.getCount()) { throw new BusinessException(商品[ item.getGoods().getName() ]库存不足); } goodsDao.decreaseStock(item.getGoods().getId(), item.getCount()); } int orderId orderDao.insert(order); for (CartItem item : cart) { orderItemDao.insert(orderId, item); } conn.commit(); } catch (BusinessException be) { conn.rollback(); throw be; } catch (Exception e) { conn.rollback(); throw new RuntimeException(订单提交失败, e); } finally { conn.setAutoCommit(true); } }扣减库存的SQL要写成条件更新防止超卖UPDATE tb_goods SET stock stock - ? WHERE id ? AND stock ?;这条SQL是防超卖的防守底线UPDATE影响行数为0说明库存不够不再继续下单。配合事务rollback整个订单不会留下半截数据。演示时你只需要在后台把某个蛋糕的库存改成1然后用两个浏览器同时下单立刻能看到“库存不足”提示这个效果比单纯展示列表页有说服力得多。答辩时如果老师追问“两个请求同时来怎么办”你就可以顺理成章提到“update的原子性保证了同一时刻只有一个请求能成功扣减”再补一句“如果要处理更复杂的并发场景需要引入悲观锁或乐观锁”。这层递进关系能把你从“会跑通”拉到“懂原理”。从那以后我拆任何JavaWeb课设都会先把商品表有没有库存字段、订单提交有没有事务包起来当作一个“项目是否认真”的体检项。这两个点只要有一个没做我就知道这个作者大概率只是完成了页面流转没有真正想过数据一致性排查时就会多加一分警惕。这次的蛋糕店系统在基础功能上是完整的表结构把快照和冗余也做对了值得你花一个晚上把它跑通、读懂再按自己的思路改出点名堂。希望帮到你。本文还有配套的精品资源点击获取
返回列表