ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SSM框架+MySQL超市管理系统毕设源码全解析

SSM框架+MySQL超市管理系统毕设源码全解析

简介:一套基于SSM框架实现的超市管理系统Java毕设源码,面向计算机专业毕业生、期末大作业作者及Java实战学习者。系统覆盖商品管理、库存管理、销售管理、会员管理和系统管理,能够支撑超市日常业务运转,并在实现中体现SSM整合、MySQL数据库设计、权限分配与日志记录等关键知识点。压缩包共938个文件,约10.17MB,包含258个js、161个png、112个java、102个css、53个jsp、20个xml和2个sql等,主体为Java后端、JSP页面、MyBatis映射及数据库脚本,前端配套图片与样式资源,另附项目文档和开发环境配置,便于导入运行与二次开发。项目文档对开发过程有较详细记录,可帮助理解SSM整合思路、前后端交互和常见问题排查;包内界面素材和演示图片也能辅助快速把握系统功能与页面结构,适合按模块学习、毕设演示与答辩准备。已有62人浏览学习。

1. 用一套SSM源码跑通期末大作业:超市管理系统为什么是Java Web选题的"安全牌"

期末季去系里走一圈,Java毕设和期末大作业的选题里,SSM框架加MySQL的管理系统永远是最高的那一撮,其中超市管理系统又是这批项目里的典型代表。原因不复杂:商品、分类、供应商、订单、用户,五六张表刚好把SpringMVC、Spring、MyBatis三层里的所有关键环节串一遍,工作量够答辩展示,难度又不会让项目卡在最后一步。这份资源就是标准SSM(Spring + SpringMVC + MyBatis)配合MySQL的Java毕设源码,整套项目带文档。我做过几年Java后端,也帮人救火改过不少半路出问题的毕设,这类带文档源码最值钱的不是代码量,而是能让你按图索骥,看到一个真实请求从JSP页面到数据库再返回的完整链路。适合的人群很明确:毕设选了这个题目的学生、期末大作业不想从零搭框架的、以及想通过完整案例吃透SSM项目结构的开发者。

2. SSM框架的项目骨架:包结构、分层职责与一次登录请求的完整流转

2.1 三个框架的分工:Spring管对象、SpringMVC接请求、MyBatis管SQL

SSM被叫"老三样"很多年,但真能把三个框架边界讲清楚的人不多。Spring是容器框架,负责Service层对象的创建和生命周期管理,项目里你几乎看不到new ServiceImpl这类代码,所有依赖都是通过@Autowired注入进来的,对象什么时候创建、什么时候销毁,全交给Spring容器。SpringMVC是Web层框架,站在Controller这一层,它接收浏览器进来的HTTP请求,把URL映射到具体方法,做参数绑定、数据校验,业务处理完后返回JSP页面或者JSON数据。MyBatis是持久层框架,站在Dao这一层,把接口方法名和XML文件里的SQL语句绑定在一起,执行SQL之后自动把结果集封装成实体类对象。

三个框架各管一段,SpringMVC管请求怎么进来,MyBatis管数据怎么出去,Spring在中间把Service、Dao这些对象维系起来。这句话理解了,整个项目的包结构就不会看晕。很多人调试SSM项目时喜欢到处加日志,其实先搞清楚请求现在处在哪一层,该去哪个文件里找问题,比盲目打日志高效得多——这算是调SSM项目的一个基本习惯。

2.2 源码包结构拆解:entity、dao、service、controller、mapper各自该放什么

拿到源码第一件事,我建议先理包结构,别急着启动。绝大多数SSM毕设项目的目录都长这样:

src/ └── main/ ├── java/com/ahpp/supermarket/ │ ├── controller/ # SpringMVC控制器,接收请求并调service │ ├── service/ # 业务逻辑接口 │ │ └── impl/ # 业务逻辑实现类 │ ├── dao/ # MyBatis的Mapper接口 │ ├── entity/ # 数据库表对应的实体类 │ └── util/ # 工具类,比如MD5加密、日期处理 └── resources/ ├── mapper/ # MyBatis的XML映射文件,SQL集中在这里 ├── jdbc.properties # 数据库连接配置,改这里连自己的MySQL ├── spring-mybatis.xml # Spring与MyBatis整合配置 └── spring-mvc.xml # SpringMVC配置,视图解析器、注解驱动都在此

