简介:这是一套面向Java初学者与毕业设计学生的完整共享厨房信息系统实战项目,基于SSM(Spring+SpringMVC+MyBatis)框架开发,聚焦校园或社区场景下的厨房资源预约、用户管理、订单调度等核心业务,助力课程设计、期末大作业及毕业设计高效落地。资源包共1214个文件,涵盖96个Java后端逻辑类、86个JSP前端页面、146个CSS样式与364个JS交互脚本,辅以2个SQL数据库脚本、Tomcat部署配置及Navicat可视化工具支持,整体压缩包仅15.45MB,结构清晰、注释详尽,新手可快速理解MVC分层设计与前后端协同逻辑。项目已通过严格调试,兼容MySQL 5.7、Tomcat 7.x/8.x及IDEA开发环境,附带Bootstrap响应式界面与ElementUI组件集成,开箱即用。
1. 项目概述:一个典型的Java Web毕业设计实战
最近在帮几个学弟学妹看他们的毕业设计,发现很多同学对“基于SSM+JSP+HTML的共享厨房信息系统”这类题目既熟悉又陌生。熟悉的是,SSM(Spring+SpringMVC+MyBatis)和JSP几乎是Java Web课程设计的“标配”组合;陌生的是,如何把这些技术点串联成一个逻辑完整、能跑起来、并且有点实际意义的系统。这个项目就是一个非常典型的案例,它模拟了一个线上共享厨房的预订与管理平台,用户可以通过网页查看厨房、预约时段,管理员则能管理厨房资源、处理订单。对于正在寻找Java毕业设计选题,或者想通过一个完整项目巩固SSM框架、前端基础(JSP/HTML)和数据库(MySQL)操作的同学来说,这是一个绝佳的练手素材。它不仅覆盖了从数据库设计到前后端交互的全流程,更关键的是,你能在其中遇到并解决那些教科书上不会写的“坑”,比如JSP页面在IDEA里的路径问题、MyBatis的复杂查询映射,以及如何让一个JSP页面看起来不那么“复古”。接下来,我就结合这个项目的源码和常见的开发流程,拆解一下它的核心实现思路、技术细节,以及那些我踩过并填平的坑。
2. 项目整体架构与技术选型解析
2.1 为什么是SSM+JSP+HTML?
看到这个技术栈,很多新手可能会问:现在不是都流行SpringBoot+Vue/React前后端分离了吗?为什么毕业设计还用SSM和JSP?这恰恰是这个项目作为学习范本的价值所在。
SSM框架的核心价值在于“理解MVC”。Spring Boot帮你做了太多自动配置,虽然高效,但容易让人变成“配置工程师”,而不清楚底层是如何工作的。而SSM组合需要你手动配置Spring的IoC容器、SpringMVC的DispatcherServlet、MyBatis的SqlSessionFactory。这个过程强迫你去理解:
- Spring:如何通过XML或注解管理Bean的生命周期和依赖注入。在这个共享厨房系统里,你的Service(如
KitchenService)、DAO(如OrderMapper)都是Spring容器中的Bean,它们之间的协作关系清晰可见。 - SpringMVC:一个请求(比如用户点击“预订”)是如何从
DispatcherServlet开始,经过HandlerMapping找到对应的Controller,Controller调用Service处理业务,最后通过ViewResolver解析JSP视图返回给浏览器的。这个流程是Web开发的基石。 - MyBatis:如何编写XML映射文件,将复杂的SQL查询结果映射成Java对象。例如,查询一个厨房的详细信息及其关联的可用时段,就需要用到
<resultMap>进行结果集映射。
JSP+HTML作为视图层,优势在于“快速上手和直观”。对于毕业设计而言,首要目标是演示完整的CRUD(增删改查)功能和业务逻辑。JSP允许你在HTML中直接嵌入Java代码(虽然不推荐大量使用)或JSTL标签,这使得数据展示(如循环列出所有厨房)变得非常直接。它避免了初学者同时学习前端框架(如Vue)和后端API接口定义的认知负担,让你可以专注于业务逻辑的实现。当然,我们会在实现时尽量遵循最佳实践,比如使用JSTL和EL表达式代替<% ... %>脚本片段,保持页面的可维护性。
技术栈的经典性与实用性:SSM是国内众多传统企业和遗留系统仍在广泛使用的技术栈,掌握它对于理解企业级Java开发、阅读老代码非常有帮助。用这个技术栈完成一个功能完备的系统,能很好地证明你具备了扎实的Java Web开发基础。
2.2 共享厨房系统的核心业务模块拆解
在动手敲代码之前,我们必须把系统要做什么搞清楚。这个共享厨房信息系统,本质上是一个小型的资源预订与管理系统。我们可以将其核心业务分解为以下几个模块:
用户端功能模块:
- 用户注册与登录:基础功能,涉及密码加密(通常用MD5或Spring Security)、会话管理(Session)。
- 厨房信息浏览:列表展示、详情查看(包括图片、设施、价格等)。
- 厨房预订:核心功能。用户选择厨房、选择日期和时段、提交订单。这里涉及复杂的业务逻辑校验,如时段冲突检查、库存(可预订量)扣减。
- 个人中心:查看我的订单(不同状态:待支付、已确认、已完成、已取消)、修改个人信息。
管理端功能模块:
- 厨房资源管理:对厨房信息进行增删改查(CRUD)。
- 订单管理:查看所有订单,进行确认、取消等操作。
- 用户管理:管理注册用户。
- 数据统计:简单的数据看板,如每日订单量、热门厨房等。
系统支撑模块:
- 数据库设计:需要设计
user(用户)、kitchen(厨房)、order(订单)、kitchen_schedule(厨房排班表)等核心表。 - 权限控制:区分普通用户和管理员,通常通过Session中存储的用户角色字段来实现,在Controller或拦截器中进行校验。
- 数据库设计:需要设计
这个业务模型虽然不复杂,但涵盖了Web系统最常见的操作模式,是练习分层架构(Controller-Service-Dao)和数据库设计的完美沙盒。
3. 数据库设计与MyBatis映射实战
3.1 核心表结构设计思路
数据库设计是项目的基石,设计得好,后续编码事半功倍。针对共享厨房系统,这里给出一个经过简化的核心表结构设计,它已经能支撑主要业务:
-- 用户表 CREATE TABLE `t_user` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(50) UNIQUE NOT NULL COMMENT '用户名', `password` VARCHAR(255) NOT NULL COMMENT '加密后的密码', `phone` VARCHAR(20) COMMENT '手机号', `email` VARCHAR(100) COMMENT '邮箱', `role` INT DEFAULT 0 COMMENT '角色:0-普通用户,1-管理员', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT='用户表'; -- 厨房信息表 CREATE TABLE `t_kitchen` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(100) NOT NULL COMMENT '厨房名称', `description` TEXT COMMENT '描述', `address` VARCHAR(255) COMMENT '地址', `price_per_hour` DECIMAL(10,2) NOT NULL COMMENT '每小时价格', `capacity` INT COMMENT '可容纳人数', `main_image` VARCHAR(255) COMMENT '主图URL', `status` INT DEFAULT 1 COMMENT '状态:1-可用,0-不可用', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT='厨房信息表'; -- 订单表(核心) CREATE TABLE `t_order` ( `id` VARCHAR(32) PRIMARY KEY COMMENT '订单号,可使用UUID生成', `user_id` INT NOT NULL COMMENT '用户ID', `kitchen_id` INT NOT NULL COMMENT '厨房ID', `booking_date` DATE NOT NULL COMMENT '预订日期', `start_time` TIME NOT NULL COMMENT '开始时间', `end_time` TIME NOT NULL COMMENT '结束时间', `total_hours` INT NOT NULL COMMENT '总小时数', `total_amount` DECIMAL(10,2) NOT NULL COMMENT '总金额', `status` INT DEFAULT 0 COMMENT '状态:0-待支付,1-已支付/待使用,2-已完成,3-已取消', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (`user_id`) REFERENCES `t_user`(`id`), FOREIGN KEY (`kitchen_id`) REFERENCES `t_kitchen`(`id`) ) COMMENT='订单表'; -- 厨房日程表(用于管理库存和冲突检查,可选但推荐) CREATE TABLE `t_kitchen_schedule` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `kitchen_id` INT NOT NULL, `schedule_date` DATE NOT NULL COMMENT '排班日期', `time_slot` VARCHAR(20) NOT NULL COMMENT '时段,如 09:00-11:00', `max_capacity` INT DEFAULT 1 COMMENT '该时段可接受的最大预订数', `booked_count` INT DEFAULT 0 COMMENT '已预订数', FOREIGN KEY (`kitchen_id`) REFERENCES `t_kitchen`(`id`), UNIQUE KEY `uk_kitchen_date_slot` (`kitchen_id`, `schedule_date`, `time_slot`) ) COMMENT='厨房日程库存表';设计要点解析:
- 订单号:不使用自增ID,而使用
VARCHAR类型的UUID或时间戳生成的唯一字符串,便于暴露给用户且更安全。 - 状态字段:使用
INT类型表示状态(如0,1,2,3),在Java代码中用枚举类(enum)与之对应,使代码更清晰。 - 厨房日程表:这是一个关键设计。如果直接通过遍历
t_order表来检查某个时段是否被预订,在数据量大时效率很低。t_kitchen_schedule表预先定义了每个厨房每天的可用时段和库存(max_capacity),下单时原子性地更新booked_count。这既高效地解决了并发预订冲突问题(通过数据库行锁或乐观锁),也方便管理库存。 - 金额字段:使用
DECIMAL(10,2)精确存储金额,避免浮点数精度问题。
3.2 MyBatis的进阶使用与踩坑点
在SSM中,MyBatis负责数据库交互。除了基本的增删改查,这个项目会用到一些更高级的特性。
1. 复杂结果映射(ResultMap): 在查询订单详情时,我们通常需要联表查询,获取订单对应的用户姓名、厨房名称等信息。这时就需要在XML映射文件中定义<resultMap>。
<!-- OrderMapper.xml --> <resultMap id="OrderDetailMap" type="com.sharedkitchen.model.Order"> <id property="id" column="order_id"/> <result property="bookingDate" column="booking_date"/> <result property="startTime" column="start_time"/> <!-- 关联厨房信息 --> <association property="kitchen" javaType="com.sharedkitchen.model.Kitchen"> <id property="id" column="kitchen_id"/> <result property="name" column="kitchen_name"/> <result property="pricePerHour" column="price_per_hour"/> </association> <!-- 关联用户信息 --> <association property="user" javaType="com.sharedkitchen.model.User"> <id property="id" column="user_id"/> <result property="username" column="username"/> <result property="phone" column="phone"/> </association> </resultMap> <select id="selectOrderDetailById" resultMap="OrderDetailMap"> SELECT o.id as order_id, o.booking_date, o.start_time, k.id as kitchen_id, k.name as kitchen_name, k.price_per_hour, u.id as user_id, u.username, u.phone FROM t_order o LEFT JOIN t_kitchen k ON o.kitchen_id = k.id LEFT JOIN t_user u ON o.user_id = u.id WHERE o.id = #{orderId} </select>注意:
<association>标签用于映射“一对一”关系。在定义resultMap时,确保column属性与SQL查询语句中的列别名完全一致,这是最常见的出错点之一。
2. 动态SQL: 在管理后台,我们经常需要根据多种条件(如厨房名称、状态、日期范围)组合查询订单。MyBatis的动态SQL标签(<if>,<where>,<choose>)就派上用场了。
<select id="selectOrderByCondition" parameterType="map" resultType="Order"> SELECT * FROM t_order <where> <if test="kitchenName != null and kitchenName != ''"> AND kitchen_id IN (SELECT id FROM t_kitchen WHERE name LIKE CONCAT('%', #{kitchenName}, '%')) </if> <if test="status != null"> AND status = #{status} </if> <if test="startDate != null"> AND booking_date >= #{startDate} </if> <if test="endDate != null"> AND booking_date <= #{endDate} </if> </where> ORDER BY create_time DESC </select>3. 事务管理: 预订厨房是一个典型的事务性操作:需要检查库存、创建订单、更新库存。这些步骤必须全部成功或全部失败。在Spring中,我们通过在Service层的方法上添加@Transactional注解来声明事务。
@Service public class OrderServiceImpl implements OrderService { @Autowired private KitchenScheduleMapper scheduleMapper; @Autowired private OrderMapper orderMapper; @Transactional(rollbackFor = Exception.class) // 发生任何异常都回滚 @Override public boolean placeOrder(Order order) { // 1. 检查并预占库存(乐观锁) KitchenSchedule schedule = scheduleMapper.selectForUpdate(order.getKitchenId(), order.getBookingDate(), order.getTimeSlot()); if (schedule == null || schedule.getBookedCount() >= schedule.getMaxCapacity()) { throw new RuntimeException("该时段已约满"); } int updateCount = scheduleMapper.increaseBookedCount(schedule.getId(), schedule.getBookedCount()); if (updateCount == 0) { // 乐观锁更新失败,说明并发冲突 throw new RuntimeException("预订冲突,请重试"); } // 2. 创建订单 orderMapper.insert(order); // 3. 其他业务逻辑... return true; } }实操心得:
@Transactional默认只对RuntimeException和Error回滚,对于检查型异常(Exception)不回滚。因此,像上面这样明确指定rollbackFor = Exception.class是个好习惯。另外,事务方法要避免自调用(即同一个类里一个非事务方法调用事务方法),因为这会绕过Spring的AOP代理导致事务失效。
4. Spring与SpringMVC核心配置与业务逻辑实现
4.1 Spring容器的配置与Bean管理
在非Spring Boot的SSM项目中,我们需要通过XML文件(如applicationContext.xml)来配置Spring容器。核心配置包括:
- 组件扫描:让Spring自动发现并注册带有
@Controller,@Service,@Repository,@Component注解的类。<context:component-scan base-package="com.sharedkitchen"/> - 数据源与事务管理:配置数据库连接池(如Druid、HikariCP)和声明式事务管理器。
<!-- 数据源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <!-- ... 其他配置 --> </bean> <!-- 事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 开启注解驱动的事务管理 --> <tx:annotation-driven transaction-manager="transactionManager"/> - MyBatis集成:配置
SqlSessionFactoryBean,指定数据源和Mapper XML文件的位置。<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> <property name="typeAliasesPackage" value="com.sharedkitchen.model"/> </bean> <!-- Mapper接口扫描 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.sharedkitchen.dao"/> </bean>
4.2 SpringMVC控制器(Controller)的编写要点
Controller是处理HTTP请求的入口。在共享厨房系统中,一个典型的订单控制器可能如下所示:
@Controller @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; @Autowired private KitchenService kitchenService; // 跳转到预订页面,需要传入厨房ID @GetMapping("/booking/page") public String bookingPage(@RequestParam("kitchenId") Integer kitchenId, Model model) { Kitchen kitchen = kitchenService.getKitchenById(kitchenId); if (kitchen == null) { return "redirect:/kitchen/list"; // 厨房不存在,重定向到列表页 } model.addAttribute("kitchen", kitchen); // 假设这里还查询了该厨房的可用时段 List<String> availableSlots = kitchenService.getAvailableSlots(kitchenId, LocalDate.now()); model.addAttribute("slots", availableSlots); return "order/booking"; // 对应 /WEB-INF/views/order/booking.jsp } // 处理提交预订的POST请求 @PostMapping("/submit") @ResponseBody // 返回JSON数据 public Map<String, Object> submitOrder(@RequestBody OrderForm orderForm, HttpSession session) { Map<String, Object> result = new HashMap<>(); // 1. 从Session获取当前登录用户 User currentUser = (User) session.getAttribute("currentUser"); if (currentUser == null) { result.put("success", false); result.put("message", "用户未登录"); return result; } // 2. 表单数据校验(省略) // 3. 调用Service层创建订单 try { orderService.placeOrder(orderForm, currentUser.getId()); result.put("success", true); result.put("message", "预订成功!"); } catch (Exception e) { result.put("success", false); result.put("message", "预订失败:" + e.getMessage()); } return result; } // 用户查看自己的订单列表 @GetMapping("/myList") public String myOrderList(HttpSession session, Model model) { User currentUser = (User) session.getAttribute("currentUser"); List<Order> orderList = orderService.getOrdersByUserId(currentUser.getId()); model.addAttribute("orderList", orderList); return "order/myList"; } }关键点解析:
@RequestMapping:定义类级别的请求路径前缀。@GetMapping/@PostMapping:更细粒度的HTTP方法映射。@RequestParam:获取查询参数。@RequestBody:用于接收前端以JSON格式发送的POST请求体,并自动绑定到Java对象。这要求前端设置Content-Type: application/json。@ResponseBody:将方法返回值直接序列化为JSON写入HTTP响应体,用于AJAX交互。Model对象:用于向视图(JSP)传递数据。HttpSession:用于获取和设置会话信息,如登录用户。- 返回值:返回
String类型通常表示视图名称,由配置的ViewResolver解析为具体的JSP文件路径。返回其他类型(如Map)并配合@ResponseBody则用于API接口。
4.3 业务逻辑层(Service)的设计模式
Service层是业务逻辑的核心,它协调多个DAO(Mapper)操作,并封装事务。一个好的Service设计应该职责清晰。
public interface OrderService { boolean placeOrder(OrderForm orderForm, Integer userId); PageInfo<Order> getOrdersByCondition(OrderQuery query, Integer pageNum, Integer pageSize); Order getOrderDetail(String orderId); boolean cancelOrder(String orderId, Integer userId); } @Service public class OrderServiceImpl implements OrderService { @Autowired private OrderMapper orderMapper; @Autowired private KitchenScheduleMapper scheduleMapper; @Autowired private KitchenMapper kitchenMapper; @Transactional @Override public boolean placeOrder(OrderForm form, Integer userId) { // 1. 参数校验 validateOrderForm(form); // 2. 库存/冲突检查(使用厨房日程表) KitchenSchedule schedule = scheduleMapper.selectForBooking(form.getKitchenId(), form.getBookingDate(), form.getTimeSlot()); // 3. 计算金额 Kitchen kitchen = kitchenMapper.selectById(form.getKitchenId()); BigDecimal totalAmount = kitchen.getPricePerHour().multiply(new BigDecimal(form.getTotalHours())); // 4. 构建订单实体 Order order = buildOrder(form, userId, totalAmount); // 5. 保存订单(这里会触发MyBatis的插入,并获取生成的主键,如果使用) orderMapper.insert(order); // 6. 更新库存 scheduleMapper.updateBookedCount(schedule.getId(), schedule.getBookedCount() + 1); // 7. 其他后续操作(如发送通知,可以异步处理) // asyncNotificationService.sendBookingSuccessNotification(userId, order.getId()); return true; } // ... 其他方法实现 }设计模式应用:在复杂的业务中,可以考虑使用策略模式来处理不同的订单状态流转,或者使用工厂模式来创建不同的通知对象。但对于毕业设计级别的项目,保持清晰的分层和单一职责原则已经足够。
5. 前端视图层:JSP与HTML的现代化实践
5.1 告别Scriptlet:使用JSTL和EL表达式
早期的JSP中充斥着<% ... %>这样的Java脚本片段(Scriptlet),这严重破坏了HTML的结构,且难以维护。现代JSP开发强烈推荐使用JSTL(JSP Standard Tag Library)标签和EL(Expression Language)表达式。
首先,需要在JSP页面头部引入JSTL核心标签库:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>然后,在页面中就可以这样使用:
<!-- 使用EL表达式获取Model中的数据 --> <h2>厨房详情:${kitchen.name}</h2> <p>价格:<fmt:formatNumber value="${kitchen.pricePerHour}" type="currency"/>/小时</p> <!-- 使用JSTL的c:if进行条件判断 --> <c:if test="${kitchen.status == 1}"> <span class="badge badge-success">可预订</span> </c:if> <c:if test="${kitchen.status != 1}"> <span class="badge badge-secondary">暂不可用</span> </c:if> <!-- 使用JSTL的c:forEach进行循环遍历 --> <table class="table"> <thead><tr><th>时段</th><th>状态</th><th>操作</th></tr></thead> <tbody> <c:forEach items="${availableSlots}" var="slot" varStatus="loop"> <tr> <td>${slot.timeRange}</td> <td> <c:choose> <c:when test="${slot.bookedCount < slot.maxCapacity}">可预订 (剩余${slot.maxCapacity - slot.bookedCount})</c:when> <c:otherwise>已约满</c:otherwise> </c:choose> </td> <td> <button class="btn btn-primary btn-sm" onclick="selectSlot('${slot.timeRange}')" <c:if test="${slot.bookedCount >= slot.maxCapacity}">disabled</c:if>>选择</button> </td> </tr> </c:forEach> </tbody> </table>这样写出来的JSP页面,结构清晰,几乎看不到Java代码,更接近纯粹的HTML,可读性和可维护性大大提升。
5.2 集成前端框架(如Bootstrap)与AJAX交互
为了让JSP页面看起来更美观、交互更流畅,集成Bootstrap这样的前端CSS框架是标准操作。同时,对于表单提交、数据加载等操作,使用AJAX可以避免页面刷新,提升用户体验。
1. 引入Bootstrap: 在JSP的<head>中通过CDN引入Bootstrap的CSS和JS。
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>2. 使用AJAX提交表单: 在预订页面,当用户点击“提交订单”时,我们使用JavaScript(或jQuery)发起AJAX请求。
<!-- booking.jsp 中的表单部分 --> <form id="bookingForm"> <input type="hidden" name="kitchenId" value="${kitchen.id}"/> <div class="mb-3"> <label for="bookingDate" class="form-label">预订日期</label> <input type="date" class="form-control" id="bookingDate" name="bookingDate" required> </div> <!-- 其他表单项 --> <button type="button" class="btn btn-primary" onclick="submitBooking()">提交预订</button> </form> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script> function submitBooking() { var formData = { kitchenId: ${kitchen.id}, bookingDate: $('#bookingDate').val(), timeSlot: $('#timeSlot').val(), // ... 其他字段 }; $.ajax({ url: '${pageContext.request.contextPath}/order/submit', type: 'POST', contentType: 'application/json', // 重要:告诉服务器发送的是JSON data: JSON.stringify(formData), dataType: 'json', success: function(response) { if (response.success) { alert(response.message); // 跳转到我的订单页面 window.location.href = '${pageContext.request.contextPath}/order/myList'; } else { alert('失败:' + response.message); } }, error: function(xhr) { alert('请求出错,状态码:' + xhr.status); } }); } </script>对应的Controller方法就是前面提到的submitOrder,它使用@RequestBody接收JSON并返回JSON。
5.3 解决JSP在IDEA中的常见问题
很多同学在用IntelliJ IDEA开发JSP时会遇到问题,这里集中说一下:
问题1:JSP页面语法不高亮或报错。
- 解决:确保项目模块的
Facets中包含了Web支持,并且Web Resource Directories指向了正确的目录(通常是src/main/webapp)。在File -> Project Structure -> Modules -> YourModule -> Sources下,将webapp目录标记为Resources。
- 解决:确保项目模块的
问题2:
<%@ taglib %>或${}EL表达式报红,提示无法解析。- 解决:这通常是IDEA的智能感知问题。确保项目的
Artifacts输出包含了WEB-INF/lib目录下的所有jar包(特别是jstl.jar和standard.jar)。更简单的办法是,在pom.xml(如果使用Maven)中正确引入依赖后,点击IDEA右侧Maven工具栏的Reimport。有时重启IDEA也能解决。
- 解决:这通常是IDEA的智能感知问题。确保项目的
问题3:JSP页面中的函数点击无法跳转到定义。
- 解决:这是IDEA对JSP内JavaScript支持的一个小问题。通常不影响运行。可以尝试在
Settings -> Editor -> Language Injections中检查,或者直接使用外部.js文件来管理JavaScript代码,这是更推荐的做法。
- 解决:这是IDEA对JSP内JavaScript支持的一个小问题。通常不影响运行。可以尝试在
问题4:运行后访问JSP出现404或500错误。
- 排查:
- 检查
web.xml中配置的DispatcherServlet的url-pattern是否正确(通常是/)。 - 检查SpringMVC的视图解析器配置。典型的配置如下,它会把Controller返回的
"order/booking"解析为/WEB-INF/views/order/booking.jsp。<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean> - 确保JSP文件放在了正确的目录下(如
/WEB-INF/views/),因为/WEB-INF/下的内容不能被客户端直接访问,增加了安全性。 - 查看Tomcat日志或IDEA控制台输出的具体错误信息,这是最直接的排错方式。
- 检查
- 排查:
6. 项目部署、测试与常见问题排查
6.1 本地开发环境搭建与项目运行
环境准备:
- JDK:安装JDK 8或11,配置
JAVA_HOME环境变量。 - Maven:安装Maven,用于管理项目依赖(如果项目是Maven项目)。将源码中的
pom.xml文件放入项目根目录。 - IDE:IntelliJ IDEA或Eclipse。IDEA对Maven和Spring的支持更好。
- 数据库:安装MySQL,创建数据库(如
shared_kitchen),并执行提供的SQL脚本初始化表结构。
- JDK:安装JDK 8或11,配置
导入项目:
- 在IDEA中,选择
Open,找到包含pom.xml的文件夹,以Maven项目形式打开。 - IDEA会自动下载
pom.xml中定义的依赖(Spring、MyBatis、MySQL驱动、JSTL等)。
- 在IDEA中,选择
配置数据库连接:
- 修改
src/main/resources目录下的jdbc.properties文件,填写正确的数据库URL、用户名和密码。 - 检查
applicationContext.xml中数据源配置是否引用了这个属性文件。
- 修改
配置Tomcat:
- 在IDEA中,点击
Add Configuration,添加一个Tomcat Server -> Local。 - 在
Deployment选项卡中,添加一个Artifact,选择你的项目生成的war包或war exploded(推荐后者,支持热更新)。 - 将
Application context设置为/或你想要的路径(如/sharedkitchen)。
- 在IDEA中,点击
运行与访问:
- 启动Tomcat,在浏览器访问
http://localhost:8080/(或你设置的上下文路径)。 - 通常需要一个首页或登录页的入口Controller,例如在
HomeController中定义一个映射到/的方法,返回index.jsp。
- 启动Tomcat,在浏览器访问
6.2 功能测试要点与边界情况
测试不能只走“阳光大道”,要专门去踩那些容易出错的“边界”。
用户相关:
- 注册时,用户名已存在、密码强度、手机号/邮箱格式校验。
- 登录时,密码错误、用户不存在、验证码(如果有时)错误。
- 会话超时后,操作是否跳转到登录页。
厨房预订(核心):
- 冲突预订:同时打开两个浏览器标签,用同一用户预订同一厨房的相同时段,系统是否正确处理(应只有一个成功)。这考验你的库存检查逻辑(乐观锁)。
- 库存超卖:
max_capacity为1,已经有一个订单,第二个用户是否无法预订。 - 非法日期/时段:选择过去的日期、非法的时段格式。
- 数据一致性:下单成功后,检查
t_order表记录是否正确,t_kitchen_schedule表的booked_count是否准确增加。
管理后台:
- 列表分页是否正确。
- 条件查询是否所有组合都工作正常。
- 删除厨房时,如果该厨房已有历史订单,如何处理?(逻辑删除还是物理删除?外键约束?)
6.3 常见问题排查速查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 访问页面报404错误 | 1. URL路径错误。 2. DispatcherServlet的url-pattern配置错误。3. Controller的 @RequestMapping路径错误。4. 视图文件(JSP)不在视图解析器配置的路径下。 | 1. 检查浏览器地址栏URL。 2. 检查 web.xml中DispatcherServlet的<url-pattern>。3. 在Controller方法入口打调试断点,看是否进入。 4. 检查 InternalResourceViewResolver的prefix和suffix配置,以及JSP文件实际位置。 |
| 页面报500错误,控制台有异常栈 | 1. 空指针异常(NPE)。 2. SQL异常。 3. 类型转换异常。 4. 依赖缺失。 | 1.仔细阅读控制台异常信息,第一行通常指明了错误类和行号。 2. 检查Service或Controller中对象是否为 null。3. 检查MyBatis的SQL语句和参数映射。 4. 检查 pom.xml依赖是否完整,特别是数据库驱动、连接池、JSTL。 |
JSP页面显示${user.name}原样字符串,而不是值 | EL表达式未被解析。 | 1. 确保JSP页面开头有<%@ page isELIgnored="false" %>(旧项目可能需要)。2. 确保web.xml的版本是2.4以上,且未设置 <el-ignored>true</el-ignored>。3. 检查是否引入了正确的 jstl依赖。 |
| 插入中文到数据库后变成乱码 | 数据库字符集不匹配。 | 1. 确保数据库、表、字段的字符集为utf8mb4。2. 在JDBC连接URL中添加参数: jdbc:mysql://localhost:3306/db?useUnicode=true&characterEncoding=UTF-8。3. 检查Tomcat的 server.xml中Connector是否配置了URIEncoding="UTF-8"。 |
| 静态资源(CSS, JS, 图片)无法加载 | SpringMVC的DispatcherServlet拦截了/,把静态资源请求也当成了Controller请求。 | 在SpringMVC配置文件中(如spring-mvc.xml)添加静态资源映射:<mvc:resources mapping="/static/**" location="/static/"/>并将静态文件放在 webapp/static/目录下。 |
| 事务不生效,部分失败未回滚 | 1. 方法不是public的。2. 异常类型不是 RuntimeException且未指定rollbackFor。3. 在同一个类中,非事务方法调用了事务方法。 | 1. 确保@Transactional注解的方法为public。2. 添加 @Transactional(rollbackFor = Exception.class)。3. 将事务方法抽到另一个Service中,或使用 AopContext.currentProxy()进行自调用。 |
这个基于SSM+JSP+HTML的共享厨房信息系统,麻雀虽小五脏俱全。完成它,你不仅能得到一个可以写在简历上的毕业设计,更重要的是走通了一个Java Web应用从数据库设计、后端逻辑到前端展示的完整闭环。过程中遇到的每一个错误和解决的每一个问题,都是宝贵的经验。最后,记得在编码时多写注释,在关键业务方法(如预订、支付)做好日志记录,这无论是在调试阶段还是后续维护中,都能让你省下大量时间。
本文还有配套的精品资源,点击获取