ARTICLE DETAIL

资讯详情

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

JavaWeb商城购买项目全流程:Servlet/JSP + MySQL 从购物车到下单实战

JavaWeb商城购买项目全流程:Servlet/JSP + MySQL 从购物车到下单实战 简介基于Eclipse构建的JavaWeb网上购物商城项目面向初级开发者适合学习Servlet、JSP、JDBC和MVC分层开发并在此基础上进行二次扩展解决从零搭建小型电商系统时对技术选型和项目结构不明确的问题。压缩包共1122个文件大小8.52MB其中235个HTML页面、171个CSS样式、137个JS脚本、157个PNG图片和69个JPG图片等构成前端展示资源18个JSP页面、41个Java源文件、42个class文件、SQL脚本及XML配置等则用于后端业务逻辑、数据存储和项目部署配置此外还包括properties、txt等辅助文件从页面展示到数据持久化均有涉及可直观看出项目的前后端构成。目前已有2078人浏览学习适合入门者对照源码理解电商项目结构。通过该项目可掌握商品信息展示、购物车管理、会话跟踪、数据库读写等关键实现方式理解Session与Cookie、JDBC操作、动态页面渲染等核心知识点为后续完善订单处理、支付接口和后台管理等功能打下基础。整体代码结构相对清晰便于初学者定位核心模块是理解JavaWeb经典开发流程和电商基础功能的实用样例。1. JavaWeb 商城购买项目Eclipse 老技术栈为什么还能帮你把购买流程吃透每年实训季和毕设季「JavaWeb商城购买」这类题目看起来只有几个字但真正下手的人大多会卡在同一排问题上Eclipse 里 Tomcat 启动报 Bootstrap 主类找不到、MySQL 连接串配不对、购物车加了却提交不了订单。这套技术栈确实老可它恰好把 Servlet 生命周期、Session 状态、JDBC 事务这些换了框架也不变的东西摊开摆在你面前。这篇笔记按我实际调通一个网上购物项目的顺序来写从环境配对、数据表设计到购物车、下单、扣库存、支付状态流转最后把最常踩的启动、乱码、驱动、部署问题一次说清。适合正在做课设、毕设或者想把 Servlet 和数据库基本功补扎实的人。方案用老派但好用的组合Eclipse JSP/Servlet MySQL不引入 SpringBoot。2. 在 Eclipse 里跑通第一个 JavaWeb 商城页JDK、Tomcat、MySQL 的版本配对与最小骨架2.1 版本配对JDK 8、Tomcat 9、Eclipse Enterprise 版才是一条稳路很多人上手就装最新版 Eclipse再配一个 Tomcat 11结果项目一启动就报一串 ClassNotFoundException然后开始怀疑自己代码写得不对。其实问题出在版本身上Tomcat 10 和 11 属于 Jakarta EEServlet 的包名从 javax.servlet 换成了 jakarta.servlet老教程里的 import javax.servlet.http.* 在编译期直接报红网上拷下来的 JavaWeb 项目几乎全是这种老代码。我一般会按下面这套组合配能少受不少折腾组件推荐版本理由JDK1.8编译级别 1.8绝大多数教材和实训代码都是 1.8 写的语法上最省心Eclipse2022-06 或更新选 Enterprise Java and Web Developers 版自带 Dynamic Web Project、Server 视图和 JSP/HTML 编辑器少了这些后面寸步难行Tomcat9.0.x仍是 javax.servlet 命名空间Servlet 4.0 规范和教材代码兼容MySQL5.7 或 8.08.0 需要配对应驱动连接串5.7 更宽松JDBC 驱动mysql-connector-java 8.0.x兼容 5.7 和 8.0驱动类名写 com.mysql.cj.jdbc.Driver提示Tomcat 11 本身不是坏选择但它意味着你要把所有 javax 改成 jakarta对 JavaWeb 商城这种以老教材为底子的项目来说这个成本完全没有必要。这里面还有一层容易被忽视的坑Eclipse 从 2021 年之后的版本要求 JDK 11 以上才能启动 Eclipse 本身而项目编译目标仍然可以选 1.8。两件事不冲突但新人经常在这卡住——Eclipse 打不开就去翻 eclipse.ini 看启动日志里是不是提示需要更高 JDK。安装 Eclipse 时如果图快选了 Java SE 版后面会发现没有 Server 视图、没有 Dynamic Web Project 选项等于废了一半所以下载时认准 Enterprise Java and Web Developers 这个发行版。2.2 新建 Dynamic Web Project把 Tomcat 运行时挂给项目并跑通第一个 Servlet从零到能跑通一个页面完整操作是这样的打开 Eclipse菜单 File → New → Dynamic Web Project项目名写 shopTarget runtime 下拉框里选中刚才配置的 Tomcat 9.0.xDynamic web module version 选 3.1 或 4.0并勾上 Generate web.xml。这一步的关键是 Target runtime 不能留空白留空白等于告诉 Eclipse 这是个没有服务器运行时的普通项目。接着新建第一个 Servlet验证整个链路是通的。右键项目 src 目录 → New → Servlet类名 IndexServletURL mapping 填 /index。这是最直接的验证方式比一上来就写一堆 DAO 和 JSP 更不容易把问题扩散。// 首页跳转 Servlet负责把请求转发到商品列表页 WebServlet(/index) public class IndexServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 转发到 JSP浏览器地址栏路径不变请求和参数原样带到页面 req.getRequestDispatcher(/index.jsp).forward(req, resp); } }说明WebServlet 是 Servlet 3.0 起推荐的注解方式和 web.xml 里的 加 等价。注意 url-pattern 必须以 / 开头写成 index 不报错但访问时的路由永远 404。第一次 Run on Server 时Eclipse 会自动创建一个 Server 实例并把项目挂进去左下角状态显示 started 就说明运行时挂对了。如果请求进不了 Servlet先在 doGet 第一行打断点看请求有没有被 Filter 拦截而不是一上来就怀疑 Tomcat 装坏了。2.3 JDBC 连接 MySQL 8驱动类名、时区、SSL 参数一次配对把 mysql-connector-java 8.0.x 的 jar 复制到 WebContent/WEB-INF/lib 目录下Eclipse 会自动把它加入构建路径。最容易翻车的是把 jar 扔到项目根的任意文件夹因为 JavaWeb 项目的依赖最终要从 WEB-INF/lib 发布到 Tomcat位置错了编译期不报错运行期直接 ClassNotFoundException。public class DBHelper { // MySQL 8 的驱动类名注意是 com.mysql.cj.jdbc.Driver不是老版的 com.mysql.jdbc.Driver private static final String DRIVER com.mysql.cj.jdbc.Driver; // 四段参数关 SSL、指定时区、指定字符集、允许公钥检索 private static final String URL jdbc:mysql://localhost:3306/shop?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8allowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; public static Connection getConnection() throws Exception { Class.forName(DRIVER); // 触发驱动注册到 DriverManager return DriverManager.getConnection(URL, USER, PASSWORD); } }参数说明useSSLfalse 关掉本地连接时 MySQL 8 默认的 SSL 握手告警serverTimezoneAsia/Shanghai 解决“The server time zone value is unrecognized”的报错characterEncodingutf8 保证写入数据库字段的字符不乱码allowPublicKeyRetrievaltrue 解决 caching_sha2_password 认证插件下的 Public Key Retrieval is not allowed 问题。密码写在代码里只限本地学习部署时至少放到配置文件里。这个工具类写完后先用一个带 main 的测试类直接调 getConnection连通了再进 Web 层能省一半调试时间。3. 从购物车到提交订单五张表设计、Session 方案与下单核心路径3.1 商城购买最少五张表用户、商品、购物车、订单、订单项的 DDL一个能讲清楚的商城购买业务表结构至少要覆盖用户、商品、订单、订单明细这四类信息。下面按全量口径列五张表如果用 Session 购物车cart 表这轮可以省掉留到做购物车持久化时再补。表名关键字段职责userid, username, password, nickname, address登录人和收货信息productid, name, price, stock, status商品快照与库存cartid, user_id, product_id, count数据库版购物车ordersid, order_no, user_id, total_price, status, create_time, pay_time订单主表order_itemid, order_id, product_id, product_name, price, count订单行冗余商品名与单价orders 和 order_item 拆开的原因很直白一个订单多个商品、不同商品下单价格不同、历史订单不能因为商品改名就变这些语义一张表表达不了。表名叫 orders 而不是 order因为 order 是 SQL 关键字用带前缀的复数名最省心。用户表 password 建议存 MD5 或 SHA-256 加盐后的值别存明文答辩时这是送分也是送命题。CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, status TINYINT DEFAULT 1 ); CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, -- 订单号唯一防重复下单的兜底 user_id INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0, -- 0待支付 1已支付 2已取消 3超时关闭 4已完成 create_time DATETIME NOT NULL, pay_time DATETIME NULL ); CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, product_id INT NOT NULL, product_name VARCHAR(100) NOT NULL, -- 冗余商品改名不影响历史订单 price DECIMAL(10,2) NOT NULL, -- 下单时单价之后不要再查商品表 count INT NOT NULL );价格用 DECIMAL(10,2) 而不是 FLOAT金额精度别用浮点这是老生常谈但每届都有人踩。order_no 的 UNIQUE 索引除了查询方便更是抵抗重复点击的最后一层防线同一个订单号第二次 INSERT 会抛 Duplicate entry 异常而不是默默生成两单。外键我建议用逻辑外键应用层保证关联因为物理外键在删除商品、用户时会带出一串级联限制新手反而容易被绊倒。3.2 购物车存 Session 还是存表两种方案的取舍与加购代码购物车有两条路Session 方案把 MapInteger, Integer商品ID → 数量放进 HttpSessionDB 方案在 cart 表里按 user_id 存购物车行。Session 方案的优点是免建表、实现最快、容器天然帮你隔离不同用户缺点是服务重启购物车清空、换设备看不到、也无法统计加购未支付的数据。DB 方案的好处是登录后任意设备可见但要多写增删改四套 SQL还要考虑未登录状态怎么合并。我一般建议课设先把 Session 方案做通把购买链路跑顺购物车持久化作为答辩加分项。常见的折衷做法是未登录用 Session、登录后把 Session 购物车同步到 cart 表不过第一版别碰这个工作量不成比例。// 加购接口从页面拿到商品ID往 Session 购物车里放一条 WebServlet(/cart/add) public class CartAddServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int productId Integer.parseInt(req.getParameter(productId)); HttpSession session req.getSession(); // 购物车结构key 是商品IDvalue 是购买数量 MapInteger, Integer cart (MapInteger, Integer) session.getAttribute(cart); if (cart null) { cart new HashMap(); session.setAttribute(cart, cart); } cart.put(productId, cart.getOrDefault(productId, 0) 1); resp.sendRedirect(req.getContextPath() /cart.jsp); // 重定向回购物车页 } }getOrDefault 避免了第一次加购时的空指针判断重定向必须用 getContextPath() 拼前缀硬编码 /cart.jsp 会丢掉 Web 应用上下文部署后就是 404。购物车页面从 session.getAttribute(cart) 里拿 Map 循环渲染改数量就是 put 新值删除就是 remove(productId)。Session 方案还有一个隐形好处不需要额外判断这个用户到底开没开过购物车容器已经替你把它管好了。3.3 提交订单的代码路径后端重算价格、生成订单号、校验库存提交订单不是单纯 insert 一条记录顺序一般是校验登录 → 取 Session 购物车 → 遍历购物车查商品最新行情 → 后端重算订单总价 → 生成唯一订单号 → 插入 orders → 批量插入 order_item → 扣减 stock → 清空购物车 → 跳转支付页。前端传的合计金额在这条链路里只当展示用真正入库的总价必须从 product 表查出来再算否则改一下页面金额就能低价下单。private String createOrderNo(Integer userId) { SimpleDateFormat sdf new SimpleDateFormat(yyyyMMddHHmmss); String ts sdf.format(new Date()); // SecureRandom 比 Random 冲突概率更低四位随机够用 int rand new java.security.SecureRandom().nextInt(9000) 1000; return ts userId rand; }订单号用时间戳、用户ID、随机数拼出来保证唯一性。userId 为空时应该提前在登录取向阶段拦截不要拼出 null 字符串。这个方法的调用要放在事务里执行和插入 orders 保持同一个 Connection。购物车为空时直接重定向回购物车页并提示不要继续往下走。库存校验放在扣减这一步而不是先读一遍库存够不够因为「先查后扣」在并发下是废的这个问题第 4 章会重点讲。4. 付款瞬间才见真章事务扣库存、订单状态机与超时回滚4.1 订单状态机待支付、已支付、已取消、超时关闭的流转约束商城下单后订单要区分至少五种状态status含义允许流转到0待支付1支付成功、2用户取消、3超时关闭1已支付4确认完成2已取消无终态3超时关闭无终态4已完成无终态很多课设翻车就翻在状态机这里所有状态都用一个 update 一把梭用户取消订单之后又能去支付支付成功一下库存也扣了订单状态却还是待支付前后端各看各的。状态机不是架构名词落地就是一句带条件的 UPDATE支付接口只允许“待支付”转到“已支付”。-- 支付成功的唯一入口只影响处于待支付状态的那一行 UPDATE orders SET status 1, pay_time NOW() WHERE id ? AND status 0;逻辑说明受影响行数为 1说明支付成功且状态推进受影响行数为 0说明订单已经被取消、被关闭或者已经支付过前端直接提示“订单状态不可支付”。这一个 UPDATE 同时解决了重复支付和状态跳变两个问题。比先 SELECT 再判断再 UPDATE 的方式稳妥得多——后者的判断窗口里两个并发请求都能通过检查最后状态被后到的覆盖就出现支付两次只算一单的事故。4.2 扣库存和下单必须同事务setAutoCommit(false) 加乐观锁血泪经验订单 insert 和库存扣减分两次提交数据库单机跑不出问题一到多个用户同时下单库存变负数、订单却在增加。原因是 Connection 默认 autocommit 模式每条 SQL 独立提交第一个操作成功第二个失败数据库状态就是半成品。下单接口的完整事务骨架如下。public boolean placeOrder(HttpServletRequest req, Integer userId) throws Exception { Connection conn DBHelper.getConnection(); PreparedStatement ps null; try { conn.setAutoCommit(false); // 关键关闭自动提交让两个动作绑定成原子操作 // 1. 遍历 Session 购物车从 product 表读最新价格与库存累加 totalPrice // 先做合法性校验商品不存在或已下架就抛异常 // 2. 插入 orders 主表得到自增主键 orderId // 3. 逐条插入 order_item // 4. 乐观锁扣库存stock ? 这个条件保证不会扣成负数 String deductSql UPDATE product SET stock stock - ? WHERE id ? AND stock ?; // 执行后判断返回行数0 行说明库存不足抛异常回滚 // 5. 清空 Session 购物车 req.getSession().removeAttribute(cart); conn.commit(); // 全链路成功统一提交 } catch (Exception e) { conn.rollback(); // 任何一步失败订单和库存一起还原这就是后悔药 throw e; } finally { // 归还连接、关闭 PreparedStatement 和 ResultSet } }setAutoCommit(false) 之后这个 Connection 上的所有 SQL 都只在事务里排队直到 commit 才真正落盘。rollback 会把这一步插入的订单、扣减的库存全部还原所以“库存不足”这种业务异常不能被 catch 吞掉必须继续抛出让事务回滚。扣库存 SQL 里的 stock ? 是乐观锁最廉价的写法它把“检查库存”和“扣减库存”合并成一条原子 UPDATE不需要先 SELECT 再 UPDATE。资源关闭建议用 try-with-resources否则连接池C3P0/Druid很快被耗尽服务就变成黑匣子。PreparedStatement 也比 Statement 多了 SQL 注入防护这条在答辩里也常被问。4.3 超时未支付自动关单定时扫描、状态置为关闭并回补库存状态机里“超时关闭”没人手动触发得靠程序自己扫地。常见做法是起一个定时任务每分钟扫一次超过 10 分钟仍处于待支付的订单把它们置为关闭再把订单关联的商品库存加回去。回补库存必须按 order_item 里的实际数量不能简单地加一。// 每分钟执行找出超时未支付订单 String findSql SELECT id FROM orders WHERE status 0 AND create_time DATE_SUB(NOW(), INTERVAL 10 MINUTE); // 对每个订单先置关闭 String closeSql UPDATE orders SET status 3 WHERE id ? AND status 0; // 再按订单项回补库存 String restockSql UPDATE product p JOIN order_item oi ON p.id oi.product_id SET p.stock p.stock oi.count WHERE oi.order_id ?;DATE_SUB 的时间差精确到秒10 分钟只是一个参数按业务调 15、30 都行。closeSql 里的 AND status 0 和 4.1 一个思路防止订单刚被支付接口改成已支付这边定时任务又给关了两层保护不可省。回补库存用相对累加 stock stock oi.count即使这个商品期间卖出过也不会把现有库存顶没。定时任务在课设里用 Timer 或 ScheduledExecutorService放在一个随 Tomcat 启动的 ServletContextListener 里销毁时记得 shutdown 线程池。如果不想做超时关单至少把“用户取消订单”接口做好回补库存的逻辑复用同一套。5. Eclipse 跑 JavaWeb 商城必看的避坑记录启动失败、中文乱码、驱动与部署排错这章集中讲真实调试里被反复问到的五个问题。每条按现象、原因、解决的顺序写遇到哪条直接对号入座。5.1 Tomcat 报「找不到或无法加载主类 org.apache.catalina.startup.Bootstrap」现象Eclipse 启动 Tomcat控制台红字“找不到或无法加载主类 org.apache.catalina.startup.Bootstrap”Server 状态一直 Starting 又变回 stopped。原因这个类在 Tomcat 安装目录的 lib/catalina.jar 里报找不到通常是 classpath 里没有它。常见诱因有两个一是项目没有关联 Tomcat 运行时右键项目 Properties → Targeted Runtimes 是空的二是把 servlet-api.jar、catalina.jar 这类容器 jar 复制进了 WEB-INF/lib类加载冲突服务器找不到自己该用的 Bootstrap。解决先确认用的是 Tomcat 9 而不是 Tomcat 1111 下包名是 jakarta.servlet老代码 import javax 会直接失败然后在项目属性里勾上 Targeted Runtimes 里的 Tomcat删掉 WEB-INF/lib 下多余的 servlet-api.jar 和 catalina*.jar最后在 Servers 视图右键 Tomcat → Clean项目右键 → Clean重新启动。5.2 Eclipse 里创建不了 Servlet / Filter现象右键 New 菜单里看不到 Servlet 和 Filter或者手动建 class 写上 WebServlet 后访问路径 404。原因创建不了是 Eclipse 版本问题。官方 Eclipse 区分为 Java 版和 Enterprise Java and Web Developers 两个发行版Java 版不带 JST Web 工具集就没有 Servlet/Filter 向导。能建类但注解不生效是项目没被识别为 Web 项目Dynamic Web Module facet 没启用Eclipse 不会把它当成动态 Web 项目去编译部署。解决做 JavaWeb 不要下 Java SE 版下载页认准 Enterprise Java and Web Developers。已有旧项目右键 Properties → Project Facets → 勾上 Dynamic Web ModuleJava 版本调到 1.8。如果 facet 勾不动直接新建 Dynamic Web Project 把源码拷进去别跟 IDE 死磕这个时间花得不值。5.3 页面和接口中文全部乱码现象JSP 页面显示中文变成“锟斤拷”或者问号表单提交到 MySQL 里的数据也是问号。原因编码在四个位置不一致JSP 文件本身的 pageEncoding 字符集服务器读取请求参数时的编码响应输出到浏览器时的编码数据库连接和表字段的字符集。最坑的是页面能看到中文一提交到数据库就乱这种通常是连接串没带 characterEncodingutf8或者建表没写 DEFAULT CHARSET。解决统一做法是写一个过滤器放在所有请求最前面设置编码WebFilter(/*) public class EncodingFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(UTF-8); // 必须在读取任何参数之前 resp.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); chain.doFilter(req, resp); } }说明setCharacterEncoding 在读取参数之前调用才有效所以要用过滤器统一拦截而不是在每个 Servlet 里补。MySQL 建表统一写 ENGINEInnoDB DEFAULT CHARSETutf8mb4utf8mb4 才是完整 UTF-8emoji 也能存老库已经建错的用 ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4 修复。5.4 MySQL 8 驱动连接失败类名、时区、SSL 一个都不能少现象Class.forName 报 ClassNotFoundException或者连接时报“The server time zone value is unrecognized”再或者报 Public Key Retrieval is not allowed。原因三类问题混在一起。驱动 jar 没放到 WEB-INF/lib驱动类名写的是老版 com.mysql.jdbc.DriverMySQL 8 下这个类已移除连接串缺 serverTimezone 和 allowPublicKeyRetrievalMySQL 8 默认认证插件 caching_sha2_password 对这两项很敏感。解决统一用 2.3 节那串 URL驱动类名写 com.mysql.cj.jdbc.Driver。一个很有效的自检办法写一个带 main 的测试类直接调 DBHelper.getConnection()能通说明 JDBC 这层没问题剩下问题在 Web 发布层别每次都等 Tomcat 甩一段长报错才动手。5.5 改了代码不生效或请求总是 404现象改 JSP 刷新没变化改 Servlet 重新访问还是旧逻辑或者新加的页面直接 404。原因Eclipse 里的「项目」和「Tomcat 上运行实例」是两回事。只 Build 只写了 class 到磁盘没发布到 Tomcat 的 webappsTomcat 跑的还是上一次的旧文件。404 还有一种经典原因Module 没发布上去或者 Servlet 映射路径少了开头斜杠。解决在 Servers 视图里看当前 Tomcat 的 Modules 有没有你的项目修改后右键项目 → Clean再对 Tomcat 执行 Clean最后 Restart Deploy。浏览器调试时按 CtrlF5 强制刷新。检查 Servlet 路径时把 WebServlet(/xxx) 和表单 action/shop/xxx带上下文路径两处对着看别一个带前缀一个不带。6. 把「能跑」做成「可信」并发验证、状态约束与幂等收尾6.1 并发验证两个入口抢最后一个库存功能跑通只是开始。我习惯把某个商品库存改成 1然后开两个不同浏览器Chrome 和 Edge各登一个账号同时把同一商品下单。理想结果是一个订单成功另一个提示库存不足数据库里库存是 0 而不是 -1。用一条 SQL 就能收尾验证-- 下单后自查库存不为负、订单状态与订单项数量对得上 SELECT p.id, p.name, p.stock FROM product p WHERE p.id 1; SELECT COUNT(*) AS item_count FROM order_item WHERE product_id 1; SELECT order_no, status FROM orders WHERE id 2;如果库存出现 -1基本可以断定扣库存的 UPDATE 没带 stock ? 条件或者没走 4.2 的事务出现两条订单但库存只扣了一次说明订单插入和库存扣减不在同一个 Connection 事务里。这个问题答辩被问概率极高建议动手前就把它想清楚。6.2 给订单加唯一约束和状态流转约束下单接口还有一个隐藏陷阱是重复提交按钮用户双击提交或者前端跳转前又发了一次请求就可能同一个人下单两次。应付它有三道坎前端按钮置灰只是体验后端要判断订单号数据库要有最后防线。order_no 的 UNIQUE 能保证相同订单号第二次插入失败这也是为什么订单号里要带时间戳和随机数。状态流转则统一走「带旧状态的 UPDATE」支付、取消、超时关闭全部写 and status旧值再用受影响行数决定是否继续。以前我做网上购物课设支付成功没做状态判断测试时连点两次支付按钮同一订单产生了两次已支付流水库存按两单扣了被答辩老师直接点破。后来我定下一条规矩凡是订单状态变更一律 UPDATE ... WHERE id? AND status?不满足就报错。这个习惯后来在做 Spring 项目时换成 Transactional 加乐观锁本质还是同一个东西。希望你一次想清楚再动手少留几个这种低级坑。希望帮到你。本文还有配套的精品资源点击获取
返回列表