这个分层对应关系很直接:entity对应数据库表,一个实体类基本就是一张表;dao只放接口方法不写实现,真正的SQL全在resources/mapper下的XML里;service接口配impl实现类处理业务规则;controller在最后接收请求,把数据往页面上送。调用方向永远是Controller调Service,Service调Dao,Dao通过MyBatis执行XML里的SQL。反方向则是数据封装返回,最终回到Controller再渲染页面。

源码里还有webapp目录,JSP就放在这里,一般也是按模块分子目录,user下放登录注册页面,goods下放商品管理页面,目录结构和Controller的RequestMapping基本对应。为什么SQL不直接写在Dao接口的注解里,非要单独建mapper目录?这个问题答辩经常被问到。注解写SQL适合简单单表操作,一旦遇到"商品名称可能为空、分类也可能为空"这种组合查询,注解拼条件就非常别扭,XML里的动态SQL用if标签就能优雅解决。另外SQL和Java分离之后,改SQL不用重新编译Java代码,项目后续移交维护都方便。

2.3 一次登录请求从浏览器到数据库再返回的完整路径

结构看明白之后,还得走通一个请求才算真正入门。以登录功能为例,用户在login.jsp输入用户名密码,点提交,表单以POST方式发到Tomcat。Tomcat把请求交给SpringMVC的前端控制器DispatcherServlet,它根据URL找到处理这个请求的LoginController对应方法。SpringMVC自动把表单参数绑定到方法入参,Controller调用userService.login(),Service下放到UserDao的selectByUsername方法,MyBatis在UserMapper.xml里找到对应的select语句执行,查出User对象后一路返回。登录成功,Controller把User放进session,返回成功视图;失败则回登录页带错误提示。

对应到代码上,登录Controller的常规写法是这样的:

@Controller @RequestMapping("/user") public class LoginController { @Autowired private UserService userService; @PostMapping("/login") public String login(String username, String password, HttpSession session, Model model) { User user = userService.login(username, password); if (user != null) { session.setAttribute("loginUser", user); return "redirect:/goods/list"; } model.addAttribute("error", "用户名或密码错误"); return "login"; } }

这里有几个细节值得留意。第一,@PostMapping("/login")限定只能接收POST请求,如果这套源码用的是Spring 4以前的版本,这里要写成@RequestMapping(value = "/login", method = RequestMethod.POST)。第二,登录成功返回"redirect:/goods/list",这是因为redirect做了二次请求,刷新商品列表页时不会重复提交登录表单;失败返回"login"逻辑视图名,SpringMVC配置的视图解析器会拼出/WEB-INF/views/login.jsp。第三,Controller方法入参直接写String username,SpringMVC会按参数名自动绑定,前提是表单里的name属性的值也要叫username。

Service层的login方法一般会有两步:先按用户名查出User,再比对密码。很多毕设源码这里用的还是明文比对,项目能跑,但答辩遇到"密码安全性怎么保证"这个问题容易卡壳,第六章我会具体说怎么改成MD5存储。现在建议拿着源码,沿着这条链路把登录模块从头到尾对一遍,这套项目基本就算吃透了。

3. MySQL表设计与MyBatis映射:商品、订单、供应商表如何与代码对齐

3.1 核心业务表:超市管理系统到底有几张表

超市管理系统的核心是进销存,业务逃不开商品、库存、订单三块。常规SSM毕设源码里,核心表大概就是这几张:

表名作用关键字段
t_user后台用户/管理员id, username, password, realname, role
t_category商品分类id, cname
t_supplier供应商id, sname, contact, phone
t_goods商品基本信息id, goods_name, price, stock, unit, category_id
t_orders销售订单头id, order_no, total_price, order_time, user_id
t_order_detail订单明细id, order_id, goods_id, quantity, price

商品、分类、供应商为什么拆成三张表而不是塞在一张商品表里,这是答辩时的高频问题。一张表确实也能做,但分类名会大量重复存储,分类改名时要批量UPDATE,想统计"食品类一共卖了多少"只能靠字符串LIKE去猜。拆表之后t_goods只存category_id,分类信息单独维护,查询商品时JOIN t_category拿分类名,这就是最基本的关系规范化设计。源码里的表虽然不多,但表间关系是清晰的,这点在论文的数据库设计章节里能写出不少内容。

