简介基于JavaSSM框架实现的水果蔬菜农产品购物商城毕业设计项目面向计算机相关专业学生与开发者适用于毕业设计、课程设计、项目实训及初期演示。该项目已通过导师指导认可答辩评审分达95分并在mac及Windows10/11环境下运行验证通过功能稳定可直接使用或二次开发。压缩包共1575个文件大小约20.4MB包含Java源码、JSP页面、数据库SQL脚本、项目配置文件、设计文档及大量前端资源其中png、css、js、jpg等样式与页面文件、java及xml后台文件分层存放目录结构清晰便于导入IDE快速部署。目前已有166人学习下载适合软件工程、计科、人工智能、电子信息等专业学生参考学习。资料内含数据库脚本、使用文档及全部配套文件可支撑从建库、启动、功能调试验证到答辩展示的完整流程还可以在此基础上扩展订单、支付、会员等模块快速形成高分毕业设计项目成果。1. 把 SSM 商城从「跑通」做到「答辩能讲」这份 Java 毕设资源到底值不值得下我见过太多人拿到的毕设项目能启动但导师一问「订单表为什么这么设计」「购物车为什么不存数据库」就卡壳。这份基于 Java SSM 的水果蔬菜农产品购物商城之所以在同类资源里值得看是因为它没绕开电商最核心的那几条链路——商品分类、购物车、订单状态流转、后台管理全都有完整的前后端闭环。SpringMVC 负责请求分发、MyBatis 管数据库操作、Spring 把 Service 层对象串起来JSP 做页面渲染典型的 SSM 老牌组合不花哨但用来做毕业设计答辩绰绰有余。适合软件工程、计科这类专业的学生直接拿来当课设、毕设基座也适合想搞明白 SSM 三个框架是怎么在一个 Web 项目里协作的人。这篇笔记我会按自己拆项目的习惯把结构、环境、数据库、核心代码、踩坑和部署一次说透。2. SSM 商城项目拆解先看清这 8 类文件再动手2.1 拿到压缩包先不要急着导入 IDE先做三分钟文件体检我接手任何一份源码第一件事不是双击打开 IDEA而是先在解压目录里过一遍文件。这份资源解压后核心内容分为几块src源码目录、数据库脚本通常是.sql文件、项目配置文件pom.xml、jdbc.properties、以及使用文档。src/main/java下面按controller、service、mapper、pojo或entity、utils分层src/main/resources放 Spring 和 MyBatis 的 XML 配置src/main/webapp下是 JSP 页面、静态资源CSS/JS/图片和WEB-INF/web.xml。# 一份典型 SSM 商城项目的解压结构 unzip 基于javaSSM水果蔬菜农产品购物商城.zip -d fruit-mall cd fruit-mall find . -maxdepth 2 -type d | head -30先确认这几样东西在不在数据库脚本有没有、pom.xml是否存在、jdbc.properties里的数据库连接信息能不能看到。如果.sql文件缺失后面数据库表结构就得手搓工作量完全不同。这份资源是完整包正常都应该有但下载后先确认是职业习惯。2.2 配置文件里藏着第一个决定成败的变量数据库连接参数打开src/main/resources/jdbc.properties你会看到类似这样的内容jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/fruit_mall?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456这里要注意三个点。第一serverTimezone必须设置MySQL 8.x 不设这个参数会报时区错误。第二useSSLfalse是常规操作本地开发没必要开 SSL 握手。第三characterEncodingutf8决定了中文数据写入数据库不乱码如果这里漏了商品名称和订单收货人全是问号。改完这个文件后记得检查 MySQL 里有没有对应的fruit_mall库没有就先执行数据库脚本创建。2.3 Spring SpringMVC MyBatis 三个配置文件的协作关系SSM 项目里最劝退新手的不是 Java 代码而是那三个 XML 配置文件。applicationContext.xml管 Spring 容器负责 Service 层 bean 的扫描和事务管理spring-mvc.xml管 SpringMVC负责 Controller 层的包扫描、视图解析器和静态资源放行MyBatis 的配置要么独立一个mybatis-config.xml要么直接合进 Spring 配置里。这份资源走的是合入方案在applicationContext.xml里配置 SqlSessionFactory 和 MapperScannerConfigurer!-- 配置 MyBatis 的 SqlSessionFactory -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ property nametypeAliasesPackage valuecom.fruit.mall.pojo/ /bean !-- 扫描 Mapper 接口自动生成代理实现类 -- bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.fruit.mall.mapper/ /beanmapperLocations指向classpath:mapper/*.xml意味着所有 SQL 语句的 XML 文件要放在resources/mapper目录下放错位置启动直接报Invalid bound statement。typeAliasesPackage配了pojo包路径这样 mapper XML 里写返回类型可以简写成com.fruit.mall.pojo.Product而不需要每次都写全限定名。刚接触 SSM 的人最容易在MapperScannerConfigurer的basePackage上翻车——包名拼错一个字母启动时不会有任何异常但一调用数据访问层就抛org.apache.ibatis.binding.BindingException而且这个错只在运行时出现翻日志才看得到。3. 从建库到跑通数据库脚本、Maven 依赖与本地启动全流程3.1 数据库设计先看懂这五张核心表购物商城这种题目的数据库设计是有套路可循的。用户表管登录注册商品表管农产品信息分类表管水果/蔬菜/粮油这些大类购物车和订单表负责交易链路。这份资源里的核心表我在下面整理一下你导入数据库后用DESC命令能逐一核对。表名用途关键字段tb_user前台用户 / 后台管理员登录id,username,password,roletb_category商品分类id,name,parent_id支持两级分类tb_product水果蔬菜农产品商品id,name,price,stock,image,category_idtb_cart购物车不落库或落库均可能id,user_id,product_id,quantitytb_ordertb_order_item订单主表与订单明细订单号、总金额、状态、下单时间、商品快照订单表和订单明细表拆开是电商系统的标准做法——订单主表只存收货信息和总价每个商品的具体名称、单价、数量放进明细表。这样做的好处是生成订单时把商品价格快照下来以后商品改价不影响历史订单。做毕业设计答辩时能用一句话讲清楚这个设计意图分数会明显不一样。-- 核心订单表结构精简版 CREATE TABLE tb_order ( id int(11) NOT NULL AUTO_INCREMENT, order_no varchar(32) DEFAULT NULL COMMENT 订单编号, user_id int(11) DEFAULT NULL, total_price decimal(10,2) DEFAULT NULL, status tinyint(4) DEFAULT 0 COMMENT 0未付款 1已付款 2已发货 3已完成, create_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8;status字段用tinyint存状态码而不是直接存中文这是常见做法——程序里定义常量或枚举去映射状态含义数据库层面只用数字。好处是存储体积小、查询快、状态流转清晰。做答辩时导师问「订单状态怎么管理」直接答「用状态机思维 数字码」就够有说服力。3.2 Maven 依赖版本怎么选JDK 版本决定一切这份资源是 SSM 架构pom.xml里 Spring 版本大概率是 4.x 或 5.x对应 JDK 8。如果你本机装了 JDK 11 或更高版本直接跑可能遇到IllegalArgumentException或反射相关的兼容问题。properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target spring.version5.2.6.RELEASE/spring.version /properties dependencies !-- Spring MVC -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency !-- MyBatis 整合 Spring -- dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.5/version /dependency !-- MySQL 驱动8.x 对应新版驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.26/version /dependency /dependenciesJava 版本要和你本机环境对齐这是我认为整个项目里最值得先确认的一件事。用java -version看一下如果本机是 JDK 8那最省事直接按1.8编译。如果是 JDK 11把source和target改成11通常也能跑但 Spring 版本最好升级到 5.3.x不然某些反射调用会有兼容性警告答辩演示时弹出一堆红色日志很难看。3.3 启动流程从 Tomcat 配置到看到登录页SSM 项目本质是一个 WAR 包项目需要部署在 Tomcat 里。IDEA 里配置 Tomcat 的步骤是固定的Run → Edit Configurations → 新增 Tomcat Server → Local → 选本地 Tomcat 路径 → Deployment 里加war exploded包。访问路径默认是http://localhost:8080/如果上下文路径带了项目名比如/fruit_mall入口地址就是http://localhost:8080/fruit_mall/。# 也可以直接用 Maven 命令打包后丢进 Tomcat mvn clean package -DskipTests cp target/fruit-mall.war $TOMCAT_HOME/webapps/ $TOMCAT_HOME/bin/startup.sh tail -f $TOMCAT_HOME/logs/catalina.out启动成功后别急着逛页面先做两件事。第一用 Navicat 或命令行连上 MySQL执行一遍数据库脚本确认所有表都建出来了。第二打开浏览器访问登录页用资源文档里给的初始账号试一次登录。如果登录后能跳转到首页且商品列表有数据说明前后端链路已经通了。如果页面空白或 500优先看catalina.out最后 50 行日志十有八九是数据库连不上或者 Mapper XML 有语法错误。4. 核心功能模块的三个实现细节购物车、订单状态与后台商品管理4.1 购物车为什么不直接存数据库从代码里看懂设计取舍我拆过很多商城毕设购物车模块有两种实现思路一种是把购物车数据存进session优点是简单、不需要建表、用户不登录也能加购另一种是落库到tb_cart表优点是不会丢、多端同步。这份资源如果走的是session方案那 Controller 里你会看到类似这样的代码Controller RequestMapping(/cart) public class CartController { Autowired private ProductService productService; RequestMapping(/add) public String addCart(Integer productId, Integer quantity, HttpSession session) { // 从 Session 中取出当前购物车没有就新建一个 MapInteger, CartItem cart (MapInteger, CartItem) session.getAttribute(CART); if (cart null) { cart new HashMap(); session.setAttribute(CART, cart); } // 判断商品是否已在购物车中已在则累加数量 CartItem item cart.get(productId); if (item null) { Product product productService.findById(productId); item new CartItem(product, quantity); } else { item.setQuantity(item.getQuantity() quantity); } cart.put(productId, item); return redirect:/cart/list; } }这段代码的核心逻辑是用HttpSession保存一个Map键是商品 ID值是购物车条目对象包含商品信息和数量。加购时先看 Map 里有没有这个商品有就加数量没有就新建条目。用 Map 而不是 List 的好处是去重方便——同一个商品加两次购不会出现两行记录。提示session 方案在毕设答辩时会被问「刷新浏览器购物车为什么没了」。答案要说清楚session 是服务端会话级存储浏览器关闭或会话超时就会清空。如果你想让购物车持久化需要改成数据库方案。4.2 订单状态流转用数字状态码替代复杂的流程引擎订单模块在毕设里不需要做成那种复杂的可配置流程引擎但状态至少要有「已下单未付款 → 已付款 → 已发货 → 已完成」这几档。实际代码里通常用一个int字段保存Controller 层根据用户操作去更新它。提交订单的简化代码如下Transactional public Order createOrder(Integer userId, Integer[] productIds, Integer[] quantities) { // 1. 查用户收货信息 User user userMapper.findById(userId); // 2. 遍历商品计算总价 double total 0.0; ListOrderItem items new ArrayList(); for (int i 0; i productIds.length; i) { Product product productMapper.findById(productIds[i]); total product.getPrice() * quantities[i]; OrderItem item new OrderItem(); item.setProductId(product.getId()); item.setProductName(product.getName()); item.setPrice(product.getPrice()); item.setQuantity(quantities[i]); items.add(item); } // 3. 生成订单主表记录 Order order new Order(); order.setOrderNo(generateOrderNo()); // 时间戳随机数 order.setUserId(userId); order.setTotalPrice(total); order.setStatus(0); // 0表示未付款 orderMapper.insert(order); // 4. 批量插入订单明细返回订单主键后再设置外键 for (OrderItem item : items) { item.setOrderId(order.getId()); orderItemMapper.insert(item); } return order; }注意看两个细节。第一Transactional标注在方法上意味着创建订单和插入明细必须同时成功或同时失败不能出现订单主表有了但明细没插入的情况。第二商品名称和价格要「快照进」订单明细表——也就是说订单生成后即使后台改了商品价格用户订单里看到的还是下单时的价格这是电商系统非常基础的一致性要求。答辩时被问「为什么订单表要拆主表和明细表」直接说这三点冗余商品快照保证历史订单稳定主表只存一次收货信息明细表可以有多条商品分表后统计和报表查询各自的关注点不同、互不干扰。4.3 后台商品管理文件上传和图片路径的坑比预想多后台管理模块的常规功能是商品的增删改查涉及图片上传时问题就来了。这份资源的图片上传代码一般长这样RequestMapping(/admin/product/save) public String saveProduct(Product product, MultipartFile file, HttpServletRequest request) { // 1. 保存图片到服务器磁盘 if (!file.isEmpty()) { String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String newFileName System.currentTimeMillis() ext; String realPath request.getServletContext().getRealPath(/upload); File targetFile new File(realPath, newFileName); file.transferTo(targetFile); product.setImage(/upload/ newFileName); } // 2. 保存商品信息到数据库 productService.save(product); return redirect:/admin/product/list; }request.getServletContext().getRealPath(/upload)拿到的是 Tomcat 部署目录下当前应用的物理路径文件会写到这个目录里。开发环境跑没问题一旦重新部署或 clean 之后重启 Tomcat这个目录可能被清空商品图片就全部丢失了——这算是毕设项目里最常见也最容易被忽视的一个坑。更靠谱的做法是把图片目录配置到项目外部比如D:/upload/或 Linux 下的/data/upload/代码里写一个常量或配置项指向它再用虚拟路径映射让浏览器能访问到。如果你在答辩演示时上传图片不显示不用慌用开发者工具看图片请求 404 还是 500基本就能定位是保存路径的问题还是虚拟映射的问题。5. SSM 购物商城避坑指南启动失败、中文乱码与 Mapper 报错的 6 条实战记录5.1 现象Tomcat 启动直接报ClassNotFoundException: org.springframework.web.context.ContextLoaderListener原因Maven 依赖没有完整打包进 WAR尤其是spring-web这个包没有引入或者 IDEA 里 Tomcat 部署的是普通war而不是war exploded依赖没有被同步。解决检查pom.xml有没有spring-web依赖IDEA 里重新右键项目 → Maven → Reload Project然后把 Artifacts 里的Available Elements全部右键选Put into /WEB-INF/lib再重启 Tomcat。5.2 现象数据库里中文正常但页面上商品名称显示???原因JSP 页面编码、数据库连接编码、MySQL 表字段编码三层不一致。数据库表是utf8但jdbc.properties里的 URL 没加characterEncodingutf8或者 JSP 页面没有pageEncodingutf-8任何一层断了都会乱码。解决先检查jdbc.properties的 URL再检查 JSP 头部是否有% page contentTypetext/html;charsetUTF-8 languagejava %注意是UTF-8中间有个短横线不要写错最后用SHOW CREATE TABLE tb_product;确认表结构是utf8。5.3 现象调用商品查询时报Invalid bound statement (not found)原因这个错几乎都是 Mapper 接口和 Mapper XML 的namespace或方法 id 对不上造成的百分之九十的问题是applicationContext.xml里mapperLocations的路径配错了。如果mapper/*.xml写的是classpath:mapper/*.xml但 XML 文件实际在resources/mapper/下路径就对了如果你把 XML 放进了java/mapper包目录Maven 默认不会把 XML 复制到 classes 目录运行时就找不到。解决把 XML 文件移到src/main/resources/mapper/下或者在pom.xml的build里配置resources把src/main/java下的*.xml也包含进去。5.4 现象MySQL 8.x 连接报Public Key Retrieval is not allowed原因MySQL 8 默认的 caching_sha2_password 认证插件要求客户端先用 RSA 公钥加密密码传输而连接 URL 没启用公钥检索。解决在jdbc.url最后加allowPublicKeyRetrievaltrue。注意如果你用的是 root 用户本地开发也可以直接新建一个用mysql_native_password认证的账号一劳永逸ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;5.5 现象改了商品价格用户之前下的订单总价也变了原因代码里订单明细没有做商品价格快照OrderItem只存了product_id查询订单时实时关联商品表读价格一旦商品表改价历史订单跟着变。解决看tb_order_item表是否有price字段。如果没有要么改表加字段要么修改下单逻辑在生成OrderItem时把数据库查到的商品价格赋值进去。这个坑不是「跑不跑得起来」的问题而是「答辩答不答得上」的问题被导师问住就亏大了。5.6 现象session 里存的中文登录名在 JSP 页面上输出乱码原因这个和 5.2 的乱码成因不同属于 Tomcat 内部 URI 编码设置问题。Tomcat 8 默认URIEncoding为 UTF-8但如果你用的还是老项目改 Tomcat 配置或者某些 JSP 里用了request.getParameter获取中文参数就可能乱。解决在server.xml的Connector上确认URIEncodingUTF-8同时在代码里统一用RequestMapping而不是手拼 URL 参数如果问题只出现在 get 请求改 Tomcat 配置即可post 请求乱码就找CharacterEncodingFilterSpringMVC 里通常已经配好确认下web.xml有没有这个过滤器filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter6. 把项目部署到正式 Tomcatwar 包构建与外部图片存储改造实战6.1 用 Maven 打 war 包并部署到本地 Tomcat答辩前三天最怕的事情是 IDEA 里能跑但换一台电脑就不会部署了。所以我在收到任何 SSM 项目后第一件事一定是验证「不依赖 IDEA 能不能启动」。做法很简单用 Maven 打包成 WAR丢进 Tomcat 的webapps目录看它能不能独立跑起来。这个验证过答辩前换电脑演示才有底气。# 在项目根目录执行跳过测试打包 mvn clean package -DskipTests # 查看生成的 war 包 ls -lh target/*.war # 复制到 Tomcat 部署目录 cp target/fruit-mall.war /usr/local/tomcat/webapps/ # 启动 Tomcat 并看日志 /usr/local/tomcat/bin/startup.sh tail -f /usr/local/tomcat/logs/catalina.out如果打包过程中报错九成是依赖下载失败先检查 Maven 仓库的settings.xml有没有配阿里云镜像。国内网络环境下直接拉 Maven 中央仓库经常超时配好镜像再执行就能过mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror部署完成后 Tomcat 会自动解压 war 包注意日志里有无报错。访问入口通常带项目名如果你希望直接通过根路径访问把fruit-mall.war重命名成ROOT.war再部署即可。6.2 图片不显示的一个隐蔽坑Tomcat 热部署导致上传文件丢失在 4.3 小节我提过getRealPath(/upload)的问题这里再延伸一下。如果你用 IDEA 热部署war explodedOn Update Action: Update resources每次触发热更新Tomcat 可能会重新创建部署目录/upload下的图片就被清掉了。这个问题在你本地开发时不明显因为热部署频率低但换到正式部署场景每次重启 Tomcat 用户上传的头像、商品图就没了非常严重。我记得有一次帮朋友调试一个农产品商城项目后台加了几张商品图刷新页面 404查了半天发现是重启后目录被重置了。从那以后我每拿到一个 SSM 项目的上传功能第一件事就是检查文件落盘路径看到getRealPath直接改成外部绝对路径// 外部存储路径不随 Tomcat 生命周期变化 private static final String UPLOAD_DIR /data/fruit-mall/upload/; // 保存文件 File targetFile new File(UPLOAD_DIR, newFileName); if (!targetFile.getParentFile().exists()) { targetFile.getParentFile().mkdirs(); } file.transferTo(targetFile); // 通过虚拟路径映射访问SpringMVC 配置类里添加 Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(/upload/**) .addResourceLocator(file:/data/fruit-mall/upload/); } }代码里的addResourceLocator指向外部磁盘路径file:前缀告诉 SpringMVC 这是文件系统路径而不是 classpath 路径末尾必须带/否则映射会失败。这样改造后图片存在项目外部重启不丢、换电脑部署只要把整个/data/fruit-mall目录拷走图片就在再也不用担心答辩演示时图片突然失踪。从那以后我每接到一个 JavaWeb 毕业设项目都会强制走一遍「打包 → 外部部署 → 重启验证图片还在不在」的流程这个习惯帮我躲过了不少临时翻车的场面希望也能帮到你。本文还有配套的精品资源点击获取