ARTICLE DETAIL

资讯详情

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

SSM框架宠物用品商城系统毕设全流程拆解:从数据库设计到事务处理

SSM框架宠物用品商城系统毕设全流程拆解:从数据库设计到事务处理 毕业季又快到了每年这个时候都会有一堆人来问我“Java毕设做什么题目好”。我的回答一直很简单宠物用品商城系统。别觉得它“烂大街”恰恰因为它经典才适合拿来做毕设——业务链条完整、技术栈清晰、工作量可控而且老师爱问的那些点表设计、框架整合、事务处理它全都占了。这篇文章我就把这套基于SSM 框架的宠物用品商城系统从项目规划、数据库设计、核心代码实现到答辩思路完整拆给你看。这篇内容不是网上那种只给个“商品增删改查”的阉割版而是按“宠物用品销售全流程”的标准来做前台用户注册登录、商品分类浏览、关键字搜索、购物车、提交订单后台管理员维护分类、上下架商品、处理订单。我会把每一步的原理和实操细节都讲清楚你照着做就行。不管是拿来当毕设还是想通过一个完整项目把 SSM 整合搞明白这篇文章都值得看完。1. 项目整体规划与SSM选型思路先说个很多人忽略的问题为什么毕设题目叫“SSM 框架宠物用品商城”而不是直接用 Spring Boot答案很简单——毕设评阅老师对“框架整合能力”的考察点基本都压在 SSM 手工整合上。用 Spring Boot 的话自动配置把细节全藏起来了你答辩时连“Spring 容器如何管理 Bean”“MyBatis 如何与 Spring 整合”都说不清楚这是减分项。而 SSM 每一步都要自己配置等于把底层原理亲手搭了一遍老师想追问技术深度的时候你能接得住话。1.1 为什么选 SSM 而不是直接上 Spring BootSSM 是 Spring SpringMVC MyBatis 的组合。我个人的理解Spring 管对象IoC/DI和事务SpringMVC 管 HTTP 请求分发MyBatis 管数据库访问。三层各自负责一件事边界非常清晰。选这套组合做宠物商城核心考量有三个第一业务和团队的匹配度。宠物用品商城虽然功能看起来不少但本质上就是标准 CRUD 加上购物车、订单这种关联逻辑。SSM 天生就是干这个的。你不需要像搞微服务那样引入一堆中间件也不需要像写算法题那样死磕复杂逻辑它的知识点和毕设要求正好在同一个维度上。第二面试和答辩的“防御力”。简历上写“精通 Spring Boot”的人太多了但写 SSM 并真正手工整合过的人对框架原理的理解是实打实的。我面试过不少应届生聊到“Spring 的 Bean 生命周期”“MyBatis 的 Mapper 代理机制”能用 SSM 项目讲明白的人明显占优。毕设答辩同理老师在问“MyBatis 的 #{} 和 ${} 有什么区别”时你因为自己配过 SQL 映射回答起来会自然很多。第三学习过程的可控性。用 Spring Boot 的话启动即用出了问题反而不容易定位。SSM 在整合那几天会让人抓狂但每解决一个配置问题你对框架的理解就深一层。这个过程本身就是毕设的价值所在。1.2 宠物用品商城系统的业务闭环与角色设计再来说说这个系统的“全流程”到底是什么意思。宠物用品商城和普通商城最大的差别在业务关键词上宠物食品、猫砂、玩具、洗护用品这些商品有分类、有品牌、有适用宠物类型猫/狗/仓鼠等订单还可能要处理“体重区间”“保质期”这类细节字段。系统的用户角色我建议明确分成两个前台普通用户注册、登录、浏览商品、搜索、加入购物车、结算生成订单。这个角色的核心诉求是“快速找到东西并完成购买”。后台管理员分类管理增删改查、商品管理上/下架、更新库存和价格、订单管理查看、发货、完成。这个角色的核心诉求是“管好商品和订单状态”。提示做毕设的时候别贪多。我见过有人非要加个“优惠券秒杀”或者“宠物社区发帖”功能结果工作量爆炸核心流程还没做扎实。宠物商城系统的精髓是“闭环”——从选品到下单再到后台发货一条线走通。扩展功能放到后面有精力再说。2. 数据库设计与核心模块拆解商城系统的灵魂不在代码而在数据库表设计。我带的每个学生动手写代码之前我都要求先把表结构画出来评审通过再开写。因为这个项目的所有业务逻辑本质上都是在操作这几张表之间的关系。2.1 六张核心表的字段设计与关系分析一个标准的宠物用品商城最少需要六张表用户表、宠物分类表、宠物用品表、购物车表、订单表和订单明细表。字段设计上我列一个比较规范且容易在答辩时讲出亮点的版本用户表user字段名类型说明idint主键自增usernamevarchar(50)登录名唯一passwordvarchar(100)加密存储nicknamevarchar(50)昵称phonevarchar(20)手机号create_timedatetime注册时间宠物分类表pet_category字段名类型说明idint主键namevarchar(50)分类名如“猫粮”“狗玩具”sort_orderint排序权重parent_idint父分类ID支持二级分类宠物用品的分类比较特殊除了“猫狗”这种宠物类型维度还有“食品/用品/药品”这种品类维度所以设计成可扩展的父子分类会更从容。不过毕设阶段做平级分类也完全够用。宠物用品表pet_product字段名类型说明idint主键category_idint关联分类表namevarchar(100)商品名subtitlevarchar(200)卖点副标题main_imagevarchar(255)主图URLdetail_htmltext图文详情pricedecimal(10,2)原价stockint库存statustinyint1上架 / 0下架create_timedatetime上架时间这里的status字段是个小细节但答辩时很加分。我不会直接物理删除商品而是用上下架状态来控制。这样历史订单在回查商品信息时不会因为商品被删而变成“查无此物”。购物车表cart_item字段名类型说明idint主键user_idint关联用户product_idint关联商品quantityint数量checkedtinyint是否勾选create_timedatetime加入时间订单表orders字段名类型说明idint主键order_novarchar(64)订单号唯一user_idint关联用户total_pricedecimal(10,2)订单总金额statusint0待支付 / 1已支付 / 2已发货 / 3已完成 / 4已取消receiver_namevarchar(50)收货人receiver_phonevarchar(20)收货电话receiver_addressvarchar(255)收货地址create_timedatetime下单时间订单明细表order_item字段名类型说明idint主键order_idint关联订单表product_idint关联商品product_namevarchar(100)商品快照product_imagevarchar(255)图片快照current_pricedecimal(10,2)下单时的价格quantityint数量total_pricedecimal(10,2)明细小计注意order_item里的商品名和价格字段我存的是“快照”。为什么因为商品表里的名称和价格后续可能改但订单一旦生成了用户看到的下单信息必须保持不变。这一个细节就能体现出你有没有真正理解电商系统。2.2 前台购买流程与后台管理的配合有了这六张表整个“销售全流程”就串起来了用户注册登录进入首页看到宠物用品列表。按分类筛选或输入关键词搜索商品。点击商品进详情页选择数量加入购物车。进入购物车勾选要买的商品填写收货地址提交订单。系统扣减库存生成订单号和订单明细返回支付页面毕设一般用一个模拟支付按钮别真去接支付宝。管理员在后台看到新订单进行发货操作。这里我提醒一句订单生成和库存扣减要放在一个事务里。如果只生成订单不扣库存就会出现超卖反过来只扣库存但订单没建成用户就白付钱了。这个“为什么需要事务、Transactional 怎么用、事务在什么情况下不会回滚”的三连问是答辩现场最高频的追问方向。我在第3章会给出核心实现。2.3 三种商品查询方式的设计思路宠物商城的前台首页至少要有三种商品获取方式正好对应了业务中的三个典型场景分类查询点击“猫粮”看出所有猫粮列表。SQL 上对应WHERE category_id ?。关键词搜索搜索框输入“冻干”按商品名模糊匹配。SQL 上用LIKE %关键词%。新品/价格排序按上架时间或价格升降序。对应ORDER BY create_time DESC或ORDER BY price ASC。这三种方式拼起来就是前台的完整商品检索体系。实现时我建议都用 MyBatis 动态 SQL 写在一个 Mapper 里用where标签来判断是否有分类参数和搜索参数。这样比写三个不同方法更清晰也更符合实际项目的写法。3. 环境搭建与核心代码实现走到这一步项目骨架已经清楚了。接下来是真正动手的部分。我会按我实际操作时的顺序把环境版本、项目结构、关键代码一段段展开。3.1 开发环境版本选择与项目结构说明先强调一下版本组合这是新手最容易踩坑的地方JDKJDK 8 或 JDK 11Maven3.6.x 或 3.8.xTomcatTomcat 8.5 或 9.0千万别用 Tomcat 10。 Tomcat 10 把包名从javax.servlet改成了jakarta.servletSSM 传统项目不兼容直接起不来MySQL5.7 或 8.08.0 的话连接驱动要用 8.0 版本IDEA随便社区版也行项目结构我用最经典的 Maven Web 结构pet-store ├── pom.xml ├── src/main/java │ ├── com.petstore.controller # Controller 层 │ ├── com.petstore.service # Service 层接口 │ ├── com.petstore.service.impl # Service 实现类 │ ├── com.petstore.dao # MyBatis 的 Mapper 接口 │ ├── com.petstore.entity # 实体类 │ └── com.petstore.common # 常量、工具类、统一返回结果封装 ├── src/main/resources │ ├── jdbc.properties # 数据库连接配置 │ ├── spring-mybatis.xml # Spring MyBatis 整合配置 │ ├── spring-mvc.xml # SpringMVC 配置 │ └── mapper # MyBatis 的 XML 映射文件 └── src/main/webapp ├── WEB-INF/web.xml # Web 部署描述符 ├── WEB-INF/views # JSP 页面 └── static # CSS、JS、图片pom.xml的核心依赖我就直接给了这几个版本搭配我用着很稳properties spring.version5.3.18/spring.version mybatis.version3.5.9/mybatis.version /properties dependencies dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version${mybatis.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.7/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.28/version /dependency dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.13.3/version /dependency !-- JSP / Servlet / JSTL 依赖按需引入 -- /dependencies3.2 Spring、SpringMVC、MyBatis 三大配置的整合要点SSM 整合说白了就是三件事。第一件事Spring 容器。Spring 管理数据源、SqlSessionFactory、Mapper 扫描、Service 组件扫描和事务管理器。核心配置是spring-mybatis.xml!-- 引入 JDBC 配置 -- context:property-placeholder locationclasspath:jdbc.properties / !-- 数据源这里用阿里的 Druid 或 Spring 自带的 DriverManagerDataSource 都行 -- bean iddataSource classcom.alibaba.druid.pool.DruidDataSource init-methodinit destroy-methodclose property namedriverClassName value${jdbc.driver} / property nameurl value${jdbc.url} / property nameusername value${jdbc.username} / property namepassword value${jdbc.password} / /bean !-- SqlSessionFactory -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource / property namemapperLocations valueclasspath:mapper/*.xml / !-- MyBatis 驼峰映射数据库下划线字段自动转成 Java 驼峰属性 -- property nameconfiguration bean classorg.apache.ibatis.session.Configuration property namemapUnderscoreToCamelCase valuetrue / /bean /property /bean !-- 扫描 Mapper 接口 -- mybatis:scan base-packagecom.petstore.dao / !-- 扫描 Service 实现类 -- context:component-scan base-packagecom.petstore.service.impl /注意mapUnderscoreToCamelCase必须设为true这样数据库的create_time字段就能自动映射到实体类的createTime属性上了。没有这一句你的实体类字段要么全部写成和下划线一致的风格要么每次查询都得在 XML 里写resultMap手动映射工作量直接翻倍。第二件事SpringMVC。它负责把请求路由到 Controller然后把返回结果渲染成页面转成 JSON。spring-mvc.xml配置三样就够了解析 JSP 的视图解析器、扫描 Controller 包、开启 MVC 注解驱动主要是把 JSON 转换器交给Jacksonmvc:annotation-driven / context:component-scan base-packagecom.petstore.controller / bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views/ / property namesuffix value.jsp / /bean第三件事MyBatis 映射。在UserMapper.xml里我用一个where标签把条件查询写在一个方法里前台的搜索和分类请求都能复用select idselectProductList resultTypecom.petstore.entity.PetProduct SELECT * FROM pet_product where if testcategoryId ! null AND category_id #{categoryId} /if if testkeyword ! null and keyword ! AND name LIKE CONCAT(%, #{keyword}, %) /if AND status 1 /where ORDER BY create_time DESC /select我特别想强调#{}和${}的区别#{}是预编译的占位符MyBatis 会把它转成?再由 JDBC 的PreparedStatement处理能有效防 SQL 注入而${}是直接拼接字符串只在少数需要动态传入表名、列名时才能用否则极其危险。这个知识点在面试和答辩中几乎是必考题。3.3 订单生成与库存扣减的事务处理刚才说过异步商城最容易出错的环节就是订单和库存。核心代码用 Service 层的Transactional控制。先看代码怎么写的我给出一个极简但完整版本Service public class OrderServiceImpl implements OrderService { Resource private OrderMapper orderMapper; Resource private OrderItemMapper orderItemMapper; Resource private ProductMapper productMapper; Override Transactional(rollbackFor Exception.class) public Order createOrder(Integer userId, Integer[] productIds, Integer[] quantities, String receiverName, String receiverPhone, String receiverAddress) { // 1. 生成订单号 String orderNo System.currentTimeMillis() String.valueOf(userId); // 2. 计算总金额 BigDecimal totalPrice BigDecimal.ZERO; ListOrderItem itemList new ArrayList(); for (int i 0; i productIds.length; i) { PetProduct product productMapper.selectById(productIds[i]); if (product null || product.getStock() quantities[i]) { throw new RuntimeException(商品库存不足); } totalPrice totalPrice.add(product.getPrice().multiply(BigDecimal.valueOf(quantities[i]))); // 构造订单明细 OrderItem item new OrderItem(); item.setProductId(product.getId()); item.setProductName(product.getName()); item.setProductImage(product.getMainImage()); item.setCurrentPrice(product.getPrice()); item.setQuantity(quantities[i]); item.setTotalPrice(product.getPrice().multiply(BigDecimal.valueOf(quantities[i]))); itemList.add(item); } // 3. 插入订单表 Order order new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setTotalPrice(totalPrice); order.setStatus(0); order.setReceiverName(receiverName); order.setReceiverPhone(receiverPhone); order.setReceiverAddress(receiverAddress); order.setCreateTime(new Date()); orderMapper.insert(order); // 4. 批量插入订单明细 for (OrderItem item : itemList) { item.setOrderId(order.getId()); orderItemMapper.insert(item); } // 5. 扣减库存 for (int i 0; i quantities.length; i) { productMapper.decreaseStock(productIds[i], quantities[i]); } return order; } }对应 Mapper 里的扣库存 SQL 也得配合好update iddecreaseStock UPDATE pet_product SET stock stock - #{quantity} WHERE id #{productId} AND stock #{quantity} /update整个过程最值得答辩时展开讲的就是Transactional和AND stock #{quantity}的双重保证。前者保证要么全成功要么全失败后者用数据库行级锁天然挡住超卖。顺序上也很有讲究先判断库存、再插入订单、最后扣库存任何一步抛异常整个事务都回滚不会出现“订单创建了但库存没扣”的脏数据。3.4 前台购物车到结算的页面流转再说一个我们平时觉得简单、但很多毕设做得不完整的地方购物车到结算的流转。我习惯的做法是购物车的“勾选”状态保存在前端跳转结算时只把选中的商品 ID 和数量传给后端。后端在生成订单前从数据库再查一遍商品的最新价格和库存。为什么不让前端直接传价格因为前端传上来的价格是不可信的用户可以改。所有最终订单金额必须以数据库商品表里的实时价格为准。这个小细节我在答辩演示时会专门提一句老师通常都会点头。注意写代码顺序上先把后台管理员的商品管理跑通再写前台浏览最后写购物车和订单。因为前台展示依赖商品数据后台先能加商品前台才有东西可看。我见过太多人一上来就写首页结果后台没通测试时商品都加不进去只能自己手动插 SQL 调页面效率极低。4. 毕设实测中常见的坑与排查方法SSM 项目我已经带着学生走过十几遍了。每次至少有两三个人会在同样的问题上卡住。我把高频问题整理成了一份速查表你可以直接存下来当排错手册。4.1 启动直接报错HTTP 404 或 Whitelabel Error Page404 在 SSM 项目里八成不是路径写错而是项目部署时的根路径问题。IDEA 中默认的 Application context 是/pet_store_war_exploded导致你访问的 URL 始终带着这一长串。这个好解决Run/Debug Configurations里把 Deployment 标签页中 Application context 改成/然后重新启动。还有种情况是 404 伴随控制台输出 “No mapping found for HTTP request with URI”。这就要检查 Controller 的RequestMapping里的路径是不是和页面表单 action 一致了。我常用一个笨但有效的方法在浏览器 F12 的 Network 面板里看请求的实际 URL再对照 Controller 注解一眼就能发现问题。4.2 请求到 Controller 了返回 JSON 却是乱码这是 ResponseBody 的老问题。SpringMVC 默认的消息转换器里StringHttpMessageConverter 默认编码是 ISO-8859-1所以返回中文字符串时就会乱码。操作上有两个办法。一种是全局统一配置在spring-mvc.xml的mvc:annotation-driven下配置mvc:annotation-driven mvc:message-converters bean classorg.springframework.http.converter.StringHttpMessageConverter constructor-arg valueUTF-8 / /bean /mvc:message-converters /mvc:annotation-driven更推荐的做法是项目结构上直接避免返回字符串。用 JSON 的话别让 Controller 返回 String而是返回对象或Map然后交给 Jackson 的MappingJackson2HttpMessageConverter处理它默认用的就是 UTF-8。4.3 中文数据库乱码这个坑比较隐蔽。数据库连接串如果没显式指定字符集MySQL 5.7 默认 latin1 的旧库特别容易出现插入数据变问号。正确的连接串写法是jdbc.urljdbc:mysql://localhost:3306/pet_store?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai同时MySQL 建表时也要加一句DEFAULT CHARSETutf8mb4utf8mb4比utf8更强的地方在于它支持 emoji 和生僻字。宠物用品商城如果用户在收货地址里填了个特殊符号用utf8就可能报错写入失败用utf8mb4就很稳。4.4 事务不生效数据回滚不了这是事务问题中最容易踩的。很多人给 Service 方法加上Transactional测试时故意抛异常但数据还是写进数据库了。原因基本就两种类没有被 Spring 扫描到。确认spring-mybatis.xml里的context:component-scan base-packagecom.petstore.service.impl /覆盖了你的 Service 实现类所在的包。方法不是 public 的。Spring 默认通过 JDK 动态代理或者 CGLIB 代理实现事务增强private方法根本不会被代理事务当然不生效。另外默认情况下只有RuntimeException和Error才触发回滚校验异常Exception不会。所以为了稳妥我建议直接写Transactional(rollbackFor Exception.class)这也是我在上面的订单代码里采用的写法。4.5 MyBatis 查询出来全是 null这个基本就是驼峰映射没开。实体类的createTime属性和数据库字段create_time对不上MyBatis 只能默认给 null。解决方式就是在spring-mybatis.xml的 SqlSessionFactory 里加上property nameconfiguration bean classorg.apache.ibatis.session.Configuration property namemapUnderscoreToCamelCase valuetrue / /bean /property加了这一句之后查询结果就会自动映射到你的实体类字段上了。我把这些整理成一个速查表方便你对照现象大概率原因快速检查方式404部署根路径错误或 SpringMVC 扫描包缺失查看 IDEA 控制台映射日志JSON 中文乱码缺少 UTF-8 消息转换器返回对象而不是字符串数据库中文问号连接串未指定字符集检查 jdbc.url 参数事务不生效类未扫描或方法非 public检查包扫描范围和修饰符查询结果全 null驼峰映射未开启检查 SqlSessionFactory 配置4.6 我最后还想单独说一个小技巧做毕设的时候日志是最容易忽略但最重要的排查手段。我只要发现页面的问题和预期现象对不上第一件事就是把log4j2.xml或者logback配置好把com.petstore.dao这个包的 logger 级别调成DEBUG。这样控制台会直接打印每条 SQL 语句和从数据库查出来的参数。这条 SQL 就是整个 Bug 排查链路的源头。logger namecom.petstore.dao levelDEBUG /很多“页面数据出不来”的问题其实 SQL 压根查出来就没数据你看一眼日志心里就有数了根本不用一层层去猜前端的问题。5. 答辩现场怎么讲系统又该往哪个方向扩展最后聊点答辩技巧。代码写完了其实才一半怎么把项目讲清楚是真正决定分数的另一半。5.1 用“一条主流程”串起整个演示我建议答辩的演示顺序千万不要按功能菜单一个个点。老师爱听的讲解逻辑是“业务驱动”。先讲用户侧我从前台页面开始登录一个用户搜索“冻干”选一款猫粮冻干加入购物车再选一款猫条一起结算生成订单模拟支付走完整个链路。然后切后台用管理员账号登录看到刚才生成的订单发货订单状态变成已发货。前台的用户的订单页同期显示已发货。一条完整的业务线几分钟讲完。这条线讲清楚老师就会觉得这个项目是完整且跑得通的。然后你要在讲的过程中把“订单金额为什么后台算”“库存为什么下单时扣”“订单明细为什么要做商品快照”这几个亮点穿插进去。不用讲太细但要有意识地指出这些设计背后的意图。5.2 数据库设计讲解从 E-R 图出发答辩时老师大概率会问“你这个系统数据库设计了哪几张表为什么这么分”这段话我建议你提前练熟就按第2章的内容来讲。可以先画一下 E-R 图说明五张表之间的外键关系然后重点解释“为什么订单和订单明细要分两张表”因为一个订单多个商品拆开之后才能支持“一订单多商品”的明细查询。接着讲到商品分类和商品的关系谁是一的一方谁是多的一方。最后落到“订单明细里为什么不直接用外键关联商品表”上——因为商品以后可能会改为了订单历史稳定我在明细表里做了冗余快照。这套话术下来老师对这个项目的评分绝对差不了。5.3 后续可以继续深挖的优化方向项目做完之后如果你想在简历上再给它加点分量可以往这几个方向扩展Redis 缓存用 Redis 缓存商品列表和商品详情缓解数据库压力。这是目前企业里非常普遍的做法写进简历价值极高。Mock 支付回调做一个模拟支付接口给订单增加一个“支付中 → 支付成功”的状态流转把支付环节也串进去。文件上传商品主图和详情图用本地图片存储或者第三方 OSS 实现管理都可以当成独立模块来展开。JWT 登录态管理把 Session 换成 JWT做一个前后端分离版的前台顺带就把 Vue 技能也覆盖了。这几个方向每个都可以单独拆成一个功能模块工作量不会太大但写在简历上会非常亮眼。特别是 Redis 缓存和文件上传属于面试官看到就一定会追问的“实战点”。说句实在话毕设这一步对很多人来说是第一次独立把一个“完整系统”从零写到能用。 SSM 宠物商城这个项目我带着好几个人做完过没有一个人中途放弃的。它真正的难点不在于某个技术特别难而在于你要把框架整合、数据库设计、业务逻辑、前端页面和答辩表达这五件事串成一个闭环。只要你把上面这套东西按顺序走一遍代码写完了答辩讲清楚了这份毕设对你来说就不会是负担反而会成为你简历上第一个拿得出手的项目。
返回列表