简介一套基于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三层里的所有关键环节串一遍工作量够答辩展示难度又不会让项目卡在最后一步。这份资源就是标准SSMSpring SpringMVC MyBatis配合MySQL的Java毕设源码整套项目带文档。我做过几年Java后端也帮人救火改过不少半路出问题的毕设这类带文档源码最值钱的不是代码量而是能让你按图索骥看到一个真实请求从JSP页面到数据库再返回的完整链路。适合的人群很明确毕设选了这个题目的学生、期末大作业不想从零搭框架的、以及想通过完整案例吃透SSM项目结构的开发者。2. SSM框架的项目骨架包结构、分层职责与一次登录请求的完整流转2.1 三个框架的分工Spring管对象、SpringMVC接请求、MyBatis管SQLSSM被叫老三样很多年但真能把三个框架边界讲清楚的人不多。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调ServiceService调DaoDao通过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 usernameSpringMVC会按参数名自动绑定前提是表单里的name属性的值也要叫username。Service层的login方法一般会有两步先按用户名查出User再比对密码。很多毕设源码这里用的还是明文比对项目能跑但答辩遇到密码安全性怎么保证这个问题容易卡壳第六章我会具体说怎么改成MD5存储。现在建议拿着源码沿着这条链路把登录模块从头到尾对一遍这套项目基本就算吃透了。3. MySQL表设计与MyBatis映射商品、订单、供应商表如何与代码对齐3.1 核心业务表超市管理系统到底有几张表超市管理系统的核心是进销存业务逃不开商品、库存、订单三块。常规SSM毕设源码里核心表大概就是这几张表名作用关键字段t_user后台用户/管理员id, username, password, realname, rolet_category商品分类id, cnamet_supplier供应商id, sname, contact, phonet_goods商品基本信息id, goods_name, price, stock, unit, category_idt_orders销售订单头id, order_no, total_price, order_time, user_idt_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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段SQL里有四个地方要认真看。第一price用DECIMAL(10,2)而不是FLOAT浮点数在MySQL里存金额有精度隐患算总价时可能出现0.10.2变成0.30000000000000004这种尴尬结果DECIMAL是定点数专为金额场景设计10表示总位数2表示小数位。第二stock用INT默认0库存不应该是负数业务代码做扣减时要先判断够不够SQL这一层没加CHECK约束只能靠Service层保证。第三外键约束fk_goods_category确保t_goods不会出现不存在的category_id但真实生产环境很多人会故意去掉外键改用逻辑关联原因是外键在高并发写入时会影响性能这个点写进论文里反而是加分项。第四ENGINEInnoDB和CHARSETutf8mb4必须保留InnoDB支持事务后面做入库出库要用utf8mb4是完整的UTF-8实现中文生僻字和emoji都能存。3.3 jdbc.properties与Mapper映射项目连接数据库的两座桥SSM项目要连通MySQL有两个文件是必改的。第一个是resources目录下的jdbc.properties改这个文件让项目连上你本机的数据库jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/ahpp_supermarket?useUnicodetruecharacterEncodingutf8 jdbc.usernameroot jdbc.password123456我每次接手毕设项目第一步就是核对这个文件里的数据库名、用户名、密码跟本机是否一致。几个要点3306是MySQL默认端口如果你电脑上装过多个MySQL版本或者改过端口这里要对齐URL里的ahpp_supermarket必须和SQL脚本建的库名完全一致大小写无所谓但拼写不能错useUnicodetruecharacterEncodingutf8是防止中文乱码的前置条件之一乱码问题第5章还会单独讲。第二个是MyBatis的Mapper映射文件以商品查询为例resultMap idGoodsResultMap typecom.ahpp.supermarket.entity.Goods id propertyid columnid/ result propertygoodsName columngoods_name/ result propertyprice columnprice/ /resultMap select idselectGoodsById resultMapGoodsResultMap SELECT id, goods_name, price FROM t_goods WHERE id #{id} /selectresultMap是MyBatis里最核心的配置之一它干两件事建立Java实体类属性property和数据库列名column的对应关系处理命名不一致的情况。数据库列是goods_nameJava实体类属性是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) { ListGoods goodsList goodsService.findAll(); model.addAttribute(goodsList, goodsList); return goods/list; } }add方法直接接收Goods对象做参数绑定SpringMVC会把表单里namegoodsName的输入框值自动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 idsearchGoods resultMapGoodsResultMap SELECT g.*, c.cname FROM t_goods g LEFT JOIN t_category c ON g.category_id c.id where if testgoodsName ! null and goodsName ! AND g.goods_name LIKE CONCAT(%, #{goodsName}, %) /if if testcategoryId ! 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 11再拼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.1IDEA里直接配置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下按WinR输入services.msc查看MySQL服务状态再核对jdbc.properties三项配置如果本机是MySQL 8.0把jdbc.driver改成com.mysql.cj.jdbc.DriverURL里再加一行serverTimezoneAsia/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连接串里没加characterEncodingutf8三处中任何一处不一致都会乱。解决JSP页面头部加上% page contentTypetext/html;charsetUTF-8 %建表时保证表字符集是utf8mb4如果表已经建好执行ALTER TABLE t_goods CONVERT TO CHARACTER SET utf8mb4;来转换jdbc.properties的URL里保留characterEncodingutf8。三处全部对齐后重启项目乱码基本消失。问题五端口被占Tomcat启动失败现象IDEA报Port 8080 was already in useTomcat启动失败。原因上一个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还找不回原样的尴尬局面。希望帮到你。本文还有配套的精品资源点击获取