订单拆分头和明细也是同样道理,一个订单可能包含多件商品,如果所有商品都塞进t_orders一行,字段没法设计;拆成t_orders存订单整体信息,t_order_detail每行存一件商品及当时的价格和数量,才能支持"查某订单买了哪些东西"这类需求。注意订单明细里存的是下单时的price快照,不能去JOIN商品表的当前价格,否则商品改价后历史订单金额就对不上了,这个细节在数据库设计说明里提一句,答辩老师会认为你真的做过订单。

3.2 建库建表SQL:在MySQL里先把数据环境铺好

源码包里一般有个SQL脚本文件,可能放在sql目录或者doc目录下。导入之前建议先打开看一遍,别拿到就双击执行,里面除了建库建表,通常还带着几条测试数据,看看初始密码是什么,后面登录要用。导入方式用Navicat直接运行.sql文件最省事,命令行方式也可以:

mysql -u root -p < ahpp_supermarket.sql

这个命令会提示输入MySQL的root密码,然后把脚本里的建库建表语句全部执行。注意Windows的cmd里跑这条命令之前,mysql命令必须能被直接找到,否则会提示"mysql不是内部或外部命令",这说明MySQL的bin目录还没加进系统环境变量PATH,去系统设置里补上再开一个新终端就行。不建议在mysql交互界面里用source命令导入,遇到路径带中文或空格的情况容易出幺蛾子。

脚本里建表语句的典型模式是这样,以商品表为例:

