简介基于JavaSSM架构的水果蔬菜农产品购物商城是面向计算机相关专业学生的高分毕业设计项目。源码、数据库、使用文档与全部资料一应俱全涵盖商品展示、购物车、订单管理等典型商城模块适合用于毕业设计、课程设计或项目初期演示也便于在此基础上扩展二次开发。资源包共1575个文件大小20.4MB以png图片、css样式、js脚本、java源码、jsp页面及sql数据库脚本等为主目录结构完整可帮助快速理解SSM整合流程与商城业务实现。已有166人学习浏览代码已通过主流系统运行测试功能可用性有保障。整体资料注重工程落地与文档配套既能为答辩提供支撑也能为后续功能改造提供清晰起点。1. 这套SSM商城毕设到底给你什么每年毕设季搜索框里敲一次“基于javaSSM水果蔬菜农产品购物商城”会出来一批打包好的zip。这些压缩包名字很长里面通常是一套SSM源码、一份数据库脚本、一份使用文档以及若干用于写论文的辅助资料。被这类项目吸引不是因为它用了多前沿的技术而是因为它把“注册登录、分类浏览、商品详情、购物车、下单结算、后台管理”这条电商主链路完整接通了。数据库表和前后端代码能对上写到毕业论文里也讲得通。它的价值不是“高分”两个字而是“开箱即跑”适合两类人一是毕业设计或课程设计只需要稳定交付、能截图能演示的同学二是想借一个前后台完整的商城项目把java基础、SSM框架和数据库设计串起来复习的求职者。下面按我平时接手这类项目的习惯从环境、数据、功能到部署踩坑点把这条链路拆开讲。2. 技术选型与环境准备把SSM跑起来的最小配置2.1 为什么选SSM而不是Spring Boot上桌先给结论SSM在今天的生产环境里确实不算新但放在毕业设计这个场景里它反而是更容易讲清楚的组合。Spring Boot的自动配置把web.xml、springmvc.xml、数据源、事务管理器这些内容全部封装成了隐形逻辑新手用起来爽可答辩老师一旦追问“Controller是怎么被找到的”“数据源为什么自动就有了”学生很容易卡壳。SSM的配置是显式躺在XML里的你亲自写过一遍连DispatcherServlet的映射规则、MyBatis的Mapper扫描路径都能说出个一二三。对比下来SSM和Spring Boot在毕设场景的差异很直观配置方式上SSM以XML显式声明为主Spring Boot靠自动配置学习曲线上SSM边界清晰、分层明显Spring Boot上手快但封装较深答辩展开度上SSM可以从web.xml一路拆到mapper.xmlSpring Boot则容易被“被starter包掉了”一句话终结可复现性上SSM对JDK、Tomcat、MySQL版本更敏感但这正好促使你去理解环境本身。结论是如果你的目标是用最少的时间把商城项目做出来提交验收选SSM不是因为技术领先而是因为它稳定、资料多、代码结构和书上的Java Web课程几乎一致。2.2 Maven依赖与JDK/Tomcat/MySQL版本搭配解压源码包之后第一步永远不是看代码而是确认本机环境。我常推荐给毕设的兼容组合是JDK 1.8、Tomcat 8.5、MySQL 5.7或8.0、Maven 3.6.3。JDK不建议直接上17或21因为不少老SSM项目里的cglib代理和JSP编译方式在高版本JDK上会报各种奇怪的模块访问错误为了一个毕设去折腾这些不值得。Maven依赖里先把最核心的坐标确认清楚再谈其他。下面这组依赖是能让Spring容器、SpringMVC和MyBatis三条线同时打通的最小子集dependencies dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.3.24/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.11/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.7/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.30/version /dependency /dependencies这里有个容易踩的版本对应关系mybatis-spring 2.x必须配合mybatis 3.5以上使用如果压箱底资料里给的是mybatis-spring 1.x配mybatis 3.4运行时会频繁出现Mapper接口无法注入的报错。spring-webmvc选5.3.x是因为它对javax.servlet命名空间兼容性最好也最容易和Tomcat 8.5配合。至于druid连接池、jstl、servlet-api这些等spring和mybatis跑通后再按使用文档补一次引入太多依赖反而加大排查难度。补充一句如果你连java环境变量配置都没做过先打开系统环境变量界面把JAVA_HOME指向JDK安装根目录再把%JAVA_HOME%\bin加入Path这一步做错后面Tomcat根本起不来。2.3 JDBC连接配置与Druid连接池参数商城项目肯定离不开数据库而SSM里面数据库连接的入口就在jdbc.properties。我见过太多第一次跑SSM的人卡在这一步报错信息五花八门但其实八成是驱动类和url格式不对。以MySQL 8.0为例标准配置是这样jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/fruit_market?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.password123456 druid.initialSize5 druid.maxActive20连接池我一般选用Druid不是因为功能用得有多深而是它的监控面板和报错信息对新手更友好。参数方面initialSize5表示启动时预热5个连接maxActive20控制最大连接数对毕设这种十几个并发都到不了的场景完全够用。如果你是MySQL 5.7驱动类可以写成com.mysql.jdbc.Driver但URL里同样要保留characterEncodingUTF-8和serverTimezone否则会出现时间字段查出来差8小时或者中文写入后变成问号。密码这里请替换成你自己本机的MySQL密码不要用压缩包使用文档里的原值很多包直接写着root/123456而你自己机器的MySQL可能设了别的密码。连接池配置好以后启动Tomcat如果还报数据库连接失败问题大概率出在驱动jar包没发布到WEB-INF/lib下这个稍后会在部署避坑章细说。3. 数据库设计六张表撑起一条完整的购物链路3.1 从页面操作倒推表结构用户、分类、商品、购物车、订单我拿到任何商城项目的第一件事不是看代码而是先打开SQL脚本看表结构。设计得好的商城数据表一定是从用户操作流程倒推出来的用户要注册登录所以有用户表打开首页要看到分类所以有分类表点击分类看到商品列表所以有商品表点击加入购物车所以有购物车表提交订单结算所以有订单主表和订单明细表。这就是六张核心表缺一张整条链路都接不上。各表的分工非常清晰user表存用户账号、密码、手机号、角色字段其中角色字段区分普通买家和后台管理员category表存水果、蔬菜、粮油干货这些分类product表存商品名称、价格、库存、图片路径、所属分类IDcart表记录哪个用户加了哪个商品、数量多少orders表存一笔订单的整体信息比如订单号、总金额、状态order_item表存这笔订单里具体买了哪些商品每个商品快照一份名称和价格。这里最值得注意的细节是商品价格不能直接去关联product表查而必须在order_item里冗余一份product_price否则将来商品改价历史订单的金额就对不上了。3.2 订单与订单项拆表一对多关系在DDL里如何落地订单主表和订单明细表拆开是商城数据库设计里最经典的一对多关系。一次下单可能在购物车里选了苹果、上海青、土鸡蛋三样东西orders表只记录这一单的订单号和总金额order_item表则拆成三行每行对应一个商品。用两张表而不是一张表的原因是订单本身的信息如收货地址、支付状态、下单时间不需要在每一个商品行上重复存储而每个商品购买的数量和当时的成交价又无法塞进一个固定字段里。下面是这两个核心表的建表SQL可以直接拿去对照现有脚本CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 订单编号, user_id INT NOT NULL COMMENT 下单用户ID, total_amount DECIMAL(10,2) NOT NULL COMMENT 订单总额, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待付款 1已付款 2已发货 3已完成, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 下单时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间 ); CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL COMMENT 所属订单ID, product_id INT NOT NULL COMMENT 商品ID, product_name VARCHAR(100) NOT NULL COMMENT 商品名称快照, product_price DECIMAL(10,2) NOT NULL COMMENT 成交单价快照, num INT NOT NULL COMMENT 购买数量, subtotal DECIMAL(10,2) NOT NULL COMMENT 小计金额 );这里有一个面试官和答辩老师都爱问的细节金额为什么用DECIMAL(10,2)而不是float或者double因为浮点数在计算机里是二进制近似存储0.1加0.2会变成0.30000000000000004金额一旦累加多次误差会越滚越大。DECIMAL是定点数按十进制精确存储加上(10,2)限定总长度10位、小数2位就保证了订单金额精确到分。对应到Java代码里接收这两个字段的实体类也建议用BigDecimal而不是Double避免从数据库到Java对象这一层又把精度丢失掉。3.3 分类与商品示例数据让商城首次打开就有内容一套空荡荡的商城是没法演示的所以建完表之后必须灌入分类和商品数据。分类表通常先固定几条比如新鲜水果、时令蔬菜、粮油干货、禽蛋肉品这正好对应标题里的“水果蔬菜农产品”。插入语句可以用下面这段INSERT INTO category (category_name, sort) VALUES (新鲜水果, 1), (时令蔬菜, 2), (粮油干货, 3), (禽蛋肉品, 4); INSERT INTO product (category_id, name, price, stock, image, description) VALUES (1, 红富士苹果, 6.80, 200, /images/product/apple.jpg, 产地直供脆甜多汁), (2, 本地上海青, 3.50, 300, /images/product/greens.jpg, 当日采摘), (4, 农家土鸡蛋, 19.90, 100, /images/product/eggs.jpg, 散养谷物喂养);商品表里的image字段只存图片路径不要存二进制图片内容否则数据库会变得异常臃肿。如果你手上的源码包没有附带图片资源启动后页面会出现裂图解决办法是在Tomcat的webapps目录下建一个images文件夹放进几张测试图再保证路径和数据库里写的一致就行。库存字段stock建议直接给一个像200、300这样的初始值方便后面演示下单扣库存的效果。数据量控制在20个商品以内最合适太少页面显得空太多又会让分页插件的效果展示不出来。3.4 建表时的四个约定自增主键、逻辑外键与金额精度看过太多份毕设SQL脚本之后我总结出四个比较省心的建表约定建议你拿到源码后也按这个标准检查一遍。第一主键全部用INT自增不要用UUID或业务号做主键自增主键写入性能好URL里也不会暴露业务数据特征。第二表与表之间用逻辑外键而不是物理外键比如order_item表里有个order_id但这个字段不需要在DDL里声明FOREIGN KEY删除订单、商品时不会被外键约束卡住性能也更好。第三金额字段统一DECIMAL(10,2)Java侧对应BigDecimal这条在上面已经说过但做项目时最容易被忽略的恰恰是实体类里仍然写成了double。第四所有状态字段用TINYINT并加COMMENT比如订单状态0待付款、1已付款而不是存一个没人看得懂的varchar字符串。这四个约定遵守下来后续的数据增删改查操作会顺畅很多写论文的数据字典部分也更体面。4. 核心功能实现商品分页、登录拦截、购物车与下单事务4.1 商品分页PageHelper的引入、调用与count查询商城首页和商品列表页最核心的功能是分页。手写LIMIT分页本身不复杂但SSM项目里每次都要手动算offset、维护total代码写起来又臭又长。常见做法是引入PageHelper这个分页插件它基于MyBatis拦截器实现在你执行查询之前自动拼接LIMIT语句同时自动执行一条count查询拿到总记录数。页面上只需要调用下面两个方法public PageInfoProduct pageProducts(Integer categoryId, String keyword, Integer pageNum, Integer pageSize) { PageHelper.startPage(pageNum, pageSize); ListProduct list productMapper.selectByCondition(categoryId, keyword); return new PageInfo(list); }ListProduct selectByCondition(Param(categoryId) Integer categoryId, Param(keyword) String keyword);对应SQL是select * from product where category_id #{categoryId} and name like concat(%, #{keyword}, %)注意like拼接要用concat函数而不是字符串拼接符否则会有SQL注入风险。参数说明有三个关键点startPage之后必须紧跟真正要分页的查询中间不能穿插其他SQL操作否则分页会作用到错误的查询上pageNum表示当前页从1开始pageSize表示每页条数商品网格布局一般设8或12比较美观PageInfo对象里封装了list、total、pageNum、pageSize、navigatePages等字段JSP页面直接用pageInfo.list遍历商品用pageInfo.total显示总记录数即可。如果你的源码没有用PageHelper而是手写limit建议也不要急着重构能跑通就保留答辩时反而多了一个可以手写讲解分页原理的素材。4.2 登录检查与拦截器保护后台页面和购买动作商城必然有需要登录才能访问的资源比如购物车、结算页、个人订单列表、后台管理页。SSM里常见的做法是写一个HandlerInterceptor在请求进入Controller之前先检查session里有没有用户对象。下面是登录拦截器的核心代码public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object loginUser request.getSession().getAttribute(loginUser); if (loginUser null) { response.sendRedirect(request.getContextPath() /login); return false; } return true; } }拦截器要在spring-mvc.xml里注册才会生效配置时注意只拦截真正需要保护的路径mvc:interceptors mvc:interceptor mvc:mapping path/cart/**/ mvc:mapping path/order/**/ mvc:mapping path/admin/**/ mvc:exclude-mapping path/static/**/ bean classcom.example.interceptor.LoginInterceptor/ /mvc:interceptor /mvc:interceptors说明两个容易翻车的点一是注册时一定要放行静态资源上面的exclude-mapping就是干这个的否则css、js、图片全部被拦截页面白板一片二是登录请求本身要排除在拦截范围之外比如/login这个路径不能拦否则用户还没登录就被重定向到登录页形成一个死循环。用拦截器而不是Filter的原因在于拦截器是Spring容器管理的Bean在preHandle里可以注入UserService去查数据库而Filter处于Web容器层要拿Spring的Bean还得绕一圈。另外密码存储如果源码里是明文建议改成MD5加盐的方式或者至少告诉老师在答辩里怎么解释权限控制方案。4.3 购物车加购先查再插入避免重复条目覆盖数量购物车模块是最容易被做成“表面能用”的功能。很多源码的加购逻辑是拿到productId直接insert一条购物车记录结果用户对同一个商品点了两次加购购物车里出现两行相同商品数量还不叠加。正确做法是先按userId和productId查一次看这个商品在购物车里是否已存在存在则更新数量不存在才插入新记录public void addToCart(Integer userId, Integer productId, Integer num) { Cart cart cartMapper.selectByUserIdAndProductId(userId, productId); if (cart null) { Cart newCart new Cart(); // 这里设置 userId、productId、num、createTime cartMapper.insert(newCart); } else { cartMapper.updateNumById(cart.getId(), cart.getNum() num); } }select idselectByUserIdAndProductId resultTypecom.example.entity.Cart select id, user_id, product_id, num from cart where user_id #{userId} and product_id #{productId} /select这段逻辑并不难但它是商城数据一致性的典型缩影。更进一步购物车页面上显示的商品数量角标应该用sum(num)累加而不是count(*)数行数否则用户买了3件苹果、2件青菜角标显示的却是2而不是5。这个细节代码review的时候很容易被漏掉但演示效果差别很大。4.4 下单事务扣库存、写订单与防超卖的一个SQL下单是整个商城最需要严谨对待的环节。一个完整的下单动作包含四步生成订单主表、写入订单明细、扣减商品库存、清空购物车。这四步里任何一步失败都不能让其他步骤落库所以必须用事务把它们包起来Transactional(rollbackFor Exception.class) public Integer createOrder(OrderCreateDTO dto) { // 1. 生成订单主表状态为待付款 // 2. 循环商品列表写 order_item计算 totalAmount // 3. 调用 stockMapper.deductStock(productId, num) // 4. 删除购物车中已下单的商品 }扣减库存这一步常见做法是前端先查库存再决定能不能买但高并发下先查再扣会有时间差用户A查完库存还有1件用户B也查到1件两人同时下单就可能超卖。更稳的做法是让数据库在一条UPDATE语句里同时完成扣减和判断update iddeductStock update product set stock stock - #{num} where id #{id} and stock #{num} /update这条SQL返回的int值表示影响行数如果返回1说明扣减成功返回0说明库存不足代码里据此抛出异常回滚整个事务。它本质上是靠where条件里的stock #{num}做了乐观锁的思路对毕设项目来说已经足够不需要引Redis和分布式锁把复杂度拉高。事务这块还要提醒一个高频坑Transactional注解加在Controller同类的私有方法上是无效的Spring事务基于代理机制必须通过外部Bean调用才会生效自查时优先确认createOrder是被Service外部调用的而不是内部方法直接调用。5. 部署避坑从端口占用到中文乱码的五个排查点5.1 启动白屏或窗口一闪而过java环境变量与Tomcat日志顺序现象双击Tomcat的startup.bat后窗口一闪而过浏览器访问localhost:8080打不开。这基本不是项目代码的问题而是Tomcat压根没起来。原因九成出在JAVA_HOME环境变量没配置或者配置时路径里带了空格、指向了JRE而不是JDK。解决方法是重新设置JAVA_HOME为JDK安装根目录比如C:\Program Files\Java\jdk1.8.0_202Path里增加%JAVA_HOME%\bin。改完环境变量后不要急着双击startup.bat改在tomcat/bin目录下打开cmd执行catalina.bat run这样Tomcat会在当前窗口前台运行任何报错都会直接打印出来而不是闪退。如果日志提示ClassNotFoundException再看是不是项目没有正常部署到webapps下。看日志的顺序也很重要先看catalina.out或localhost.yyyy-MM-dd.log最后再看控制台控制台的输出往往经过IDEA包装信息不全。5.2 数据库连接失败驱动包、时区与MySQL权限现象Tomcat能启动但访问首页或登录功能时页面报500后台堆栈抛CommunicationsException或Access denied。原因通常有三个第一是MySQL驱动jar没有复制到WEB-INF/lib下Maven依赖只在编译期有效运行期Tomcat要用lib目录里的jar第二是驱动类名写错MySQL 8.0必须用com.mysql.cj.jdbc.Driver5.7用com.mysql.jdbc.Driver两代驱动类名不能混用第三是URL缺少serverTimezone参数8.0以上版本会强制校验时区。解决方式是按顺序排查先看lib下有没有mysql-connector-java的jar再看jdbc.properties里的驱动类和URL是否搭配。这里特别提一句压缩包里的使用文档很多文档是作者在他自己电脑环境写的数据库版本、密码、端口都未必和你本机一致照着抄之前先核对一遍。5.3 tomcat端口被占用8080被抢的处理方法现象Tomcat启动时窗口报Address already in use: JVM_Bind:8080或者另一个项目还能正常启动但当前项目永远卡在初始化。原因很简单8080端口被其他进程占用常见的是另一个Tomcat实例、IDEA内嵌容器或者其他开发工具。解决办法分两步先执行netstat -ano | findstr 8080找到占用端口的PID再打开任务管理器结束对应进程如果这个端口确实动不得就改Tomcat的conf/server.xml把Connector节点里的port改成8081或8082。改完端口后使用文档里所有写localhost:8080的地方都要同步改否则会一直打不开页面还找不到原因。不建议改成80端口Windows下监听80需要管理员权限跑起来又是一堆额外的权限问题。5.4 中文乱码JSP、请求参数、数据库三处同时查现象页面标题中文变成问号往数据库里insert的商品名称乱码控制台打印日志乱码。这几乎是SSM商城第一次部署必踩的坑原因不是某一个环节错而是JSP页面、Tomcat解析请求、数据库连接串三处编码不统一。解决思路是全部统一成UTF-8。JSP文件顶部写上% page contentTypetext/html;charsetUTF-8 %web.xml里配置CharacterEncodingFilter并设置forceEncoding为true强制请求和响应都走UTF-8。数据库连接串里保留characterEncodingUTF-8建库时指定utf8mb4字符集。如果数据已经写入乱码只能先导出再改字符集重新导入没有后悔药可吃。后面接新项目时我习惯把这三处编码验证放在连数据库之前这样能避免后面所有页面都得返工。5.5 页面404与静态资源全部丢失视图解析器前缀与资源映射现象访问首页接口返回200但页面上的CSS、JS、图片全部加载失败或者Controller正常执行跳转JSP时却提示404。前者的原因是DispatcherServlet拦截了所有请求包括静态文件而spring-mvc.xml里没有放行静态资源解决方法是加上静态资源映射mvc:resources mapping/static/** location/static/或者配置default-servlet-handler让Tomcat默认处理静态资源。后者的原因是视图解析器的前缀配错InternalResourceViewResolver的prefix通常写成/WEB-INF/jsp/suffix写成.jsp而你的页面实际放在另一个目录下。查看WEB-INF下页面的真实位置让配置和目录结构一致404基本都能解决。这里再提醒一句JSP文件放在WEB-INF下是正常且更安全的做法浏览器无法直接访问WEB-INF内部文件只能通过Controller转发访问这本身不是配置错误。6. 答辩与面试延伸把源码讲成自己的项目项目能跑只是第一步真的到了毕业设计答辩或者面试现场老师问的不是“你下载了什么”而是“你做了什么”。我的习惯是拿到这类源码后不要急着交差先做三个整理动作。第一按业务链路画出流程用户注册登录、浏览商品、加入购物车、下订单结算、后台管理商品和订单每一步对应的Controller、Service、Mapper分别是谁源码里这些文件往往命名很规范顺着类名看一遍就能串起来。第二把数据库六张表的关系用一句话讲清“一个用户有多个订单一个订单包含多个订单明细一个订单明细对应一个商品购物车是用户和商品的关联表。”这句话比背十页PPT都管用。第三把核心模块的高频追问过一遍下面这张表可以作为自查清单。答辩追问推荐答题方向为什么选SSM框架分层清晰、配置显式、便于理解Java Web请求链路MyBatis中#{}和${}的区别#{}预编译防SQL注入${}是字符串拼接有注入风险分页逻辑怎么实现的PageHelper基于MyBatis拦截器自动拼接limit并执行count商品金额为什么用BigDecimal浮点数精度丢失金额必须用定点数精确表示下单时库存怎么控制乐观锁思路update where stock num判断影响行数Transactional什么时候回滚抛出运行时异常或主动抛出Exception且rollbackFor指定才能回滚上面这些问题同时也是Java面试八股文里的经典题一个商城项目能把这几个点讲透比简历上写“精通微服务”更有说服力。第三件值得做的事是找一个点往里深挖一小步。比如把下单接口的日志补全每次创建订单打印订单号、用户ID、商品明细答办时和面试官聊“分布式系统里排查问题先从日志链路下手”这个思路很容易留下好印象。不推荐去学Redis缓存或MQ异步削峰一个SSM商城硬塞微服务组件后期给自己挖的坑比加分点多得多。这些年我接手过不少打包源码第一次打开一个商城项目时也是先删掉一堆用不上的文件然后照着数据库脚本把每张表过一遍再在本地把Tomcat和MySQL跑通。这个过程很枯燥但正是这些重复动作让我后来拿到再普通的项目也能快速进入状态。如果你现在正对着一个压缩包不知所措建议你也照着这个顺序走一遍环境、数据库、核心链路、部署排错。先把这条路走通再谈改成自己的东西希望帮到你。本文还有配套的精品资源点击获取
