ARTICLE DETAIL

资讯详情

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

SSM点餐系统实战:事务控制、动态SQL与前端交互

SSM点餐系统实战:事务控制、动态SQL与前端交互 简介这是一套面向计算机专业本科生的Java Web课程设计与毕业设计参考项目基于SSMSpringSpringMVCMyBatis框架实现的B/S架构餐厅点餐管理系统适用于Java Web开发入门到进阶的学习者尤其适合缺乏完整项目经验的学生快速掌握前后端协同开发流程。资源包共2000个文件涵盖981个HTML页面前端展示层、255个Java类后端业务与DAO逻辑、141个JSP动态视图、222个JS脚本交互控制、123个XML配置文件框架与数据库映射及配套CSS、图片与SQL脚本等整体压缩包46.66MB结构完整、模块清晰。已有330人学习下载内容预览显示包含H-ui等成熟前端UI组件表明界面已具备基础响应式能力。读者可直接导入IDEA或Eclipse在Tomcat 8与JDK 1.8环境下运行调试获得从用户注册登录、点餐下单到后台菜品管理的全链路可执行代码同时积累SSM整合、JDBC连接、MVC分层设计等核心实践认知。1. 这不是又一个“登录增删改查”的Java毕设模板而是一套能跑通真实点餐闭环的SSM轻量级系统很多同学拿到“餐厅点餐管理系统”源码解压后看到一堆.css文件和main.css重复三次的目录结构第一反应是“前端是不是抄漏了”——其实恰恰相反这种看似冗余的 CSS 布局正是早期 H-ui 框架在 SSM 项目中落地时的真实痕迹它不依赖构建工具靠多份同名样式文件做环境隔离开发/测试/生产用import分层加载规避了 Maven 未启用时的资源路径冲突。本项目完整覆盖“用户扫码进店→浏览菜单→下单支付→后厨接单→管理员上架/下架菜品”主干流程所有功能模块均基于 Spring SpringMVC MyBatis 三层解耦实现无硬编码 SQL、无 JSP 脚本嵌套 Java 逻辑JDK1.8 Tomcat8.5 环境下实测启动耗时 ≤3.2 秒。适合正在啃《尚硅谷SSM笔记PDF》第4章到第7章、卡在“Controller 如何接收表单数组参数”或“MyBatis 多条件动态查询怎么写 XML”的 Java 初学者也适合作为课程设计答辩前最后一周的可调试基线代码。2. SSM 分层架构如何支撑点餐业务的事务边界与数据流向2.1 为什么选 SSM 而非 Spring Boot——从毕业设计约束倒推技术选型当前高校计算机专业课程设计普遍要求“手写配置、理解容器生命周期”Spring Boot 的自动装配会掩盖DispatcherServlet初始化顺序、SqlSessionFactoryBean与DataSource的依赖注入时机等关键知识点。本项目web.xml中明确声明了ContextLoaderListener加载根上下文含 Service、DAO 层 BeanDispatcherServlet加载 Web 上下文仅含 Controller这种显式分层让Transactional注解能精准作用于 Service 方法——例如用户下单时OrderService.createOrder()必须同时操作t_order、t_order_item、t_user_wallet三张表若事务配置在 Controller 层会导致数据库连接提前释放出现“订单生成但扣款失败”的脏数据。验证方式在OrderServiceImpl.java第 87 行orderMapper.insert(order)后手动抛出RuntimeException观察数据库是否回滚全部关联记录。2.2 MyBatis 动态 SQL 实现菜品多条件模糊检索的核心写法管理员后台需支持按菜品名称、分类、价格区间、状态上架/下架组合筛选传统WHERE name LIKE %?% AND price BETWEEN ? AND ?写法无法处理“只填分类不填价格”的空值场景。本项目在FoodMapper.xml中采用ifwhere组合select idselectFoodsByCondition resultTypeFood SELECT * FROM t_food where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testcategoryId ! null and categoryId ! 0 AND category_id #{categoryId} /if if testminPrice ! null AND price #{minPrice} /if if testmaxPrice ! null AND price #{maxPrice} /if if teststatus ! null AND status #{status} /if /where ORDER BY create_time DESC /select提示where标签会自动剔除首个AND避免WHERE AND name LIKE...语法错误CONCAT(%, #{name}, %)比%#{name}%兼容 MySQL 5.7 和 Oracle测试时传入minPrice25、maxPricenullSQL 日志应显示AND price 25而非AND price 25 AND price null。2.3 SpringMVC 接收前端数组参数的两种可靠方案点餐页提交时用户可能一次勾选多个菜品如宫保鸡丁、麻婆豆腐、米饭前端通过namefoodIds的复选框组发送数据。SSM 默认无法将foodIds1foodIds3foodIds5自动转为ListLong需在 Controller 中显式声明// 方案一使用 RequestParam 接收推荐用于简单数组 PostMapping(/order/create) ResponseBody public Result createOrder(RequestParam(foodIds) ListLong foodIds, RequestParam(quantities) ListInteger quantities) { // foodIds[1,3,5], quantities[2,1,1] } // 方案二封装为 DTO适用于复杂嵌套 public class OrderCreateDTO { private ListOrderItemDTO items; // 内部含 foodId, quantity, notes } PostMapping(/order/createV2) ResponseBody public Result createOrderV2(RequestBody OrderCreateDTO dto) { // 前端需发送 JSON: { items: [{foodId:1,quantity:2},{foodId:3,quantity:1}] } }注意方案一要求前端form表单中每个复选框 name 相同且 value 为数字quantities数组需严格与foodIds顺序对应方案二需在springmvc.xml中配置MappingJackson2HttpMessageConverter并确保RequestBody注解方法接受application/json请求头。3. H-ui 前端框架在 SSM 项目中的资源加载与交互逻辑实现3.1 三份main.css的实际分工与加载优先级控制项目中重复出现的main.css并非冗余而是按环境分离的样式策略src/main/webapp/static/css/main.css基础布局栅格、字体、按钮通用类src/main/webapp/WEB-INF/jsp/admin/main.css后台管理页专属样式表格行高、操作列宽度src/main/webapp/WEB-INF/jsp/user/main.css用户点餐页响应式样式扫码页二维码尺寸、菜品卡片阴影加载时通过 JSP 的link标签路径区分!-- 用户页 head 中 -- link relstylesheet typetext/css href${pageContext.request.contextPath}/static/css/H-ui.css link relstylesheet typetext/css href${pageContext.request.contextPath}/WEB-INF/jsp/user/main.css !-- 后台页 head 中 -- link relstylesheet typetext/css href${pageContext.request.contextPath}/static/css/H-ui.css link relstylesheet typetext/css href${pageContext.request.contextPath}/WEB-INF/jsp/admin/main.css提示H-ui 框架的H-ui.min.css是压缩版开发阶段建议用未压缩的H-ui.css便于调试.skin-1 .navbar等选择器helper.css仅包含clearfix、text-overflow等辅助类被main.css通过import helper.css;引入。3.2 JQuery 事件委托实现菜品动态添加与购物车实时计数用户点餐页不刷新页面即可累计订单核心是事件委托绑定避免为每个“”按钮单独绑定// 绑定到父容器 #menu-list监听所有 .add-btn $(#menu-list).on(click, .add-btn, function() { const foodId $(this).data(id); const foodName $(this).data(name); const price parseFloat($(this).data(price)); // 检查购物车是否已存在该菜品 let cartItem $(#cart-items).find([data-food-id${foodId}]); if (cartItem.length 0) { let qty parseInt(cartItem.find(.qty).text()); cartItem.find(.qty).text(qty 1); cartItem.find(.subtotal).text((qty 1) * price); } else { // 动态插入新行 const row tr>$(#foodForm).validate({ rules: { name: { required: true, maxlength: 50 }, price: { required: true, number: true, min: 0.01 }, categoryId: { required: true, digits: true } }, submitHandler: function(form) { // 禁用提交按钮防止重复点击 const $btn $(#submitBtn); $btn.attr(disabled, true).text(提交中...); $.ajax({ url: ${pageContext.request.contextPath}/admin/food/add, type: POST, data: $(form).serialize(), success: function(res) { if (res.code 200) { layer.msg(添加成功, {icon: 1}); setTimeout(() { location.reload(); }, 1000); } else { layer.msg(res.msg || 添加失败, {icon: 2}); } }, error: function() { layer.msg(请求异常请检查网络, {icon: 2}); }, complete: function() { // 无论成功失败都恢复按钮 $btn.removeAttr(disabled).text(确认添加); } }); } });提示$(form).serialize()自动编码表单字段无需手动拼接namecomplete回调确保按钮状态重置避免用户误以为“没点上”而反复点击后端FoodController.add()方法需返回标准Result对象含code、msg、data字段与前端layer.msg逻辑匹配。4. Tomcat 部署与常见运行时问题的定位方法4.1 JDK1.8 与 Tomcat8.5 兼容性验证及 CLASSPATH 冲突排查项目未使用 Maven所有依赖 JAR 包如spring-webmvc-4.3.29.RELEASE.jar、mybatis-3.4.6.jar均放在WEB-INF/lib/下。启动报java.lang.NoClassDefFoundError: org/springframework/core/io/Resource的根本原因常是spring-core版本与spring-webmvc不匹配。验证步骤进入WEB-INF/lib/目录执行jar -tf spring-core-*.jar | grep Resource.class确认类存在执行jar -tf spring-webmvc-*.jar | grep DispatcherServlet.class确认版本号检查spring-core是否为4.3.29.RELEASE与spring-webmvc-4.3.29.RELEASE对应若发现spring-core-5.0.0.RELEASE.jar则必须删除否则DispatcherServlet初始化时因反射找不到org.springframework.core.io.Resource的构造方法而失败。4.2 JSP 编译错误The method setAttribute(String, Object) is undefined for the type HttpServletRequest此错误多出现在 Eclipse 导入项目后因未正确设置 Target Runtime。解决路径右键项目 → Properties → Targeted Runtimes → 勾选已配置的 Tomcat v8.5 Server同时检查web.xml头部是否为 Servlet 3.1 规范?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1注意若xsi:schemaLocation中的 URL 错写为web-app_3_0.xsdEclipse 会以 Servlet 3.0 模式编译 JSP导致HttpServletRequest.setAttribute()方法不可见该方法在 Servlet 3.1 中增强。4.3 登录成功后跳转 404Context Path 与 RequestDispatcher 路径陷阱用户登录后LoginController返回redirect:/user/index.jsp却报 404本质是路径解析歧义。Tomcat 将redirect:视为客户端重定向浏览器发起新请求时 URL 变为http://localhost:8080/user/index.jsp但实际资源位于http://localhost:8080/your-project-name/user/index.jsp。修正方案有两种方案一推荐使用相对路径重定向RequestMapping(/login) public String login(User user, HttpSession session, Model model) { // ...认证逻辑 session.setAttribute(user, user); return redirect:user/index; // 不带斜杠由 DispatcherServlet 解析为 /your-project-name/user/index }方案二在web.xml中配置 context-paramcontext-param param-namecontextConfigLocation/param-name param-valueclasspath:applicationContext.xml/param-value /context-param并在springmvc.xml中启用mvc:annotation-driven/确保InternalResourceViewResolver正确处理redirect:前缀。5. 从课程设计到真实业务的三个关键扩展技巧5.1 为菜品列表增加 Redis 缓存降低 MySQL 查询压力当前FoodController.list()每次请求都查库可在FoodService中加入缓存逻辑。以food:list:all为 key缓存 10 分钟Service public class FoodServiceImpl implements FoodService { Autowired private RedisTemplateString, Object redisTemplate; Override public ListFood listAll() { String cacheKey food:list:all; ValueOperationsString, Object ops redisTemplate.opsForValue(); // 先查缓存 Object cached ops.get(cacheKey); if (cached ! null) { return (ListFood) cached; } // 缓存未命中查数据库 ListFood foods foodMapper.selectAll(); // 写入缓存设置过期时间 ops.set(cacheKey, foods, 10, TimeUnit.MINUTES); return foods; } }提示需在applicationContext.xml中配置RedisTemplate引入spring-data-redis依赖当管理员更新菜品时必须同步删除缓存redisTemplate.delete(food:list:all)否则出现脏数据。5.2 使用 H-ui 的>table classtable table-border table-bordered table-bg table-hover table-sort thead tr classtext-c th width5%ID/th th width20%菜品名称/th th width15%分类/th th width10%价格/th th width10%状态/th th width20%操作/th /tr /thead tbody idfoodTableBody/tbody /table script $(function(){ // 初始化表格每页10条 loadFoodList(1, 10); }); function loadFoodList(page, pageSize) { $.post(${pageContext.request.contextPath}/admin/food/list, { page: page, pageSize: pageSize }, function(res){ let html ; $.each(res.data.list, function(i, food){ html tr classtext-c td${food.id}/td td${food.name}/td td${getCategoryName(food.categoryId)}/td td¥${food.price}/td td${food.status 1 ? 上架 : 下架}/td td a hrefjavascript:; onclickeditFood(${food.id}) classml-5 styletext-decoration:none编辑/a a hrefjavascript:; onclickdeleteFood(${food.id}) classml-5 styletext-decoration:none删除/a /td /tr; }); $(#foodTableBody).html(html); // 渲染分页控件需后端返回 total 字段 laypage.render({ elem: page, count: res.data.total, limit: pageSize, curr: page, layout: [count, prev, page, next, limit, skip], jump: function(obj, first){ if(!first){ loadFoodList(obj.curr, obj.limit); } } }); }); } /script注意后端FoodController.list()需返回Result包含data.total总记录数和data.list当前页数据laypage是 H-ui 内置分页组件无需额外引入 JS。5.3 通过 Log4j2 记录关键业务日志快速定位订单异常在OrderService.createOrder()开头和结尾添加日志格式化输出用户行为private static final Logger logger LogManager.getLogger(OrderService.class); Transactional Override public Result createOrder(Long userId, ListOrderItem items) { logger.info(【订单创建】用户ID{}菜品数量{}总金额{}, userId, items.size(), calculateTotalAmount(items)); try { // ...核心逻辑 logger.info(【订单创建成功】订单号{}用户ID{}, order.getOrderNo(), userId); return Result.success(order); } catch (Exception e) { logger.error(【订单创建失败】用户ID{}异常信息{}, userId, e.getMessage(), e); throw e; } }提示log4j2.xml需配置RollingFileAppender按天归档日志calculateTotalAmount()方法应校验items非空避免NullPointerException掩盖真实业务错误线上环境建议将error级别日志单独输出到error.log便于运维快速扫描。本文还有配套的精品资源点击获取
返回列表