简介:一份基于 SSM+JSP 的母婴用品网站毕业设计项目,面向 Java 方向本科生和课程设计学习者,解决从零搭建可运行前后端项目的问题。压缩包约 25.45MB,内含 1371 个文件,其中 jsp/HTML 用于页面展示,Java 文件对应控制器与业务逻辑,CSS/JS 与图片素材支撑前端界面,SQL 脚本可直接初始化数据库,另有部署所需的 Maven/Tomcat 配置说明。项目功能完善、界面美观,代码中添加了注释,经严格调试确保可直接运行,适合作为毕设、期末大作业或课程设计的参考。目前已有 65 人学习下载,包含项目源码、数据库脚本及常用软件工具,能够帮助快速部署并理解 SSM 整合流程。
1. 基于SSM+JSP的母婴用品网站:先搞清楚这个包里装的到底是什么
在毕业设计答辩现场,“你的项目为什么不用Spring Boot?”几乎是基于SSM+JSP的母婴用品网站必被追问的一道题。这个选题是Java Web方向最经典的落地形态:Spring容器管理对象、SpringMVC分发请求、MyBatis完成数据库增删改查,最后用JSP把商品列表、购物车、订单这些页面渲染出来。它能解决的问题很明确——一套完整代码讲清“用户从注册到下单”的每一环,源码、数据库脚本、教程三件套齐全,适合正在做课程设计或毕业设计的学生对照复现,也适合想补SSM实战经验的开发者快速起步。接下来按技术拆解、工程搭建、业务实现、避坑排查一条条过。
2. SSM+JSP技术栈拆解:Spring、SpringMVC、MyBatis、JSP各自扛什么
2.1 Spring容器:把对象的创建和装配交出去
Spring在整个SSM项目里的定位很朴实:所有业务对象(Service)、控制器对象(Controller)、数据访问对象(Mapper)的生命周期都由Spring容器统一“托管”。我们不在代码里到处new UserServiceImpl(),而是写接口、写实现类、用注解或者XML声明依赖关系,容器启动时自动装配。母婴用品网站虽然规模不大,但用户、商品、购物车、订单四个模块之间有大量交叉引用,比如OrderService要调用GoodsService扣库存、还要调UserMapper查用户信息,这些依赖如果手写new,后期改一个构造函数全链路都要动。Spring把依赖关系收敛到配置文件或注解上,这是一个在答辩时值得展开讲的点:为什么要IoC,没有IoC会怎样。
IoC之外,Spring还负责声明式事务的管理。在这个项目里,订单创建、库存扣减是强事务场景,几个表的数据必须同时成功或同时失败。这种需求只需要在Service方法上加一个@Transactional注解,容器就会自动开启事务、提交或回滚。对比一下JDBC时代要手动connection.setAutoCommit(false)、再在catch块里connection.rollback()的写法,Spring把这一层复杂度完全收走了。这也是SSM项目里Service层为什么必不可少的原因——事务边界通常划在Service方法上,Controller里的事务控制是不规范的。
2.2 SpringMVC请求分发:一个“加入购物车”请求的完整旅程
用户在前端页面点击“加入购物车”,这个动作最终变成一次HTTP请求,比如POST /cart/add?goodsId=3&count=1。请求到达Tomcat后,被SpringMVC的前端控制器DispatcherServlet拦截。DispatcherServlet拿到请求URL后,通过HandlerMapping找到对应的Controller方法,比如CartController里的addCart(HttpServletRequest request)方法。然后由参数解析器把goodsId这类请求参数绑定到方法参数上。Controller拿到参数后,调用CartService把商品信息写入购物车存储介质(Session),最后返回一个字符串,视图解析器把它拼成实际JSP路径,渲染后的HTML回写给浏览器。
这个流程在SSM项目里有一条很清晰的链路:请求 → DispatcherServlet → HandlerMapping → Controller → Service → Mapper → 数据库 → 反向逐层返回 → JSP渲染。答辩时能把这条链路完整画出来,就已经比很多只会背概念的人强了。需要注意Controller只做“请求接收”和“结果返回”两件事,不要在里面写SQL、写复杂业务判断。代码写多了之后,这部分很容易被塞成“万能入口”,是代码评审一定看的坏味道。
2.3 MyBatis持久层:SQL写在Mapper文件里,数据库增删改查不再散落各处
MyBatis的核心机制是Mapper接口和Mapper XML文件映射。工作流程是:定义接口方法,在XML里写SQL,启动时MyBatis为接口生成代理实现。以商品查询为例,GoodsMapper.xml里这样写:
<!-- GoodsMapper.xml --> <mapper namespace="com.muying.dao.GoodsMapper"> <!-- 按价格区间查商品,动态条件用 where + if 组合 --> <select id="selectByPriceRange" resultType="com.muying.entity.Goods"> SELECT id, goods_name, price, stock, category_id, image FROM goods <where> <if test="minPrice != null"> AND price >= #{minPrice} </if> <if test="maxPrice != null"> AND price <= #{maxPrice} </if> </where> ORDER BY price ASC </select> </mapper>这段SQL能说明两个MyBatis的关键设计。一是resultType指定了结果映射目标实体类,MyBatis会自动把查询列名与Goods属性对应,前提是实体类字段名和表列名保持一致的命名习惯,不一致时要用<resultMap>手动映射。二是<where>和<if>组成动态SQL,minPrice和maxPrice为空时不会拼进WHERE子句,这样数据库增删改查里的“查”就能应对母婴用品网站里最多变的场景——按品牌、按价格、按分类筛选商品。
写Mapper文件时要小心XML转义,代码里>要写成>,<要写成<。另外MyBatis接口绑定有一条硬性规则:Mapper接口的包名和XML的namespace必须一致,接口方法名必须和XML里select/insert/update标签的id一致,否则启动就报Invalid bound statement。这个错误的详细排查放到第五章。
2.4 JSP视图层:EL表达式和JSTL是你的必修课
JSP在SSM项目中的职责是最终的HTML渲染。一个干净的JSP页面应该主要包含HTML标签、EL表达式${...}、JSTL标签比如<c:forEach>、<c:if>。拿商品列表页来说,Controller把List<Goods>放进Model后,JSP里这样遍历:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <table> <c:forEach var="g" items="${goodsList}"> <tr> <td>${g.goodsName}</td> <td>${g.price}</td> <td> <a href="${pageContext.request.contextPath}/cart/add?goodsId=${g.id}">加入购物车</a> </td> </tr> </c:forEach> </table>几个细节要注意。pageContext.request.contextPath用来获取项目上下文路径,推荐写它而不是写死/muying,这样项目context-path改了也不用改页面。${g.goodsName}会调用Goods对象的getGoodsName()方法,这是EL表达式的取值机制,方法名拼接规则是get加属性名首字母大写。JSP页面里尽量别出现<% ... %>脚本片段,脚本片段读起来困难、调试困难,这是老项目里最被人诟病的写法。答辩时如果代码里大量存在Scriptlet,老师大概率会直接扣代码规范分。
2.5 毕业设计选型逻辑:这个组合为什么至今还出现在选题表里
现在企业新项目基本都走Spring Boot + Vue或者Spring Cloud微服务路线。为什么SSM+JSP还在毕业设计里大量存在?核心原因有三点。
第一,教学还是这套。很多高校的Java Web课程大纲还停留在SSM+JSP阶段,课程设计和毕业设计自然沿用。第二,SSM能拆开讲的东西更多。Spring Boot把大量配置自动化,但自动化意味着黑匣子,学生说不清内部发生了什么。SSM里数据源、事务、视图解析器、Mapper扫描每一处都要手写,答辩时每一个配置都能讲清楚,这些恰恰是面试时考察的基础。第三,跑起来环境成本低。SSM+JSP只要一个Tomcat和一个MySQL就能启动,真出问题也容易查。所以在近两年的毕业设计里,这个组合并没有消失,只是它开始以“课程要求”的形式出现在选题库里。对做这个项目的同学来说,能把这个老组合讲出新意——比如加上Redis缓存或MQ异步——反而更容易拿到高分。
3. 把项目跑起来:数据库表设计、工程目录与SSM三大配置
3.1 母婴用品网站的核心表:用户、商品、分类、购物车、订单
拿到源码包后,第一步不是启动项目,而是先看懂数据库。母婴用品网站的库表设计是典型的小型电商结构,核心六张表:
| 表名 | 用途 | 关键字段 |
|---|---|---|
| user | 用户与登录 | id, username, password, nickname, phone, address |
| category | 商品分类 | id, cat_name, parent_id |
| goods | 商品 | id, goods_name, price, stock, image, category_id, sales |
| cart | 购物车 | id, user_id, goods_id, count |
| orders | 订单主表 | id, order_no, user_id, total_price, status, create_time |
| order_item | 订单明细 | id, order_id, goods_id, goods_name, price, count |
商品表的建表语句可以拆开看两个设计点:
-- 商品表 CREATE TABLE `goods` ( `id` int(11) NOT NULL AUTO_INCREMENT, `goods_name` varchar(100) NOT NULL, `price` decimal(10,2) NOT NULL, `stock` int(11) NOT NULL DEFAULT 0, `image` varchar(255) DEFAULT NULL, `category_id` int(11) DEFAULT NULL, `sales` int(11) NOT NULL DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;第一个设计点:密码字段要处理。毕业设计里通常不要求高强度加密,但至少别明文存储。常见做法是用MD5加盐或SHA-256摘要,项目里一般会自带一个MD5Util工具类。哪怕导师不要求,把“密码脱敏”写进论文里也是亮点。第二个设计点:订单为什么要拆主表和明细表。因为一笔订单可能包含多个商品,orders表存订单总价、状态、用户、时间,order_item存每个商品的购买数量和单价。查询订单详情时通过order_id关联,这是电商表设计的基本范式。库存字段stock要用int类型,在下单扣库存时做stock >= count的判断,避免超卖。
3.2 标准Maven工程目录:源码、配置、前端页面放哪里
解压源码后,src下的目录结构应该长这样:
src/ ├── main/ │ ├── java/ │ │ └── com/muying/ │ │ ├── controller/ # Controller层 │ │ ├── service/ # Service接口 │ │ ├── service/impl/ # Service实现类 │ │ ├── dao/ # MyBatis Mapper接口 │ │ ├── entity/ # 实体类 │ │ └── util/ # 工具类 │ ├── resources/ │ │ ├── jdbc.properties # 数据库连接配置 │ │ ├── spring-mybatis.xml # Spring+MyBatis整合 │ │ ├── springmvc.xml # SpringMVC配置 │ │ └── mapper/ # MyBatis XML文件 │ └── webapp/ │ ├── WEB-INF/ │ │ ├── web.xml │ │ └── jsp/ # JSP页面 │ └── static/ # CSS、JS、图片 ├── pom.xml └── sql/ └── muying.sql # 数据库初始化脚本每个目录的职责要分清:controller不写业务逻辑、service里不写SQL、mapper里不放HTML。答辩时老师常会随机指着一个目录问“这个目录是干什么的”,答不上来就很尴尬。注意WEB-INF/jsp目录下的页面不能通过URL直接访问,必须要经过Controller跳转,这是Servlet规范的安全限制,也是为什么JSP要放在WEB-INF里的原因。
3.3 SSM整合的三大配置:jdbc.properties、spring-mybatis.xml、springmvc.xml
这是整包的核心,配置不对项目根本没法启动。先看数据库连接配置jdbc.properties:
# jdbc.properties jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/muying?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456useUnicode=true&characterEncoding=utf8解决存取中文乱码,serverTimezone=Asia/Shanghai解决MySQL 8.x的时区报错,这两个参数缺一不可。接下来是spring-mybatis.xml里最重要的三个配置:数据源、SqlSessionFactory、Mapper扫描器。
<!-- spring-mybatis.xml 核心片段 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!-- 实体类包名,MyBatis会自动为这些类创建别名 --> <property name="typeAliasesPackage" value="com.muying.entity"/> <!-- Mapper XML的路径 --> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- Mapper接口所在包,MyBatis会为这些接口生成代理对象 --> <property name="basePackage" value="com.muying.dao"/> </bean>三个配置的对应关系:dataSource定义连接数据;typeAliasesPackage告诉MyBatis哪些类能作为resultType的短名使用;mapperLocations告诉SqlSessionFactory去哪里找SQL;MapperScannerConfigurer则负责扫描Mapper接口,让接口方法绑定到XML里的SQL语句。springmvc.xml里关键的是包扫描、视图解析器、静态资源放行:
<!-- springmvc.xml 核心片段 --> <context:component-scan base-package="com.muying.controller"/> <mvc:annotation-driven/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:resources mapping="/static/**" location="/static/"/>InternalResourceViewResolver是JSP视图解析器的标准实现,前缀/WEB-INF/jsp/加后缀.jsp,意味着Controller返回"goods/list"时,实际渲染的是/WEB-INF/jsp/goods/list.jsp。<mvc:resources/>是很多初次接触的人最容易忽略的配置,没有它的放行,CSS、JS请求会被DispatcherServlet拦截,页面样式直接丢失。
3.4 导入项目后在Tomcat里启动的检查清单
拿到源码后不要急着点启动,先按这个顺序检查三件事。
第一,看数据库是否已经导入。用Navicat或命令行执行sql文件夹里的muying.sql,导入后确认表名大小写与配置一致。Linux下MySQL表名默认区分大小写,Windows下默认不区分,这个差异会导致启动不报错但查询报错。第二,看jdbc.properties里的账号密码是否和本地一致。本地密码不是root/123456一定要改,否则报Access denied。第三,看Tomcat版本和JDK版本是否匹配。SSM项目一般适配JDK 8和Tomcat 8/9,用JDK 17跑老项目经常会遇到javax.servlet包找不到的问题,因为JDK 17默认的是Jakarta命名空间。
检查无误后把war包部署到Tomcat的webapps目录,或者用IDEA配置Tomcat Server,启动后访问http://localhost:8080/muying/。如果看到首页说明基础链路通了,接下来进入业务模块实现。
4. 业务模块实现:从用户注册到购物车订单的完整链路
4.1 用户注册与登录:JSP个人信息展示页面与会话控制
注册登录是第一个要完成的模块,因为它牵扯到后续所有与用户相关的数据操作。用户表设计完成之后,UserMapper接口需要提供insertUser、selectByUsername、selectByUsernameAndPassword三个方法。注册的Service层代码:
// UserServiceImpl.java @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public boolean register(User user) { // 先查用户名是否已存在 User exist = userMapper.selectByUsername(user.getUsername()); if (exist != null) { return false; // 用户名已占用 } // 密码脱敏:MD5加盐,避免明文入库 user.setPassword(MD5Util.md5(user.getPassword() + "muying_salt")); return userMapper.insertUser(user) > 0; } }这段代码里有两个点值得说明。一是先查后插的防重复注册逻辑,MySQL里如果没有对username建唯一索引,这个查询是最后一道防线。二是密码用MD5加固定盐处理,这个写法能挡住“数据库被导出后密码一眼看穿”的风险,虽然MD5不算高强度,但在毕业设计语境下比明文强很多。
登录控制的传统做法是Session。登录成功后把User对象放进request.getSession().setAttribute("loginUser", user),JSP页面用${sessionScope.loginUser.nickname}展示当前登录用户,在JSP个人信息展示页面里把用户头像、手机号、收货地址这些字段一并回显。退出登录则是session.invalidate()。这里要注意权限判断,最简单的写法是在JSP顶部用<c:if test="${sessionScope.loginUser == null}">做跳转,但更规范的做法是写一个LoginInterceptor拦截器,统一拦截需要登录的路径,Controller代码里就不用到处判断Session是否为空了。
4.2 商品列表与分类检索:分页查询的正确打开方式
商品列表页是母婴用品网站的门面。Controller接收分类id、价格区间、页码这几个常见参数,把参数封装成查询条件传给Service:
// GoodsController.java 片段 @Controller @RequestMapping("/goods") public class GoodsController { @Autowired private GoodsService goodsService; @RequestMapping("/list") public String list(Integer categoryId, HttpServletRequest request, @RequestParam(value = "pageNum", defaultValue = "1") int pageNum, @RequestParam(value = "pageSize", defaultValue = "12") int pageSize) { List<Goods> goodsList = goodsService.getGoodsByCondition(categoryId, pageNum, pageSize); request.setAttribute("goodsList", goodsList); return "goods/list"; } }这里返回字符串"goods/list",配上视图解析器的前缀后缀后,最终渲染WEB-INF/jsp/goods/list.jsp。这种返回方式在SSM老项目中是绝对主流。Service层背后是分页查询逻辑,早期项目自己写limit,后来普遍用PageHelper插件。PageHelper的用法比较特别,必须在查询前调用启动方法:
// GoodsServiceImpl.java 片段 public List<Goods> getGoodsByCondition(Integer categoryId, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); // 只对下一条SQL生效 return goodsMapper.selectByCondition(categoryId); }PageHelper.startPage是线程本地变量,只对接下来执行的第一条SQL生效。一个高频踩坑点是在startPage之后又执行了别的查询,导致分页效果错乱,这个在第五章详细排查。
4.3 购物车:放Session还是放数据库
购物车是这个项目里唯一一个没有统一标准答案的设计点。常见两种方案:未登录时放Session,登录后放数据库表。毕业设计为了演示方便,多数选择全程放Session里,省掉购物车表的读写。
Session购物车的实现思路:用一个Map<Integer, Integer>表示“商品id → 数量”,整体塞进Session的某个属性里。用户点击“加入购物车”,Controller取出这个Map,put或累加数量再放回去:
// CartController.java 片段 @SuppressWarnings("unchecked") @RequestMapping("/add") public String add(int goodsId, int count, HttpSession session) { Map<Integer, Integer> cart = (Map<Integer, Integer>) session.getAttribute("cart"); if (cart == null) { cart = new HashMap<>(); } cart.put(goodsId, cart.getOrDefault(goodsId, 0) + count); session.setAttribute("cart", cart); return "redirect:/cart/list"; }Session购物车的优点是实现快、答辩能说清;缺点也很明显:用户清浏览器一切就没了,也没法和订单流程的幂等性挂钩。如果是做数据库版购物车,就要多建一张cart表,每次增删都写一次数据库,代码多一倍,但更接近真实电商系统的做法。对这个项目来说,先按Session方案跑通,再在论文里把“数据库购物车”作为优化方向提出,这样代码量可控、论文也有话写。注意Session的Map一定要用泛型处理,很多同学的getAttribute("cart")返回Object后忘记强转,一运行就ClassCastException。
4.4 订单生成与库存扣减:一个需要事务保护的完整过程
下单是这个项目业务链路上最重要的一步。用户在购物车页面点击“去结算”,系统要做的事包括:根据购物车内容计算订单总价;在orders表插入主记录拿到自增id;遍历购物车的每种商品往order_item插入明细,同时扣减goods表的stock并累加sales;清空购物车;跳转到订单成功页。这几步分布在4张表上,任何一步失败都得回滚,不然会出现订单主表有记录、明细缺失、或者库存被扣了但订单没生成的情况:
// OrderServiceImpl.java 片段 @Transactional(rollbackFor = Exception.class) public Order createOrder(Integer userId, Map<Integer, Integer> cart) { // 1. 计算总价并插入订单主表 // 2. 遍历购物车插入订单明细、扣库存 // 3. 清空购物车 return order; }细节说明:@Transactional默认只回滚RuntimeException,这里写rollbackFor = Exception.class是为了让受检异常也触发回滚。另外要提前校验库存是否够,“假如库存只有3件却买5件,要在事务里抛异常并回滚,而不是硬扣成负数。”最后,下单的地方不建议把秒杀级的高并发优化加进来,毕业设计页面正确比性能更重要。这个模块答辩时老师常问“事务加在哪一层、为什么”,答案就是Service层,因为事务的粒度是业务操作,不是HTTP请求。
5. SSM+JSP项目避坑排查:七个最常翻车的问题,原因和解决一次讲清
5.1 中文乱码:页面、请求、数据库三层都可能出问题
现象:JSP页面上商品名称显示成“???”,或者表单提交的中文用户名在数据库里变成乱码。
原因:JSP默认编码、HTTP请求编码、数据库连接编码、数据表字符集四个环节有一层不匹配就出乱码。常见的是JSP页面没写<%@ page contentType="text/html;charset=UTF-8" %>,或者数据库连接URL少了characterEncoding=utf8。
解决:三处一起检查。第一,JSP文件头部加上page指令指定UTF-8;第二,jdbc.properties的url里加useUnicode=true&characterEncoding=utf8;第三,建表语句统一DEFAULT CHARSET=utf8。如果已经乱掉的数据,用mysql数据库修改结构的命令把表字符集改成utf8mb4再重新导入数据:ALTER TABLE goods CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;。乱码在毕业设计里是老师最常挑的问题之一,必须一次弄干净。
5.2 CSS和JS被DispatcherServlet拦截:页面有内容没样式
现象:页面内容正常,但CSS样式全丢、按钮没有图片。
原因:web.xml里把DispatcherServlet映射到了/,拦截所有请求。浏览器请求/static/css/style.css时,这个静态文件请求也被DispatcherServlet接管了,而SpringMVC默认不处理静态资源。
解决:在springmvc.xml里加<mvc:resources mapping="/static/**" location="/static/"/>,或者在web.xml里给静态资源单独配置DefaultServlet。这两种做法是SSM老项目里最常见的修复方案。注意mapping和location的路径要匹配,/static/**表示URL前缀,/static/表示实际目录,很多人只改一个导致还是404。
5.3 数据库连接失败:先按顺序查这五个地方
现象:项目启动报错,日志里出现Access denied for user或Communications link failure或Connection refused。
原因:五选一。数据库服务没启动;账号密码不对;jdbc.url的库名写错;端口被改过;MySQL 5.7与8.0的驱动不匹配。
解决:按我的血泪经验,顺序很重要。一查MySQL服务是否在运行,Windows下看任务管理器里的mysql进程;二查jdbc.properties的账号密码;三查MySQL端口是不是3306;四查库名是否存在,用mysql数据库常用命令show databases;确认muying库在不在;五查驱动版本,MySQL 5.7用5.x驱动,MySQL 8.0用8.x驱动,用错会出现SQLNonTransientConnectionException。这个顺序从概率最高的开始排,能最快定位问题。
5.4 Invalid bound statement:MyBatis映射文件没被扫描到
现象:启动正常,访问接口报Invalid bound statement (not found): com.muying.dao.GoodsMapper.selectByCondition。
原因:Mapper接口找到了,但对应的XML没被SqlSessionFactory加载,或者XML里的namespace和接口全限定名不一致。
解决:先检查mapperLocations配置里的classpath:mapper/*.xml路径是否准确;再看namespace是不是写成了com.muying.dao.GoodsMapper;最后看接口方法名与XML标签id是否完全一致。检查完这三个点,绝大多数绑定失败都消失了。如果路径没问题,还要看编译输出目录里有没有生成xml,有时IDEA不把resources里的xml拷贝到target,需要在pom.xml里加resource配置把xml和properties文件都打进去。
5.5 购物车刷新就没了:Session的存活范围没搞清
现象:加入购物车后刷新页面,购物车空了。
原因:常见两种。第一种是重启了Tomcat,内存里的Session全部清空;第二种是代码里把购物车放进了request而不是session,request.setAttribute("cart", cart)在请求结束后就没了。
解决:确认用HttpSession session = request.getSession()拿到会话对象再往里放。还要注意浏览器清缓存、换浏览器、换设备都会导致Session丢失,这是答辩演示时要避开的坑——提前把商品加好再开始讲,不要现场演示到一半购物车空空如也。Session丢失的机制就是会话ID存在客户端Cookie里,服务端内存里没有对应数据就只能新建。
5.6 PageHelper分页插件:startPage位置不对就串数据
现象:第一页的数据正确,第二页数据重复或总数统计错误。
原因:PageHelper.startPage()是ThreadLocal机制,只对紧接着的第一条查询生效。但代码里在startPage之后又调用了其他Mapper,比如把商品分类列表查询放在了商品分页查询之前,分页参数被错误吞掉。
解决:最稳妥的写法是startPage后立即执行真正的列表查询,中间不要插入任何其他Mapper调用。另外PageHelper的版本要和MyBatis版本匹配,低版本MyBatis配新PageHelper有时会报反射错误,最简单的方式是直接用PageHelper 5.x配MyBatis 3.4以上版本。这个报错信息往往只是“反射失败”几个字,不熟悉的人会以为是代码逻辑问题,实际上就是版本兼容问题。
5.7 改完JSP不生效:浏览器缓存和编译缓存的干扰
现象:改了JSP页面,刷新浏览器还是旧页面。
原因:Tomcat对JSP后台有增量编译,多数情况下改了会自动重编译。真正影响的是浏览器缓存和部署目录不一致。另外浏览器地址栏缓存了重定向前的URL,也会让人误以为没生效。
解决:强制刷新按Ctrl+F5;在IDEA里确认部署方式,如果用的external build,确保把项目rebuild后重新部署;如果项目在Tomcat的webapps里,确认改的是webapps下的那份而不是src里的副本。这个“玄学”问题在毕业设计中经常让人反复怀疑人生,其实九成是缓存或者部署路径不一致,不涉及任何高深原理。
6. 把这个项目从“能跑”做到“能答辩”:验证方法和三个改动
项目能跑起来之后,距离“能过答辩”还差几步。我建议按一条业务主线去完整走查:注册一个新用户 → 登录 → 逛首页 → 按分类筛选 → 加入购物车 → 改数量 → 结算下单 → 在个人中心看到订单。这条链路只要每一步都能正常走通,项目的基础分就稳了。验证时顺手记下哪个环节出问题,优先修购物车和订单这两块,因为答辩老师最喜欢从这两个模块追问数据流转的细节。
三个小改动,投入小收益大。第一个,注册时加一个简单的用户名重复校验和两次密码一致性校验,前端用几行JS就能做,能挡住演示时最常见的翻车。第二个,在订单创建时用UUID或时间戳生成订单号,订单号要唯一可读,这比直接展示自增id专业得多。第三个,给列表页加一个分页条,用PageHelper已经生成的分页数据把上一页/下一页链接补上,商品列表和订单列表都有完整的浏览体验。
做毕业设计的真实感受是:参考源码能让你少走弯路,但要拿高分,一定要至少改动一处属于你自己想法的功能。哪怕只是把“用户列表”改成“按注册时间排序”,答辩时都能说清“这个是我改的、为什么这么改”。用别人的代码不可耻,完全看不懂才是问题。把SSM这套东西的请求链路、配置、事务、Session这几个核心点讲明白,才算是真正掌握。希望帮到你。
本文还有配套的精品资源,点击获取