CREATE TABLE t_goods ( id INT PRIMARY KEY AUTO_INCREMENT, goods_name VARCHAR(100) NOT NULL, price DECIMAL(10, 2) NOT NULL DEFAULT 0.00, stock INT NOT NULL DEFAULT 0, unit VARCHAR(20), category_id INT, CONSTRAINT fk_goods_category FOREIGN KEY (category_id) REFERENCES t_category(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这段SQL里有四个地方要认真看。第一,price用DECIMAL(10,2)而不是FLOAT,浮点数在MySQL里存金额有精度隐患,算总价时可能出现0.1+0.2变成0.30000000000000004这种尴尬结果,DECIMAL是定点数,专为金额场景设计,10表示总位数,2表示小数位。第二,stock用INT默认0,库存不应该是负数,业务代码做扣减时要先判断够不够,SQL这一层没加CHECK约束,只能靠Service层保证。第三,外键约束fk_goods_category确保t_goods不会出现不存在的category_id,但真实生产环境很多人会故意去掉外键改用逻辑关联,原因是外键在高并发写入时会影响性能,这个点写进论文里反而是加分项。第四,ENGINE=InnoDB和CHARSET=utf8mb4必须保留,InnoDB支持事务,后面做入库出库要用;utf8mb4是完整的UTF-8实现,中文生僻字和emoji都能存。

3.3 jdbc.properties与Mapper映射:项目连接数据库的两座桥

SSM项目要连通MySQL,有两个文件是必改的。第一个是resources目录下的jdbc.properties,改这个文件让项目连上你本机的数据库:

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ahpp_supermarket?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=123456

我每次接手毕设项目,第一步就是核对这个文件里的数据库名、用户名、密码跟本机是否一致。几个要点:3306是MySQL默认端口,如果你电脑上装过多个MySQL版本或者改过端口,这里要对齐;URL里的ahpp_supermarket必须和SQL脚本建的库名完全一致,大小写无所谓但拼写不能错;useUnicode=true&characterEncoding=utf8是防止中文乱码的前置条件之一,乱码问题第5章还会单独讲。

第二个是MyBatis的Mapper映射文件,以商品查询为例:

<resultMap id="GoodsResultMap" type="com.ahpp.supermarket.entity.Goods"> <id property="id" column="id"/> <result property="goodsName" column="goods_name"/> <result property="price" column="price"/> </resultMap> <select id="selectGoodsById" resultMap="GoodsResultMap"> SELECT id, goods_name, price FROM t_goods WHERE id = #{id} </select>

resultMap是MyBatis里最核心的配置之一,它干两件事:建立Java实体类属性property和数据库列名column的对应关系;处理命名不一致的情况。数据库列是goods_name,Java实体类属性是goodsName,两个名字对不上号,不配置映射框架就懵了。select标签的id必须严格等于GoodsDao接口里的方法名,namespace要等于Dao接口的全限定名,这是MyBatis"找到"SQL的关键,后面报"Invalid bound statement"错误时,九成问题出在这个匹配关系上。参数#{id}是预编译占位符,MyBatis会把它替换成?再传入参数,能有效防SQL注入,项目里所有带参数查询都应该用这种写法。

把这两座桥看明白,就掌握了"一个实体类对应一张表、一个XML文件对应一个Dao接口"的规律,后面改功能时想找某张表的SQL,直接按Dao接口名去找同名XML就行。

4. 核心业务模块走读:商品管理CRUD、入库出库与动态SQL查询的实现套路

4.1 商品管理:一个标准CRUD从Controller到Mapper的完整写法

商品管理是这套源码的主干功能,列表、新增、修改、删除、条件搜索五件事,正好覆盖SSM项目里最高频的开发模式。以新增商品为例,前端add.jsp页面提交到Controller,代码是这样组织的:

@Controller @RequestMapping("/goods") public class GoodsController { @Autowired private GoodsService goodsService; @RequestMapping("/add") public String add(Goods goods) { goodsService.saveGoods(goods); return "redirect:/goods/list"; } @RequestMapping("/list") public String list(Model model) { List<Goods> goodsList = goodsService.findAll(); model.addAttribute("goodsList", goodsList); return "goods/list"; } }

add方法直接接收Goods对象做参数绑定,SpringMVC会把表单里name="goodsName"的输入框值自动set进Goods对象的goodsName属性。这里有个隐含约定:表单字段的name要和实体类属性名一致,写错一个字母绑进来的值就是null,但页面不报错,数据进库后才发现字段空着,这是新人最容易翻车的地方。list方法用Model把列表数据传给视图,JSP里配合JSTL标签循环渲染成表格,这种"Controller返回逻辑视图名+Model传数据"是SSM项目JSP页面的标准交互方式。

Service层的实现类写法也很固定:

@Service public class GoodsServiceImpl implements GoodsService { @Autowired private GoodsDao goodsDao; @Override @Transactional public void saveGoods(Goods goods) { goodsDao.insert(goods); } }

@Transactional注解加在这个方法上,单看一个insert好像多余,但入库、出库这类涉及多表多语句的业务里它就非常关键。比如新增商品的同时往库存流水表插一条记录,任何一步失败,事务机制会让两条操作一起回滚,不会出现流水表有记录但商品表没有数据的脏状态。这里补充一个实战经验:Spring声明式事务默认只对RuntimeException回滚,SQLException这类受检异常抛出时不一定会回滚,所以Service层捕获受检异常后通常要包一层RuntimeException再抛出去,这个细节在答辩时主动说出来,老师会觉得你踩过真实的坑。

4.2 入库出库:库存变动为什么不直接UPDATE偏要加流水表

商品入库、出库是超市系统里仅次于CRUD的核心功能。很多入门写法的做法是UPDATE t_goods SET stock = stock + 数量,完事。这样写库存确实变了,但溯源能力为零:这批货什么时候进的、经手人是谁、进价多少,全部没有记录。这套源码如果带了库存流水表,它的实现逻辑值得仔细看。先记流水,再改库存,Service层的典型写法:

@Override @Transactional public void stockIn(StockRecord record) { stockRecordDao.insert(record); goodsDao.updateStock(record.getGoodsId(), record.getQuantity()); }

这个方法的精妙之处在于事务把两件事绑在一起:先往库存流水表插一条入库记录,再更新t_goods的库存数字。如果只更新库存不写流水,月底对账时数量对不上根本查不出原因;如果只写流水不更新库存,页面显示库存数就是错的。两个操作任何一步失败,事务整体回滚,库存数字和流水永远保持一致。

做这类功能时有个约定俗成的要求:StockRecord里除了goodsId和quantity,至少要带recordType字段区分入库还是出库,以及操作时间。出库的时候,Service层要先查当前库存够不够扣,不够就抛出业务异常,而不是让库存变成负数,这个判断放在Service层而不是Controller,因为未来可能有多个入口调用同一套出库逻辑,规则要收口到业务层。源码里如果这块逻辑写得完整,它就是你论文里"业务逻辑封装"的最佳素材。

4.3 组合条件查询:MyBatis动态SQL解决SQL拼接的痛点

商品列表几乎必带条件搜索:按商品名称模糊查、按分类筛选,两个条件还可能同时为空。这种场景用JDBC原生方式拼SQL极其痛苦,你要判断每个条件是否为空再拼接字符串,稍不注意就多一个AND或少一个空格。MyBatis的动态SQL标签就是为这个场景设计的,看搜索功能的Mapper映射写法:

<select id="searchGoods" resultMap="GoodsResultMap"> SELECT g.*, c.cname FROM t_goods g LEFT JOIN t_category c ON g.category_id = c.id <where> <if test="goodsName != null and goodsName != ''"> AND g.goods_name LIKE CONCAT('%', #{goodsName}, '%') </if> <if test="categoryId != null"> AND g.category_id = #{categoryId} </if> </where> ORDER BY g.id DESC </select>

两个关键点。第一, 标签会自动处理AND前缀:第一个if条件成立时,SQL变成"SELECT ... WHERE goods_name LIKE ...",第二个if再成立时, 会在中间自动补上AND,不会出现"WHERE AND"这种语法错误。如果不用 ,很多老代码会写成WHERE 1=1再拼AND,虽然能跑但不够干净,答辩时说 标签是标准加分回答。第二,LIKE模糊查询不能写成'%#{goodsName}%',MyBatis预编译阶段会处理不了这种占位符,正确姿势是CONCAT('%', #{goodsName}, '%'),这也是面试常考点。

动态SQL还有 、 标签,比如按价格区间筛选、批量删除商品,都用得上。这套源码里如果只有 和 ,可以自己动手加上 实现一个批量删除,改完记得在Service层把批量删除也加上事务注解,这就算一次完整的功能扩展练习了。

5. 从导入到跑通的部署避坑:配置、启动与五个翻车现场的解决办法

5.1 环境准备:JDK、Tomcat、MySQL、导入方式先对齐

跑这套SSM项目的环境,我建议按下面这张表对齐,可以省掉大量安装后遗症:

组件推荐版本说明
JDK1.8和SSM老项目兼容性最好,JDK 11以上跑旧版cglib会报一堆反射警告
Tomcat8.5或9.0配JDK 1.8最稳,支持Servlet 3.1,IDEA里直接配置
MySQL5.7或8.05.7最常见,8.0要注意JDBC驱动类名变化
Maven3.6左右如果项目带pom.xml;不带Maven说明是lib目录直接引jar包的老式Web项目
IDEA2020以上社区版也能跑,但Tomcat集成需要Ultimate版,也可以用Eclipse

判断项目是不是Maven工程很简单:解压后看根目录有没有pom.xml。有,就用IDEA的Open导入,等待依赖下载完成;没有,说明jar包都放在WEB-INF/lib下,用IDEA直接Open这个目录,再把它识别成Web项目进行配置。

5.2 从源码到浏览器看到登录页:三步操作顺序

完整跑通这个项目,推荐按三步走,顺序不要乱。第一步先建库导数据,数据库没准备好就启动Tomcat,项目会一直在启动日志里报连接失败。第二步改jdbc.properties里的账号密码。第三步配置Tomcat部署项目再启动。

数据库准备这一步,命令行导入前面已经讲过,这里再给一条验证SQL是否导入成功的命令:

SHOW TABLES;

在Navicat或者命令行里执行这条SQL,如果能看到t_user、t_goods、t_category这些表,说明脚本执行成功。顺便执行一条SELECT * FROM t_user,看一下测试账号密码是什么,有些源码初始密码就是admin或者123456,先记住,等下登录要用。

IDEA里配置Tomcat的路径是:Run菜单 -> Edit Configurations -> 左上角加号 -> Tomcat Server Local -> 选本地Tomcat安装目录 -> Deployment标签页点加号选Artifact。这一步有个坑:Deployment里Artifact的Application context值默认是项目名,比如/supermarket_war_exploded,启动后访问地址是http://localhost:8080/supermarket_war_exploded/,有人不知道这个路径,启动成功后访问根路径看到404就以为项目挂了。建议把Application context改短一点,直接改成/,访问http://localhost:8080就能进登录页。

5.3 常见问题排查记录:五个翻车现场

接手这套源码的实战里,下面五个问题出现频率最高,每条都是现象、原因、解决三步定位。

问题一:启动报404,页面找不到

现象:Tomcat启动日志没报错,但访问URL显示404。

原因:Application context没配置对,或者web.xml里的servlet-mapping路径和实际访问路径不一致,还有一种可能,项目没成功部署进Tomcat的webapps。

解决:先看IDEA的控制台输出,找到"Deployment ... has finished"字样确认部署成功;再核对浏览器URL和Deployment里的Application context完全一致;最后打开web.xml看DispatcherServlet的url-pattern,如果配的是*.do,访问页面路径就必须带.do后缀。

问题二:MySQL连接失败,报Communications link failure

现象:启动项目时报错,日志里出现Cannot create JDBC driver of class或Communications link failure,界面一直打不开。

原因:MySQL没有启动,或者jdbc.properties里的端口、密码跟本机不一致。另外如果是MySQL 8.0数据库,驱动类名com.mysql.jdbc.Driver已经废弃,8.0要求用com.mysql.cj.jdbc.Driver。

解决:先确认MySQL服务有没有启动,Windows下按Win+R输入services.msc查看MySQL服务状态;再核对jdbc.properties三项配置;如果本机是MySQL 8.0,把jdbc.driver改成com.mysql.cj.jdbc.Driver,URL里再加一行&serverTimezone=Asia/Shanghai,否则时区报错能卡半天。

问题三:Mapper方法报Invalid bound statement (not found)

现象:项目启动正常,一调用某个Dao方法就抛BindingException,提示Invalid bound statement (not found)。

原因:Mapper接口的包路径和XML文件的namespace对不上,或者XML文件没被编译进classes目录。常见根源是XML文件放在了java源码目录下但没在pom.xml里配置资源过滤,Maven默认只打包resources目录下的XML。

解决:先核对XML文件的namespace是否等于Dao接口全限定名,select标签的id是否等于方法名;再检查target/classes目录里有没有对应的XML文件,如果确实没有,把XML移到src/main/resources/mapper目录下,注意包路径要和Dao接口保持一致;如果项目没有Maven而是lib式Web项目,确认XML在classes目录内。

问题四:页面上全是中文乱码

现象:登录进去之后,列表中文字全部变成问号或者一堆乱码符号。

原因:三处编码不统一:JSP页面没设置UTF-8、数据库表字符集不是utf8mb4、jdbc.url连接串里没加characterEncoding=utf8,三处中任何一处不一致,都会乱。

解决:JSP页面头部加上<%@ page contentType="text/html;charset=UTF-8" %>;建表时保证表字符集是utf8mb4,如果表已经建好,执行ALTER TABLE t_goods CONVERT TO CHARACTER SET utf8mb4;来转换;jdbc.properties的URL里保留characterEncoding=utf8。三处全部对齐后重启项目,乱码基本消失。

问题五:端口被占,Tomcat启动失败

现象:IDEA报Port 8080 was already in use,Tomcat启动失败。

原因:上一个Tomcat实例没关干净,或者电脑上其他程序占用了8080端口。

解决:命令行执行netstat -ano | findstr 8080,查看到占用进程的PID,再到任务管理器结束对应进程。如果想省事,也可以在Tomcat配置里改HTTP port为8081,但注意改完访问URL要跟着变。我一般倾向于杀掉占用进程而不是改端口,因为项目文档里写的访问地址默认是8080,改端口等于给后面留坑。

6. 答辩前的三个小改造:密码加密、列表分页与未登录拦截

6.1 用户密码从明文改成MD5

源码里若密码是明文存储,答辩被问"安全性"是个硬伤。改造方法:UserService里注册时存密文,登录时比对密文,工具类里写一个MD5方法:

public static String md5(String source) { String result = ""; try { MessageDigest md = MessageDigest.getInstance("MD5"); result = new BigInteger(1, md.digest(source.getBytes("UTF-8"))).toString(16); } catch (Exception e) { e.printStackTrace(); } return result; }

6.2 商品列表加分页

数据量大时一次性查全部是个隐患。引入PageHelper插件是SSM项目里最常见方案:pom里加依赖,mybatis配置里加拦截器插件,Service里调用PageHelper.startPage(pageNum, pageSize)后正常查询,MyBatis会自动生成LIMIT语句。改造后在地址栏带pageNum参数,列表页就有上一页下一页了。

6.3 加一个登录拦截器

没做拦截的话,用户不登录直接访问/goods/list也能看到数据,这是功能完整性问题。写一个HandlerInterceptor,在preHandle里判断session里有没有loginUser,没有就重定向到登录页,再在spring-mvc.xml里配置 mvc:interceptors 拦截/goods/**这些需要登录才能访问的路径。

这三个改造的工作量都不大,但每一个都能在答辩时展开讲出设计理由。做这些改动时我习惯先把原始代码备份一份,改坏了随时能退回去,从那以后每次拿到毕设源码,我都强制自己先跑通原版、再备份、再动手改,这个顺序真的能避免很多"改出一堆bug还找不回原样"的尴尬局面。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进