ARTICLE DETAIL

资讯详情

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

Java超市购物系统:数据库设计、事务与订单管理实战解析

Java超市购物系统:数据库设计、事务与订单管理实战解析 简介一套基于Java实现的超市购物系统完整源码包附带数据库文件与开发文档面向课程设计、毕业设计及需要Java Web项目练手的开发者便于缺少可运行参考项目的读者直接上手。压缩包共165个文件整体约2.48MB内含31个Java源文件、105个class编译文件、5个jar依赖包以及PNG界面截图、xls数据表、doc说明文档和mdf/ldf数据库文件类型覆盖完整。文档部分梳理了系统需求、功能模块、数据库表设计和接口调用等内容涵盖需求分析、数据库设计、API说明、用户手册与测试记录等开发环节并结合MVC分层、Spring集成和对象关系映射等典型实践展开说明有助于理解真实业务系统的开发脉络。目前已有349人学习使用适合有一定Java基础、希望借助完整案例提升项目开发能力的开发者参考学习。1. 为什么「Java超市购物系统数据库文档」是练手价值最高的项目如果你去搜“java超市购物系统”十个人里有八个是为了应付课程设计或毕业设计剩下两个是自学Java想找个完整项目练手。但很多人拿到源码后只盯着代码跑起来忽略了标题里后半句——包含数据库和详细的文档。我做了几年Java开发面试过不少应届生可以负责任地说代码能跑通的人很多能把数据库设计讲清楚、把文档写得像样的人十个里不到两个。这个项目真正的价值不在那几百行Java代码而在数据库表和配套文档是否经得起追问。超市购物系统是典型的“麻雀虽小五脏俱全”业务商品、用户、购物车、订单、库存每张表之间都有明确的关联关系天然适合练增删改查、事务和联表查询。它不像电商平台那样复杂到让人无从下手又比单纯的学生管理系统多了一层“下单扣库存”的强事务场景。这篇笔记我会从表结构设计讲到Java分层实现再讲管理端的订单流转和统计最后把常见坑和文档写法一并交代清楚希望能帮你把这个项目做得能在面试里拿得出手。2. 数据库设计先行从需求到建表的完整过程2.1 六张核心表把业务对象拆清楚拿到“超市购物系统”这个需求先别急着写Java代码第一件事是把业务里的实体列出来。常见的拆法就是六张表用户表、商品表、购物车表、订单表、订单明细表、管理员表。这六张表基本覆盖了用户从注册到下单再到管理员发货的全过程。用户表和商品表是基础数据购物车和订单是过程数据订单明细是订单的展开。设计时最核心的一个决策是购物车和订单为什么要分开两张表。购物车是临时数据用户可能今天加了三件商品明天又来修改它是会话级的订单是历史数据一旦产生就不能随意修改它是存档级的。如果混在一张表里订单的历史状态会被购物车的增删改污染后面做对账和统计会非常痛苦。用户表字段至少要有用户名、密码、手机号、注册时间。密码这一项有个细节很多课程设计直接明文存储这在面试时被问到会很尴尬稍微好一点的做法是MD5加盐哪怕系统本身很简单也建议把加密逻辑写上。商品表要包含商品名、价格、库存、分类、上下架状态。这里的库存字段非常重要后面下单扣库存的事务就靠它类型选INT不能省。订单表要记录订单编号、用户ID、总金额、订单状态、创建时间。总金额这个字段是冗余设计因为理论上可以用订单明细表SUM出来但实际开发中几乎没有人会每次查订单都去明细表做聚合订单表的总额字段能极大提升列表页和统计页的查询性能。订单明细表则存商品ID、单价、数量、小计它和订单表是一对多的关系通过订单ID关联。2.2 字段类型与约束建表SQL里的三个关键细节下面给出MySQL 8.0环境下的建表SQL。我习惯把建库建表脚本单独放在sql/init.sql里这本身就是“包含数据库”的一部分以后换机器部署直接执行这个脚本就行。CREATE DATABASE IF NOT EXISTS supermarket DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE supermarket; CREATE TABLE user ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键ID, username VARCHAR(50) NOT NULL COMMENT 用户名, password VARCHAR(100) NOT NULL COMMENT 密码(MD5加密), phone VARCHAR(20) DEFAULT NULL COMMENT 手机号, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE product ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品ID, name VARCHAR(100) NOT NULL COMMENT 商品名称, price DECIMAL(10,2) NOT NULL COMMENT 单价, stock INT NOT NULL DEFAULT 0 COMMENT 库存, category VARCHAR(50) DEFAULT 未分类 COMMENT 商品分类, status TINYINT NOT NULL DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_category (category) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表;这两张表的关键点金额字段不用FLOAT或DOUBLE而是用DECIMAL(10,2)。原因是二进制浮点数在计算0.1加0.2时会得到类似0.30000000000000004的结果存储金额这种精确值必须用定点数。这是数据库基础题里反复出现的考点也是真实项目里的硬性要求。库存字段加上DEFAULT 0约束避免插入时漏传导致NULL后续做库存扣减时NULL参与计算会直接变成NULL引发一系列莫名其妙的空指针。用户名加唯一约束UNIQUE KEY uk_username这是从数据层面防止重复注册的最后一道防线代码里的判断只是第一道。CREATE TABLE cart ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键ID, user_id INT UNSIGNED NOT NULL COMMENT 用户ID, product_id INT UNSIGNED NOT NULL COMMENT 商品ID, quantity INT NOT NULL DEFAULT 1 COMMENT 数量, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_user_product (user_id, product_id), KEY idx_product (product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT购物车表; CREATE TABLE orders ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键ID, order_no VARCHAR(32) NOT NULL COMMENT 订单编号, user_id INT UNSIGNED NOT NULL COMMENT 用户ID, total_amount DECIMAL(10,2) NOT NULL COMMENT 订单总金额, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态:0待付款 1已付款 2已发货 3已完成 4已取消, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 下单时间, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; CREATE TABLE order_item ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键ID, order_id INT UNSIGNED NOT NULL COMMENT 订单ID, product_id INT UNSIGNED NOT NULL COMMENT 商品ID, product_name VARCHAR(100) NOT NULL COMMENT 商品快照名称, price DECIMAL(10,2) NOT NULL COMMENT 下单时单价, quantity INT NOT NULL COMMENT 购买数量, subtotal DECIMAL(10,2) NOT NULL COMMENT 小计金额, PRIMARY KEY (id), KEY idx_order (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;购物车表加了个联合唯一索引uk_user_product保证同一个用户对同一件商品只有一条记录。这样用户在页面上反复点击“加入购物车”时后端只要执行INSERT ... ON DUPLICATE KEY UPDATE quantity quantity 1就能累加数量省去了先查询再判断更新或插入的繁琐逻辑。订单明细表里的product_name和price字段是典型的快照设计。如果只存商品ID下单三个月后商品改价或者被删除历史订单里的小计就全错了。把名称和单价原样复制一份到明细表订单就与商品表完全解耦这是电商系统订单模块的基本素养也是面试官喜欢追问的一个点。subtotal字段同样可以实时计算但存下来能让列表页少做乘法运算属于性价比很高的冗余。2.3 外键到底建不建InnoDB引擎下的取舍很多教材强调要建外键约束但在实际项目里我建表从来不加FOREIGN KEY只建普通索引。原因很简单外键约束会让每次插入和删除都去校验被引用表高并发下单时格外消耗性能而且一旦数据量上来删父表记录时外键的级联操作很容易把数据库锁死。超市购物系统虽然体量小但它教你的是真实项目的习惯。不加外键不代表关联关系不维护。通过user_id、product_id上的普通索引Java代码里用事务保证插入订单明细时一定已经插入了订单主表这种“应用层维护一致性”的方式是业界主流。面试时如果被问到“为什么不用外键”这个回答是可以直接用的。初始化数据也建议写进SQL脚本里。管理员账号、测试商品、测试用户这三类数据放进INSERT语句文档里注明测试账号和密码。这样别人拿到项目后不需要先注册再慢慢逛直接用测试账号登录后台体验成本会低很多。3. Java分层实现从DAO到Service的用户端核心链路3.1 项目结构与分层别把代码全塞进Servlet超市购物系统这类传统Java Web项目常见的结构是JSP Servlet JDBC也就是所谓的三层架构。我见过太多课程设计把所有代码写在Servlet里两百行代码从数据库查询写到HTML输出维护性为零。规范的分层是实体类entity放数据模型DAO层只做增删改查Service层处理业务逻辑Servlet或Controller层接收请求和返回页面。实际目录结构大致如下src/main/java/com/supermarket/ ├── entity/ User.java, Product.java, Cart.java, Orders.java ├── dao/ UserDao.java, ProductDao.java, CartDao.java, OrderDao.java ├── service/ UserService.java, CartService.java, OrderService.java ├── servlet/ LoginServlet.java, RegisterServlet.java, CartServlet.java, OrderServlet.java └── util/ DBUtil.java, MD5Util.java src/main/webapp/ ├── jsp/ login.jsp, register.jsp, index.jsp, cart.jsp, orders.jsp └── WEB-INF/web.xml实体类只是数据库表的映射字段与表结构一一对应基本没有技术含量。DAO层的写法在JDBC原生时代有一定套路每个方法里获取连接、准备PreparedStatement、执行、处理结果集、最后在finally里关闭资源。这里最核心的一个点是必须用PreparedStatement而不是Statement前者预编译能防止SQL注入后者直接拼接字符串是安全漏洞。数据库连接我建议用一个简单的DBUtil封装。常见做法是读取db.properties配置文件然后通过DriverManager.getConnection获取连接。虽然真实项目会用Druid或HikariCP连接池但学习阶段先理解原生JDBC的流程后面换连接池就是改一个工具类的事。3.2 加购与下单事务边界的正确姿势下单是超市购物系统里唯一需要数据库事务的场景也是最容易被写错的地方。一个完整下单操作包含三步检查库存并扣减、生成订单主表记录、生成订单明细记录。这三步要么全部成功要么全部回滚缺一不可。public boolean createOrder(int userId, MapInteger, Integer cartMap) { Connection conn null; try { conn DBUtil.getConnection(); // 开启事务关闭自动提交 conn.setAutoCommit(false); OrderDao orderDao new OrderDao(); double totalAmount 0; String orderNo SO System.currentTimeMillis(); // 遍历购物车中的商品商品ID - 数量 for (Map.EntryInteger, Integer entry : cartMap.entrySet()) { int productId entry.getKey(); int quantity entry.getValue(); // 步骤1扣减库存SQL中带上 stock quantity 条件 boolean ok orderDao.deductStock(conn, productId, quantity); if (!ok) { // 库存不足直接回滚 conn.rollback(); return false; } // 步骤2查询商品价格累加总金额 Product p orderDao.findProductById(conn, productId); totalAmount p.getPrice() * quantity; } // 步骤3插入订单主表获取自增ID int orderId orderDao.insertOrder(conn, orderNo, userId, totalAmount); // 步骤4逐条插入订单明细 for (Map.EntryInteger, Integer entry : cartMap.entrySet()) { Product p orderDao.findProductById(conn, entry.getKey()); orderDao.insertOrderItem(conn, orderId, p.getId(), p.getName(), p.getPrice(), entry.getValue()); } // 全部成功提交事务 conn.commit(); return true; } catch (Exception e) { // 任何异常都回滚保证数据一致 try { if (conn ! null) conn.rollback(); } catch (Exception ex) {} e.printStackTrace(); return false; } finally { try { if (conn ! null) { // 恢复自动提交并关闭连接 conn.setAutoCommit(true); conn.close(); } } catch (Exception e) {} } }这段代码的逻辑说明核心在conn.setAutoCommit(false)到conn.commit()之间构成一个事务边界。deductStock方法的SQL是关键所在它写成UPDATE product SET stock stock - ? WHERE id ? AND stock ?这个带条件的更新是防超卖的第一道防线如果影响行数为0则说明库存不足直接回滚。这里有一个容易忽略的参数orderNo SO System.currentTimeMillis()。用时间戳拼单号在单机学习项目里没问题但如果将来并发量上来同一毫秒可能生成重复单号。进阶的替代方案是Redis自增序列或UUID但在当前阶段保证单号唯一且可读就够了。事务的边界要覆盖“检查库存、扣库存、生成订单、生成明细”这四件事少任何一步都会出问题。如果先插入订单再扣库存扣库存失败时订单就在库里留下了脏数据如果扣了库存但没生成订单那库存就凭空少了。所以四步必须在一个事务里执行这是整个系统里事务唯一的用武之地。3.3 购物车合并与下单后的库存一致性购物车表在用户每次加购时的操作相对简单但有一个细节要注意用户可能把购物车页面打开很久不操作期间另一个用户已经把库存买光了。因此真正扣库存必须发生在下单那一刻而不能发生在加购那一刻。加购只往购物车表插记录库存的校验和扣减全部在订单事务里完成。另外下单成功后要把购物车里对应的商品记录删除。删除时机不能是“加购时”也不能是“生成订单前”而应该是在事务提交成功后。如果在事务里删除购物车一旦事务回滚购物车数据也会被误删用户想重新下单就得再挑一次。正确做法是在createOrder返回true之后调用CartDao.clearCart(userId)清理购物车。登录注册这块相对简单但建议至少做两件事密码用MD5加盐而非明文登录成功后把用户ID和用户名放进Session。Session的读取在JSP页面里通过${sessionScope.user.username}这种方式取值可以让页面逻辑更干净。退出登录就是session.invalidate()这个操作虽然代码只有一行但漏了会导致用户刷新页面后仍显示已登录状态。4. 管理端功能订单状态流转、销量统计与库存预警4.1 订单状态机用一张表管住所有流转管理端最常被做成“一堆增删改查”但订单模块的增删改查和普通数据不一样它的状态是有先后顺序的。前面建表SQL里把status定义为TINYINT类型0到4分别代表待付款、已付款、已发货、已完成、已取消。这五个状态之间的合法流转路径必须由代码控制不能允许用户从“已发货”直接跳回“待付款”。public boolean updateOrderStatus(int orderId, int currentStatus, int targetStatus, int userId) { // 合法的状态流转映射待付款-已付款、待付款-已取消、已付款-已发货、已发货-已完成 MapInteger, SetInteger allowedTransitions new HashMap(); allowedTransitions.put(0, new HashSet(Arrays.asList(1, 4))); allowedTransitions.put(1, new HashSet(Arrays.asList(2))); allowedTransitions.put(2, new HashSet(Arrays.asList(3))); SetInteger allowedTargets allowedTransitions.get(currentStatus); if (allowedTargets null || !allowedTargets.contains(targetStatus)) { // 非法状态流转直接拒绝 return false; } // 带条件的UPDATE防止并发下重复更新 String sql UPDATE orders SET status ? WHERE id ? AND status ? AND user_id ?; return update(sql, targetStatus, orderId, currentStatus, userId); }这段代码的逻辑是先把所有合法流转存入allowedTransitions更新时用WHERE id ? AND status ?这种乐观锁写法保证只有当前状态确实等于期望状态时才更新。这条SQL返回的影响行数为0就说明状态已经被别人改过操作被拒绝。这里有个实际场景值得注意用户下单后跳转支付页理论上存在“重复提交支付回调”的情况。比如用户点击两次支付按钮或者网络超时后重试这时如果后端不检查状态就会把“待付款”的订单连续两次更新为“已付款”。带状态条件的UPDATE天然解决了这个问题第二次更新时发现status不再等于0影响行数为0自然被拦截。管理端的商品上下架、分类调整则没有这么多状态逻辑直接UPDATE product SET status ? WHERE id ?即可。关键是管理端接口要做好权限控制管理员登录后Session里存一个isAdmin标记所有管理端Servlet在执行前统一检查没有权限直接跳转登录页不要每处都重复写这段校验代码。4.2 销量统计与库存预警三条SQL搞定运营数据管理端除了订单列表还要有简单的统计页面来展示销量和库存状况。统计模块最体现SQL基本功不需要Java做太多计算让数据库把聚合结果算好返回就行了。查询热销商品Top 10的SQL是这样的SELECT p.id, p.name, SUM(od.quantity) AS total_sold FROM order_item od JOIN product p ON od.product_id p.id JOIN orders o ON od.order_id o.id WHERE o.status IN (1, 2, 3) GROUP BY p.id, p.name ORDER BY total_sold DESC LIMIT 10;这条SQL的逻辑只有状态为已付款、已发货、已完成的订单才计入销量已取消的订单要排除在外。按商品ID分组后对数量求和再按销量倒序排列。这里用JOIN orders的目的是过滤订单状态如果明细表没有冗余状态字段就必须连表。库存预警的SQL则更简单查出所有库存低于警戒线的商品比如低于10件SELECT id, name, stock FROM product WHERE status 1 AND stock 10 ORDER BY stock ASC;预警阈值不要硬编码在Java里建议放到配置文件或者数据库参数表里这样改阈值时不用重新编译部署。常见做法是在管理端做一个“系统参数”页面把预警阈值做成可配置项。时间段统计的SQL也值得写查最近七天的每日订单数凑不出日期的日期用0补齐。这个需求在MySQL里用DATE_FORMAT 左连接实现略复杂学习阶段可以退而求其次先按天GROUP BY没有订单的日期自然不出现文档里注明这个限制即可面试官通常能接受。4.3 管理端页面的关键操作发货与取消的连带处理管理端“发货”操作除了改订单状态还有一个容易被忽略的连带动作用不上但代码里最好预留已发货的订单用户申请退款时不能直接取消而要走售后流程。这个逻辑在课程设计里可以简化但至少要知道状态机里“已付款”可以直接取消“已发货”不能。管理端的“删除商品”也别做成物理删除。商品可能被历史订单引用物理删掉后订单明细里的快照字段虽然还在但商品维度统计会缺失。业界通用做法是逻辑删除加个is_deleted字段或直接把status置为0下架。物理删除看起来很干净但会在统计数据上埋雷。5. 避坑与排查从连接池超时到中文乱码的5个血泪经验5.1 数据库连不上MySQL 8的驱动类与时区问题现象项目部署后启动第一次访问页面报ClassNotFoundException或Communications link failure。原因MySQL 8把驱动类从com.mysql.jdbc.Driver换成了com.mysql.cj.jdbc.Driver旧驱动类在新版本驱动包里被移除了。同时MySQL 8默认时区是UTCJDBC连接串不指定时区会报Server returns invalid timezone错误。解决在db.properties里确认驱动类为com.mysql.cj.jdbc.Driver连接串末尾加上?characterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse。这三个参数缺一不可编码参数保证中文不乱码时区参数保证时间字段正确SSL参数避免本地开发的证书校验报错。5.2 连接池耗尽finally里漏了关闭连接现象系统运行一会儿后页面加载越来越慢最后报Too many connections。原因DAO层的查询方法里获取了Connection但没在finally里关闭。Java的Connection对象被垃圾回收时不一定立即释放数据库连接高并发下连接数很快被打满。解决每个DAO方法严格按照try-catch-finally结构finally里依次关闭ResultSet、PreparedStatement、Connection关闭顺序与打开顺序相反。更省心的方案是用try-with-resources语法让JVM帮你关连接。try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, id); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User user new User(); user.setId(rs.getInt(id)); return user; } } } catch (Exception e) { e.printStackTrace(); }这段代码的好处是三层资源全部交给try-with-resources管理无论正常返回还是抛出异常连接的关闭都由语言机制保证。注意ResultSet要单独嵌套一层try因为它依赖PreparedStatement必须在Statement之后关闭。5.3 中文乱码从JSP到MySQL的编码链路现象页面上商品名称显示问号或者数据库里存的是乱码明明建表时指定了utf8mb4。原因编码问题往往出在请求链路上浏览器用UTF-8发请求Servlet用ISO-8859-1解析JSP页面又没指定编码。解决四层统一。JSP文件头部加% page contentTypetext/html;charsetUTF-8 languagejava %Servlet里加request.setCharacterEncoding(UTF-8)数据库连接串带characterEncodingutf8MySQL建库指定utf8mb4。这四层少一层都会出现局部乱码排查时先确认数据库本身能不能直接用SQL插入中文能插就是Java层的编码问题。5.4 事务无效表引擎是MyISAM回滚静默失效现象下单方法里故意在插入订单明细后抛异常发现订单主表的数据还是插入成功了事务完全没起作用。原因MySQL的MyISAM引擎不支持事务conn.rollback()对它来说是空操作。解决建表时每个表都指定ENGINEInnoDB这个参数在建表SQL里别省。如果已经建了MyISAM的表用ALTER TABLE orders ENGINEInnoDB迁移引擎。排查方式也很简单SHOW CREATE TABLE orders看表引擎这一条放在项目初始化检查清单里。5.5 外键与批量删除的冲突删用户把订单也牵连了现象后台想删除一个测试用户报外键约束错误或者连带把该用户的订单也删了。原因建表时如果加了ON DELETE CASCADE删除用户会级联删掉orders表里的记录连带order_item表也没了。这在测试阶段可能无所谓但生产环境是事故。解决要么不建物理外键靠应用层维护要么给外键指定ON DELETE RESTRICT有订单的用户禁止删除。更优雅的方案是用户表加status字段做逻辑注销保留所有历史数据这既是行业习惯也能让你的项目在面试里多一个亮点。还有一个经典坑批量删除购物车记录时用DELETE FROM cart WHERE user_id ?如果忘记加user_id条件会把所有用户的购物车清空。我见过不止一个新手在产品环境犯这个错建议所有DELETE语句写完后先自查一遍WHERE条件这个习惯能救你很多次。6. 让文档成为加分项从数据库说明到接口清单的进阶技巧6.1 数据库文档的三种打开方式标题强调“详细的文档”但大部分人的“文档”只是把代码注释抄了一遍。真正有分量的文档至少包含三部分数据库设计说明、接口说明、部署手册。数据库设计说明里要有ER图、表结构字典表和初始化数据说明。表结构字典表用Excel或Markdown表格写清楚每个字段的类型、长度、含义、是否为空、默认值这份文档比任何代码注释都直观。接口说明不要求写成那种正式的API文档但至少要把每个Servlet或Servlet路径对应的功能列清楚比如/login对应登录/cart/add对应加购参数有哪些返回值是什么。部署手册则要写清楚JDK版本、MySQL版本、Tomcat版本以及从导入SQL到启动项目的完整步骤。这三份文档配合初始化的测试账号能让一个从未接触过你项目的人在三十分钟内把它跑起来。6.2 代码注释与“遇到问题记录”模块代码注释贵精不贵多。我倾向于在方法名上用Javadoc说明业务含义在复杂逻辑处用行注释解释“为什么这么写”而不是逐行翻译代码。比如下单事务那段代码注释应该写“这是防超卖的关键条件”而不是“扣减库存”。遇到问题记录是容易被忽略但性价比极高的模块——把你踩过的坑比如时区报错、连接没关、编码乱码按现象、原因、解决三段式写进文档这就是给未来的自己留的后悔药。6.3 进阶方向哪些改造能让项目在面试中脱颖而出如果你时间充裕这个方向有三个高性价比的改造点。第一个是引入MyBatis替换原生JDBC学习曲线平缓但能体现你对持久层框架的掌握第二个是给商品列表页加Redis缓存商品数据读多写少缓存能显著降低数据库压力第三个是下单接口完善并发控制用SELECT ... FOR UPDATE或者Redis预扣库存把这个点讲清楚面试含金量立刻不一样。最后一个习惯是我这几年才养成的项目跑通之后把建表SQL、初始化数据、部署手册、遇到问题记录一起放进一个zip包命名为supermarket-v1.0.0.zip放到一个固定目录保存。下次面试前不用翻聊天记录找源码也不用重新回忆怎么部署。文档要长期更新改一次需求就同步改一次数据库说明和接口清单否则文档和代码不匹配比没有文档更误事。希望这篇笔记能帮你把这个项目做得完整、讲得清楚面试时心里有底。本文还有配套的精品资源点击获取
返回列表