资讯动态

JavaWeb在线点餐系统实战:从数据库设计到Spring Boot部署

发布时间:2026/8/27 21:06:28 来源:尧图企业网站定制
简介在Web应用开发领域数据库设计与业务逻辑实现是构建稳定系统的核心基础。其原理在于通过合理的表结构规划与索引优化确保数据一致性并支撑高并发访问这对于电商、社交等各类互联网平台具有普遍的技术价值。以在线点餐这一典型应用场景为例系统需处理用户、订单、菜品等多实体关系并保障购物车、库存等核心模块的准确运作。本文基于Spring Boot与MyBatis-Plus技术栈深入剖析了订单状态机设计与事务控制等关键实现并针对高并发下的库存超卖问题探讨了通过Redis分布式锁或乐观锁等方案进行防护的工程实践为开发具备商用潜力的JavaWeb项目提供完整参考。1. 项目概述从零到一构建一个可商用的在线点餐系统最近在整理硬盘翻出了当年毕业设计的“遗产”——一个基于JavaWeb的在线点餐系统。看着那个熟悉的在线点餐系统源码数据库.zip压缩包不禁感慨万千。这不仅仅是一个毕业设计它几乎涵盖了从后台管理到前端用户交互、从数据库设计到业务逻辑实现的完整Web开发流程。对于正在学习JavaWeb或者想找一个完整项目来练手、丰富简历的朋友来说这类系统是一个绝佳的“练级副本”。它麻雀虽小五脏俱全涉及的技术栈非常经典Servlet/JSP或Spring MVC、JDBC或MyBatis/Hibernate、MySQL、HTML/CSS/JavaScript以及Tomcat服务器。今天我就以这个项目为蓝本结合我后来在实际工作中的经验为你深度拆解如何构建一个扎实、可扩展、甚至具备初步商用潜力的在线点餐系统。我会带你走过从需求分析、技术选型、数据库设计到核心功能实现、安全考量再到部署上线的完整闭环并分享那些只有踩过坑才知道的“实战心得”。2. 系统核心需求与业务逻辑拆解在动手写一行代码之前我们必须彻底搞清楚这个系统要干什么为谁服务。一个在线点餐系统本质上是连接顾客、商家和配送员如果支持的多边平台。我们的毕业设计版本通常聚焦于核心的顾客与商家两端。2.1 用户角色与核心用例系统主要涉及两类用户前台顾客和后台管理员即商家。顾客端核心需求浏览与搜索清晰分类展示菜品如热销、套餐、饮料支持按名称、分类搜索。菜品详情查看菜品图片、描述、价格、库存状态。购物车管理添加、删除、修改购物车中的菜品数量。订单流程生成订单、选择配送地址或到店自取、选择支付方式模拟、提交订单。个人中心查看历史订单、订单状态待付款、待制作、配送中、已完成、管理收货地址。管理端核心需求菜品管理对菜品进行增删改查CRUD包括上传图片、设置分类、调整价格和库存。订单管理查看所有订单处理订单接单、标记制作完成、标记已配送/完成处理退款如果设计。分类管理管理菜品分类如川菜、湘菜、主食、酒水。数据统计简单的销售数据看板如日/月订单量、热门菜品排行。注意毕业设计通常简化支付和配送。支付环节常采用“模拟支付”即点击支付按钮后订单状态直接变为“已付款”。配送则简化为“配送中”和“已完成”的状态流转或仅支持“到店自取”。2.2 业务流程与状态机设计订单是系统的核心业务实体其状态流转定义了系统的业务流程。一个健壮的状态机设计至关重要。顾客提交订单状态为“待付款”。此时库存应被预扣防止超卖但订单可被取消库存回滚。模拟支付成功状态变为“待接单/待制作”。此时订单正式进入后厨流程。商家接单状态变为“制作中”。商家标记制作完成状态变为“待配送”或“待取餐”。配送/取餐完成状态最终变为“已完成”。这个状态机需要用数据库字段如order_status和后台逻辑严格维护。状态只能向前推进不可随意跳跃或回退除取消逻辑外这是保证业务数据一致性的基础。3. 技术选型与架构设计解析为什么是JavaWeb因为它成熟、稳定、生态完整是学习企业级应用开发的经典路径。下面是我们基于当前非十年前最佳实践的技术选型建议。3.1 后端技术栈从Servlet到Spring Boot核心框架Spring Boot。这是绝对的主流选择。它避免了传统SSH/SSM繁琐的XML配置内置Tomcat可以让你“一键启动”项目。对于新手来说start.spring.io网站生成项目骨架极其方便。它整合了Spring MVC处理Web请求、Spring Data JPA或MyBatis操作数据库、Spring Security安全控制等是事实上的标准。数据持久层MyBatis-Plus。相比于原始的JDBC或复杂的HibernateMyBatis-Plus在MyBatis的基础上提供了强大的CRUD封装和条件构造器能极大减少简单SQL的编写同时保留复杂SQL手写的灵活性。它的TableName、TableField注解让实体类与数据库表映射变得直观。数据库MySQL 8.0。关系型数据库的不二之选。务必使用8.0版本它在性能、JSON支持和窗口函数等方面比5.7有显著提升。项目管理与构建Maven或Gradle。管理项目依赖Jar包。Spring Boot项目默认使用Gradle或Maven。为什么不直接用最原始的ServletJSP虽然能完成功能但代码会显得臃肿、难以维护。Servlet负责逻辑JSP混写Java和HTML这种模式Model 1早已被更清晰的MVC模式淘汰。使用Spring Boot等现代框架能让你更专注于业务逻辑而非底层配置这也是企业开发的实际要求。3.2 前端技术栈告别JSP拥抱前后端分离毕业设计的老代码可能全是JSP。但对于一个现代项目我强烈建议采用前后端分离架构。后端Spring Boot仅提供RESTful API接口返回JSON数据。职责清晰接收请求、处理业务、访问数据库、返回JSON。前端独立的工程可以使用Thymeleaf一种现代的服务端模板引擎语法自然适合不想深入前端但希望有较好视图层的开发者。它仍属于“后端渲染”但比JSP优雅。Vue.js / React真正的分离。前端通过Ajaxaxios库调用后端API动态渲染页面。这是目前主流互联网公司的标准做法。优势是前端体验好、交互丰富后端接口可被多种客户端Web、小程序、App复用。对于毕业设计如果时间有限Thymeleaf是平滑过渡的好选择。如果希望技术栈更前沿Vue.jsElement UI是快速构建美观管理后台的利器。3.3 项目分层架构MVC演进版一个结构清晰的项目是成功的一半。推荐采用以下分层com.yourcompany.ordering ├── controller // 控制层接收请求调用Service返回结果 ├── service // 业务逻辑层核心业务处理在这里 │ └── impl // 业务逻辑实现类 ├── mapper // MyBatis的Mapper接口层负责数据库操作 ├── entity // 实体层与数据库表对应的Java类 ├── dto // 数据传输对象用于前后端交互可能不同于Entity ├── vo // 视图对象用于封装返回给前端的数据 ├── utils // 工具类如加密、验证、文件上传等 └── config // 配置类如Web配置、数据库配置、拦截器等这种分层确保了“高内聚、低耦合”。Controller很薄只负责参数校验和响应封装Service包含所有业务规则Mapper只做数据存取。4. 数据库设计与核心表结构详解数据库设计是系统的基石。设计不当后期修改变得极其痛苦。以下是核心表及其字段设计我为你加上了关键注释和设计理由。4.1 核心实体表设计1. 用户表 (user)CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(50) NOT NULL UNIQUE COMMENT 用户名用于登录, password varchar(255) NOT NULL COMMENT 加密后的密码, nickname varchar(50) DEFAULT NULL COMMENT 用户昵称, phone varchar(20) DEFAULT NULL COMMENT 手机号, email varchar(100) DEFAULT NULL COMMENT 邮箱, avatar varchar(500) DEFAULT NULL COMMENT 头像URL, user_type tinyint NOT NULL DEFAULT 1 COMMENT 用户类型1-顾客2-管理员, status tinyint NOT NULL DEFAULT 1 COMMENT 状态0-禁用1-启用, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;设计要点password字段必须足够长推荐255以存储BCrypt等强哈希算法的结果。user_type用于区分前后台用户。status实现软删除或禁用功能。utf8mb4字符集支持存储Emoji表情。2. 菜品分类表 (category)CREATE TABLE category ( id int NOT NULL AUTO_INCREMENT, name varchar(20) NOT NULL COMMENT 分类名称如主食、饮料, sort int DEFAULT 0 COMMENT 排序字段值越小越靠前, status tinyint DEFAULT 1 COMMENT 状态0-停用1-启用, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT菜品分类表;设计要点独立的分类表便于管理。sort字段用于后台拖拽排序。3. 菜品表 (dish)CREATE TABLE dish ( id bigint NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 菜品名称, category_id int NOT NULL COMMENT 所属分类ID, price decimal(10,2) NOT NULL COMMENT 价格, image varchar(500) DEFAULT NULL COMMENT 菜品图片URL, description text COMMENT 菜品描述, status tinyint DEFAULT 1 COMMENT 状态0-停售1-启售, stock int DEFAULT 999 COMMENT 库存简化模型实际可能需更复杂库存管理, sales int DEFAULT 0 COMMENT 销量用于排序, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category_status (category_id,status) COMMENT 复合索引优化按分类查询 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT菜品表;设计要点价格使用DECIMAL(10,2)类型精确存储金融数据。image存储图片在服务器或OSS对象存储上的路径而非二进制数据。stock库存是简化模型在高并发场景下需要更精细的设计如预扣库存。建立(category_id, status)复合索引能极大提升前台按分类查询启售菜品的速度。4. 购物车表 (shopping_cart)CREATE TABLE shopping_cart ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 用户ID, dish_id bigint NOT NULL COMMENT 菜品ID, number int NOT NULL DEFAULT 1 COMMENT 菜品数量, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY idx_user_dish (user_id,dish_id) COMMENT 唯一索引防止同一菜品重复添加 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT购物车表;设计要点(user_id, dish_id)唯一索引是关键。它确保了同一个用户的购物车里同一菜品只对应一条记录数量变化只需更新number字段而不是新增记录。这简化了“添加至购物车”的逻辑。5. 订单主表 (orders)CREATE TABLE orders ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, order_no varchar(32) NOT NULL UNIQUE COMMENT 订单号唯一用于对外展示, user_id bigint NOT NULL COMMENT 用户ID, total_amount decimal(10,2) NOT NULL COMMENT 订单总金额, status tinyint NOT NULL DEFAULT 1 COMMENT 订单状态1-待付款2-待接单3-制作中4-待配送5-配送中6-已完成0-已取消, address varchar(500) NOT NULL COMMENT 配送地址, consignee varchar(50) NOT NULL COMMENT 收货人, phone varchar(20) NOT NULL COMMENT 收货人电话, pay_method tinyint DEFAULT NULL COMMENT 支付方式1-微信2-支付宝, pay_time datetime DEFAULT NULL COMMENT 支付时间, estimated_delivery_time datetime DEFAULT NULL COMMENT 预计送达时间, remark varchar(500) DEFAULT NULL COMMENT 订单备注, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_order_no (order_no), KEY idx_create_time (create_time) COMMENT 便于按时间查询统计 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单主表;设计要点order_no订单号必须全局唯一且不连续避免被猜出订单量通常用“时间戳随机数用户ID哈希”等方式生成。status字段使用明确的整型状态码并在代码中用枚举类Enum管理避免魔法数字。所有时间字段pay_time,estimated_delivery_time都清晰定义。6. 订单明细表 (order_detail)CREATE TABLE order_detail ( id bigint NOT NULL AUTO_INCREMENT, order_id bigint NOT NULL COMMENT 订单ID, dish_id bigint NOT NULL COMMENT 菜品ID, dish_name varchar(100) NOT NULL COMMENT 菜品名称快照, dish_image varchar(500) DEFAULT NULL COMMENT 菜品图片快照, amount decimal(10,2) NOT NULL COMMENT 单价快照, number int NOT NULL COMMENT 数量, PRIMARY KEY (id), KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;设计要点这是最重要的设计之一订单明细必须保存下单瞬间的菜品信息名称、图片、单价即“数据快照”。因为菜品信息如价格、名称后续可能会被商家修改但订单历史必须保持原样。order_id建立索引方便查询一个订单的所有明细。4.2 表关系与索引优化心得关系user:orders 1 : Norders:order_detail 1 : Ndish:order_detail 1 : N通过快照关联。索引心得主键id自增InnoDB的聚簇索引。唯一索引user.username,orders.order_no保证业务唯一性。普通索引所有外键字段如order_detail.order_id,orders.user_id必须建索引这是数据库优化的黄金法则。复合索引像dish表的(category_id, status)针对高频查询场景建立。避免过度索引索引会降低写速度。只为高频的查询条件WHERE, ORDER BY, JOIN建立索引。5. 核心功能模块实现与代码剖析接下来我们深入到几个最关键的业务模块看看代码应该如何组织。这里以Spring Boot MyBatis-Plus Thymeleaf技术栈为例。5.1 用户登录与会话管理安全是第一位。绝对不能明文存储密码。// Service层用户登录逻辑 Service public class UserServiceImpl implements UserService { Autowired private UserMapper userMapper; Override public User login(String username, String rawPassword) { // 1. 根据用户名查询用户 User user userMapper.selectOne(new QueryWrapperUser().eq(username, username)); if (user null) { throw new RuntimeException(用户名或密码错误); // 应使用自定义业务异常 } // 2. 验证密码使用BCryptPasswordEncoder BCryptPasswordEncoder encoder new BCryptPasswordEncoder(); if (!encoder.matches(rawPassword, user.getPassword())) { throw new RuntimeException(用户名或密码错误); } // 3. 检查用户状态 if (user.getStatus() 0) { throw new RuntimeException(账号已被禁用); } // 4. 登录成功返回用户信息注意清除敏感信息如password user.setPassword(null); return user; } }实操要点使用BCryptPasswordEncoder进行密码加密和验证。它是目前最推荐的密码哈希算法内置盐值salt能有效抵御彩虹表攻击。用户注册时调用encoder.encode(rawPassword)生成密文存入数据库。会话管理登录成功后将用户ID等信息存入HttpSession。可以编写一个拦截器Interceptor来保护需要登录的页面。// 登录拦截器示例 Component public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user request.getSession().getAttribute(currentUser); if (user null) { // 未登录跳转到登录页 response.sendRedirect(/login); return false; } return true; } }踩坑提醒Session默认存储在服务器内存中在集群部署或服务器重启时会丢失。生产环境需要将会话数据集中存储如Redis。对于毕业设计可以暂时忽略。5.2 购物车与订单生成购物车的核心是合并同一商品。// Service层添加购物车 public void addToCart(Long userId, Long dishId, Integer number) { // 构建查询条件用户ID和菜品ID LambdaQueryWrapperShoppingCart wrapper new LambdaQueryWrapper(); wrapper.eq(ShoppingCart::getUserId, userId).eq(ShoppingCart::getDishId, dishId); ShoppingCart cartItem shoppingCartMapper.selectOne(wrapper); if (cartItem ! null) { // 已存在更新数量 cartItem.setNumber(cartItem.getNumber() number); shoppingCartMapper.updateById(cartItem); } else { // 不存在新增记录 cartItem new ShoppingCart(); cartItem.setUserId(userId); cartItem.setDishId(dishId); cartItem.setNumber(number); shoppingCartMapper.insert(cartItem); } }生成订单是系统最复杂的业务涉及数据库事务必须保证“购物车清空”、“订单创建”、“扣减库存”三者要么全部成功要么全部失败。// Service层提交订单事务性操作 Transactional(rollbackFor Exception.class) // 声明式事务异常则回滚 public String submitOrder(OrderSubmitDTO submitDTO, Long userId) { // 1. 查询当前用户的购物车数据 ListShoppingCart cartList shoppingCartMapper.selectByUserId(userId); if (cartList null || cartList.isEmpty()) { throw new RuntimeException(购物车为空); } // 2. 计算总金额并校验菜品状态和库存这里需要遍历cartList查询dish表 BigDecimal totalAmount BigDecimal.ZERO; ListOrderDetail orderDetailList new ArrayList(); for (ShoppingCart cart : cartList) { Dish dish dishMapper.selectById(cart.getDishId()); // 校验菜品是否存在、是否启售、库存是否充足 if (dish null || dish.getStatus() 0) { throw new RuntimeException(菜品[ dish.getName() ]已下架); } if (dish.getStock() cart.getNumber()) { throw new RuntimeException(菜品[ dish.getName() ]库存不足); } // 计算金额 BigDecimal itemAmount dish.getPrice().multiply(new BigDecimal(cart.getNumber())); totalAmount totalAmount.add(itemAmount); // 构建订单明细快照 OrderDetail detail new OrderDetail(); detail.setDishId(dish.getId()); detail.setDishName(dish.getName()); detail.setDishImage(dish.getImage()); detail.setAmount(dish.getPrice()); detail.setNumber(cart.getNumber()); orderDetailList.add(detail); // **预扣库存**高并发下需用乐观锁或分布式锁此处简化 dish.setStock(dish.getStock() - cart.getNumber()); dishMapper.updateById(dish); } // 3. 生成订单号工具类生成 String orderNo OrderNoGenerator.generate(); // 4. 组装订单主数据 Orders order new Orders(); order.setOrderNo(orderNo); order.setUserId(userId); order.setTotalAmount(totalAmount); order.setStatus(OrderStatusEnum.WAIT_PAY.getCode()); // 使用枚举 order.setAddress(submitDTO.getAddress()); // ... 设置其他字段 orderMapper.insert(order); // 插入后order.getId()会被自动填充 // 5. 保存订单明细设置orderId for (OrderDetail detail : orderDetailList) { detail.setOrderId(order.getId()); orderDetailMapper.insert(detail); } // 6. 清空购物车 shoppingCartMapper.deleteByUserId(userId); // 7. 返回订单号 return orderNo; }关键点解析Transactional这是Spring提供的事务注解。方法内所有数据库操作在一个事务内任何一步失败整个事务回滚数据保持一致。库存预扣在订单创建时即扣减库存防止超卖。这是一个简化模型在高并发场景下dish.setStock(dish.getStock() - cart.getNumber())存在“更新丢失”风险。生产环境需使用UPDATE dish SET stock stock - #{number} WHERE id #{id} AND stock #{number}这样的原子操作或使用乐观锁版本号。订单号生成不能使用数据库自增ID因为暴露业务量且可能被遍历。常用“时间戳yyyyMMddHHmmss 随机数或序列”来生成。5.3 后台管理菜品与订单管理后台管理主要是对数据表的CRUD操作但需要注意文件上传和数据分页。文件上传菜品图片 Spring Boot使用MultipartFile接收上传文件。PostMapping(/upload) public Result uploadImage(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return Result.error(请选择文件); } // 1. 生成唯一文件名防止覆盖 String originalFilename file.getOriginalFilename(); String suffix originalFilename.substring(originalFilename.lastIndexOf(.)); String fileName UUID.randomUUID().toString() suffix; // 2. 指定存储路径开发时存本地生产环境应上传至OSS File dest new File(/path/to/upload/dir/ fileName); try { file.transferTo(dest); // 3. 返回访问路径如 /images/xxx.jpg String imageUrl /images/ fileName; return Result.success(imageUrl); } catch (IOException e) { e.printStackTrace(); return Result.error(上传失败); } }重要提醒生产环境绝对不要将用户上传的文件存在应用服务器的本地目录因为服务器重启、扩容或集群部署时文件会丢失或访问不到。必须使用对象存储服务OSS如阿里云OSS、腾讯云COS它们提供稳定、海量、高可用的文件存储和访问服务。数据分页查询 MyBatis-Plus提供了强大的分页插件。// Controller GetMapping(/page) public Result pageDish(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size, String name) { PageDish pageInfo new Page(page, size); LambdaQueryWrapperDish wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.isNotBlank(name), Dish::getName, name); // 动态条件 wrapper.orderByDesc(Dish::getUpdateTime); PageDish result dishService.page(pageInfo, wrapper); return Result.success(result); }前端如Thymeleaf或Vue接收分页结果对象包含records数据列表、total总条数、current当前页等即可渲染表格和分页组件。6. 系统安全、性能与部署考量6.1 安全加固要点SQL注入防护使用MyBatis-Plus等ORM框架其底层使用预编译语句PreparedStatement能有效防止SQL注入。绝对不要手动拼接SQL字符串。XSS跨站脚本攻击对用户输入如地址、备注进行转义或过滤。Thymeleaf模板默认会对输出进行HTML转义。如果前端是Vue/React也要注意使用v-html或dangerouslySetInnerHTML时的风险。CSRF跨站请求伪造如果是JSP/ThymeleafSpring Security可以提供CSRF令牌保护。如果是前后端分离确保API设计遵循RESTful无状态原则并使用合适的认证机制如JWT可降低CSRF风险。敏感信息泄露不要在日志、异常信息中打印密码、SQL语句生产环境、完整的堆栈信息。配置文件中的数据库密码等应使用环境变量或配置中心。权限控制后台管理接口必须校验用户角色user_type。可以使用Spring Security或自定义注解拦截器实现。6.2 性能优化浅谈数据库层面如前所述合理的索引是最有效的优化。避免SELECT *只查询需要的字段。复杂查询考虑使用冗余字段或定期统计表。应用层面缓存将不常变但高频访问的数据放入缓存如菜品分类、热门菜品。可以使用Spring Cache集成Redis。异步处理对于非实时任务如订单完成后发送短信通知可以放入消息队列如RabbitMQ异步处理提升主流程响应速度。前端层面压缩静态资源JS/CSS/图片使用浏览器缓存。6.3 项目部署上线毕业设计通常本地运行即可。但如果想体验完整流程可以打包使用mvn clean package生成可执行的JAR文件Spring Boot或WAR包。准备服务器购买一台云服务器如阿里云ECS安装JDK、MySQL。数据库初始化在服务器MySQL中执行建表SQL脚本。上传与运行将JAR包和配置文件上传至服务器。使用nohup java -jar your-app.jar 命令在后台运行。域名与Nginx购买域名并解析到服务器IP。安装Nginx配置反向代理将80端口的请求转发到你的Spring Boot应用默认8080端口。这样用户就可以通过域名访问了。7. 常见问题排查与调试心得在开发过程中你一定会遇到各种“坑”。这里记录几个典型的问题一页面提交中文数据到后台出现乱码。排查检查各个环节的字符编码。解决确保MySQL数据库、表、字段的字符集为utf8mb4。在Spring Boot的application.properties中配置数据库连接参数jdbc:mysql://...?useUnicodetruecharacterEncodingutf-8。检查Tomcat/Spring Boot的服务器编码配置。Spring Boot 2.x后默认已是UTF-8。前端页面meta charsetUTF-8。问题二MyBatis-Plus插入数据后实体对象ID为null。原因默认的ID生成策略是IdType.NONE即跟随数据库自增。但插入后需要手动查询才能获得ID。解决在实体类主键字段上添加注解TableId(type IdType.AUTO)并确保数据库字段是自增AUTO_INCREMENT。这样插入后实体的ID字段会被自动回填。问题三事务Transactional不生效。常见原因方法不是public的。异常被方法内部catch住了没有抛出。在同一个类中一个非事务方法A调用了事务方法B事务不会生效Spring AOP代理机制导致。解决确保事务方法是public确保异常抛出将事务方法放到另一个Service类中调用。问题四静态资源如图片访问404。排查Spring Boot默认从classpath:/static/,/public/等目录提供静态资源。解决将图片放在src/main/resources/static/images/下访问路径为http://localhost:8080/images/xxx.jpg。如果自定义了上传路径需要在配置中添加资源映射Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/images/**) .addResourceLocations(file:/path/to/your/upload/dir/); } }问题五订单超卖问题。现象多人同时下单同一库存为1的商品结果都成功了。根本原因我们之前代码中的“查询库存 - 内存计算 - 更新库存”不是原子操作。解决方案进阶悲观锁在查询库存时使用SELECT ... FOR UPDATE锁定该行数据性能差。乐观锁在dish表增加一个version字段。更新时UPDATE dish SET stockstock-1, versionversion1 WHERE id#{id} AND version#{oldVersion}。如果更新条数为0说明版本号变了重试或提示失败。Redis分布式锁在扣减库存前用Redis的setnx命令对商品ID加锁。Redis原子操作将库存数量放在Redis中使用DECR命令原子扣减扣到负数则返回失败。这个基于JavaWeb的在线点餐系统虽然是一个经典的毕业设计题目但它所蕴含的软件工程思想、技术选型权衡、数据库设计技巧和业务逻辑复杂性是每一个后端开发者成长路上必须经历的。我建议你在实现基本功能后不妨挑战一下自己尝试改用Spring Boot Vue前后端分离架构引入Redis缓存菜品信息甚至模拟一下高并发下的秒杀场景。这些探索会让你对“如何做一个好用的系统”有更深的理解。最后记得写好代码注释整理一份清晰的README.md这对任何项目来说都是加分项。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价