JSP+SSM网上购物商城系统设计与实现:从架构到避坑指南 📅 发布时间:2026/9/18 2:54:01 👁 浏览次数: 做Java Web项目JSP SSM这个组合几乎算得上是一代人的共同记忆了。哪怕到了今天前后端分离大行其道的时代依然有大量高校课程设计、毕业设计以及企业内部老旧系统的维护需求停留在这套技术栈上。这篇文章要聊的就是一个非常典型的SSM网上购物商城系统——它的设计思路、表结构、核心模块实现以及新手开发JSP页面时最容易踩的那些坑。这个项目适合谁两类人。一类是正在做Java Web课程设计或毕业设计的学生需要一套完整、可运行、能讲清楚原理的商城代码另一类是刚接触SSM整合、想搞清楚Spring SpringMVC MyBatis三个框架到底怎么协作的开发者。文章不会只丢给你一段代码而是把整个项目的骨架、关键实现思路、常见报错逐层拆开来讲。1. 项目整体设计与技术选型1.1 为什么2024年还在用JSP SSM先聊一个绕不开的问题JSP SSM这套组合早就不是主流新项目的第一选择了为什么还有大量商城系统、管理系统在用原因很现实。首先是教学和毕设生态的惯性。绝大多数高校的Java Web课程还在讲Servlet、JSP、三大框架或者SSM学生做课程设计和毕业设计时最熟悉的就是这套东西网上能参考的完整项目也最多。其次是维护成本。很多中小型企业的内部系统、电商后台尤其是一些传统行业的信息化系统本身就是五六年前用SSM写的后续迭代依然需要懂这套技术栈的人来维护。从学习角度来说SSM尽管上手曲线比Spring Boot陡一点但它把Spring的IOC/AOP、SpringMVC的请求流转、MyBatis的SQL管理拆得非常清楚对理解Java Web的底层运作反而更有帮助。Spring Boot把一切自动配置好了你反而看不到web.xml、XML配置文件这些曾经的中枢神经是怎么运转的了。1.2 技术选型背后的取舍逻辑这套商城系统我选择的技术栈如下前端页面JSP JSTL EL表达式 Bootstrap或者简单的原生CSS后端框架Spring SpringMVC MyBatisSSM数据库MySQL 5.7 / 8.0服务器Tomcat 8.5 / 9.0构建工具Maven 3.6开发工具IntelliJ IDEA为什么不用Spring Boot因为很多毕设硬性要求SSM。为什么不用前后端分离因为JSP本身就是服务端渲染课程设计阶段没必要引入Vue/React增加复杂度。为什么不用Redis做购物车因为对新手来说用Session或Cookie实现购物车更能理解HTTP无状态协议下的会话跟踪原理。这套选型最大的优势是所有代码都在一个工程里编译、部署、调试链路短出现问题容易排查。对应地最大的代价是前端页面和Java代码耦合度高这是JSP天生的局限接受它就行没必要过度设计。1.3 三层架构与包结构规划项目的包结构是照着经典的三层架构来拆的。建议按下面的方式组织后期维护会省很多事src/main/java ├── com.shop.controller // SpringMVC控制层 ├── com.shop.service // 业务逻辑层接口 ├── com.shop.service.impl // 业务逻辑层实现 ├── com.shop.dao // MyBatis数据访问层接口Mapper ├── com.shop.entity // 实体类POJO ├── com.shop.common // 通用工具类、常量、统一结果封装 ├── com.shop.interceptor // 拦截器登录校验等 └── com.shop.exception // 自定义异常控制层只负责接收参数、调用服务、返回视图或JSONService层处理所有业务规则例如库存扣减、订单状态流转Dao层单纯做数据持久化操作。这样分层之后三层之间通过接口依赖哪一层出了问题只需要替换对应实现不需要动其他层的代码。2. 数据库设计与会话状态处理2.1 核心数据表结构一个网上购物商城系统核心业务绕不开用户、商品、分类、购物车、订单、订单项这几张表。我在设计时遵循了一个原则能用一张表解决的问题绝对不拆两张但必须拆开的比如订单和订单项一定拆开。商品分类表category字段名类型说明idint主键自增namevarchar(50)分类名称parent_idint父分类ID0表示一级分类sortint排序权重statustinyint是否启用0停用1启用这里加了一个parent_id字段是为了支持两级商品分类比如手机数码下面挂手机、耳机只做一级分类的话可以忽略但预留这个字段以后扩展会方便很多。商品表product的设计要注意几个重点字段字段名类型说明idint主键自增namevarchar(100)商品标题category_idint所属分类IDmain_imagevarchar(255)主图URLdetailtext商品详情描述pricedecimal(10,2)单价stockint库存数量salesint销量冗余字段避免每次统计订单statustinyint上下架状态0下架1上架create_timedatetime创建时间特别注意price字段一定要用decimal而不是float或double。浮点数在涉及金额计算时会因为二进制表示问题出现精度丢失轻则显示异常重则对账出错。这是自电商系统诞生以来被反复踩的坑新手极容易忽略。用户表user除了基础的用户名、密码、手机号之外需要一个唯一索引约束用户名避免重复注册。密码一律用MD5加盐或BCrypt加密存储不要明文存数据库。订单表orders和订单项表order_item是典型的主从表结构orders订单编号、用户ID、总金额、收货人信息、订单状态、创建时间order_item订单ID、商品ID、购买数量、单价、小计金额设计时有一个非常关键的取舍order_item里单独存一份商品名称和商品单价快照而不是通过product_id实时关联查商品表。为什么因为用户下单之后如果商家修改了商品价格或名称订单的历史记录不应该跟着变。这也是一种常见的历史数据快照思路对后来的订单对账、售后处理非常重要。2.2 购物车的三种实现方案对比购物车是整个商城系统里最能体现技术选型功底的部分。我见过三种常见方案方案实现方式优点缺点Session购物车把购物车对象放HttpSession里实现简单服务端可控服务器重启丢失集群环境不共享Cookie购物车把购物车商品ID和数量序列化进Cookie不占服务端内存大小受限敏感信息不安全数据库购物车建cart表关联用户ID持久化可跨设备同步每次增删改查都要读写数据库课程设计阶段我建议用Session方式这是理解购物车逻辑成本最低的方案。后续如果有个性化需求再升级成数据库购物车业务逻辑基本可以复用。Cookie方式一般不推荐因为购物车数据存在客户端用户清掉浏览器数据就全没了体验很差。2.3 登录状态与会话跟踪SSM商城里的登录状态管理用的是Session 拦截器。核心逻辑分三步用户提交用户名和密码Service层校验通过后把用户对象放进Sessionsession.setAttribute(loginUser, user)定义一个拦截器在SpringMVC配置文件里注册让它拦截所有需要登录才能访问的路径比如购物车、结算、个人中心拦截器里判断如果Session中没有loginUser就重定向到登录页面有一个细节值得注意JSP页面里判断登录状态不要直接使用session.getAttribute()这种原始方式而是用SpringMVC拦截器处理好之后在Controller里把用户信息通过Model传给视图JSP端用EL表达式${loginUser.username}渲染这样页面代码会干净很多。3. 典型功能模块拆解3.1 用户注册与登录注册模块的逻辑流程比较直白但有两个点新手很容易做错。第一个是表单校验必须前后端都做。前端用JavaScript做非空、格式校验是为了用户体验但前端校验可以被绕过真正的校验必须在Service层做。用户名是否存在、密码长度是否满足要求这些都应该在Service层再校验一遍否则数据库中可能被写入脏数据。第二个是密码加密。用DigestUtils.md5DigestAsHex配合盐值处理数据库中不要只存一个静态的MD5否则撞库攻击很容易还原弱密码。盐值可以用用户名或随机字符串只要保证同一个用户每次加密结果一致用于登录比对即可。登录Controller的核心代码大致是这样Controller public class UserController { 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) { model.addAttribute(msg, 用户名或密码错误); return user/login; } session.setAttribute(loginUser, user); return redirect:/index; } }3.2 商品展示与分页查询商品列表页是商城访问量最大的页面分页查询做得好不好直接影响用户体验。传统做法是手动拼LIMIT offset, pageSize然后单独写一条SELECT COUNT(*)查询总记录数来计算总页数。使用MyBatis时我推荐用PageHelper插件来简化分页因为它可以在不改动SQL的前提下自动拼接分页参数。PageHelper.startPage(pageNum, pageSize); ListProduct list productMapper.selectByCategoryId(categoryId); PageInfoProduct pageInfo new PageInfo(list);一行PageHelper.startPage()之后紧跟的查询语句会自动被拦截并附加LIMIT子句查出来的list强转成Page或者用PageInfo包装就能拿到总页数、当前页码等信息然后传给JSP渲染。这里有一个特别容易踩的坑PageHelper.startPage()必须在查询之前调用而且只对接下来第一条查询语句生效。项目里Service层如果先执行了其他查询再执行目标查询分页就会失效或者分页信息跑到别的查询上排查的时候很让人头大。商品搜索功能同理。可以用商品的name字段配合LIKE CONCAT(%, #{keyword}, %)做模糊搜索遇到输入为空时就用默认条件查全部。3.3 购物车与订单结算流程这里的购物车按Session方案设计我创建一个Cart对象里面维护一个MapInteger, CartItemkey是商品IDvalue是条目对象包含商品信息、数量、小计。每次加入购物车时先检查Map里有没有这个商品有就数量加1没有就新增一个条目。结算下单是商城系统的核心难点所在原因是它涉及多个数据表的写操作必须保证事务一致性Transactional public Order createOrder(Integer userId, Cart cart, Address address) { // 1. 从购物车遍历条目计算总金额 // 2. 校验商品库存是否充足 // 3. 扣减库存更新销量 // 4. 生成订单主记录状态为待付款或已付款 // 5. 批量插入订单项详情 // 6. 清空购物车 return order; }Transactional注解是这里的关键。如果没有事务假设第3步扣库存成功后第5步插入订单项时报错了数据库里就会留下一个库存减少了但订单不存在的脏状态对真实商城来说这是致命的。支付功能在课程设计阶段一般不用对接真实支付宝或微信支付可以做一个模拟支付用户点击确认付款把订单状态从待付款改为已付款同时记录支付时间。等到有真实需求时再对接第三方支付SDK业务代码不会冲突。3.4 后台商品管理与订单处理后台管理模块是整套系统里业务最简单但工作量最大的部分主要包括管理员登录与权限拦截商品列表管理新增、编辑、上下架、删除分类管理增删改查删除前要检查该分类下是否还有商品订单管理按状态筛选订单、修改发货状态、查看订单项商品新增时涉及图片上传。SSM项目中文件上传用MultipartFile接收前端提交的文件保存路径有两种选择一种是把图片上传到Tomcat部署目录下的upload文件夹里比如${catalina.home}/webapps/upload。好处是部署简单但war包重新部署时文件容易被清理掉。另一种是把图片存到一个独立的磁盘目录比如D:/upload然后通过Tomcat的虚拟目录映射来访问。后者虽然配置多一步但文件生命周期更安全。我个人的建议是独立磁盘目录方案并把图片访问路径存到数据库。而且注意一点商品编辑时如果换了新图要把旧图片文件同步删除否则时间长了服务器上会堆满垃圾文件。4. 从零搭建SSM项目并跑起来4.1 Maven项目结构准备在IntelliJ IDEA中创建Maven项目并选择maven-archetype-webapp骨架之后需要手动补全Java源代码目录结构。很多新手在这一步就懵了IDEA生成的项目默认没有src/main/java目录需要自己右键新建目录并标记为Sources Root。之后在pom.xml里添加依赖。核心依赖有dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.3.30/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.7/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.6/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper/artifactId version5.3.0/version /dependency注意版本兼容性如果使用Tomcat 10Servlet API的包名从javax.servlet变成了jakarta.servlet网上大部分老教程仍然使用javax直接照抄容易在部署时报ClassNotFoundException。稳妥起见课程设计阶段请使用Tomcat 8.5或9.0完全兼容javax。4.2 三个关键配置文件解读Spring、SpringMVC、MyBatis三个框架整合配置文件是核心中的核心。新手最容易搞不清楚的是applicationContext.xml和spring-mvc.xml的分工。applicationContext.xml管的是Spring容器数据源、事务管理器、Service层Bean的扫描、MyBatis的SqlSessionFactory。context:component-scan base-packagecom.shop context:exclude-filter typeannotation expressionorg.springframework.stereotype.Controller/ /context:component-scan bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ /beanspring-mvc.xml管的是SpringMVC组件的扫描和视图解析。注意这里只扫描Controller和applicationContext.xml里的扫描范围错开这是SSM整合中最重要的一条配置规则我在第五章节会详细展开。context:component-scan base-packagecom.shop.controller/ bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views// property namesuffix value.jsp/ /beanweb.xml的作用是启动Spring容器、配置SpringMVC的前端控制器DispatcherServlet以及设置字符编码过滤器。中文乱码问题百分之九十都出在没配或配错这个过滤器上务必要确保它出现在所有Servlet之前且强制为UTF-8。4.3 部署到Tomcat并访问配置完上述文件后在IDEA里配置Tomcat的运行环境Run - Edit Configurations - 添加Tomcat Server - Local。Deployment面板点击加号选择shop-web:war explodedApplication context设置为/shop启动后浏览器输入http://localhost:8080/shop即可访问首页。如果页面404优先检查部署的应用上下文路径和实际访问路径是否一致如果500去IDEA控制台看异常栈绝大多数是SQL语法或Mapper映射错误。5. JSP页面开发高频问题与排查实录5.1 JSP编译后的Java类到底在哪里很多人在Idea里写JSP突然想知道Tomcat到底把JSP翻译成了什么Java类却怎么都找不到。这里直接说结论使用IDEA内置Tomcat启动时JSP编译生成的源码和class文件在C:\Users\你的用户名\AppData\Local\JetBrains\IntelliJIdea202X.X\tomcat\...\work\Catalina\localhost\项目名\org\apache\jsp目录下。这个路径和你在IDEA里配置的Tomcat物理目录不是一回事。IDEA每次启动Tomcat时会复制一份临时容器到系统临时目录真正的work目录并不在Tomcat安装目录里。找到这个路径你就能看到index_jsp.java这种编译后的类文件想排查JSP内置对象或者EL表达式解析问题会方便不少。5.2 页面显示完JSP源码而不是解析结果有时浏览器直接显示了JSP文件的源代码而不是渲染后的页面这个现象的原因几乎可以锁定为项目没有编译JSP的引擎或者Servlet容器没有正确识别.jsp后缀。最常见的场景是把项目部署到了没有JSP引擎的嵌入式Web服务器上。假如你误用Spring Boot内置的Tomcat却只有普通Tomcat的依赖而没有tomcat-embed-jasper就会出现这种情况。SSM纯war包项目里如果出现检查一下web.xml里是否配置了JspServlet的映射以及Tomcat的conf/web.xml是否被改动过。5.3 IDEA中没有JSP文件创建选项怎么办有相当一部分人使用的是IntelliJ IDEA Community版社区版这个版本本身不提供对JSP/Java Web开发的内置支持。Community版里新建文件时找不到JSP选项很正常解决方案有两种一是升级为IntelliJ IDEA Ultimate版旗舰版JSP支持是内置的。如果因为授权问题不用旗舰版也可以手动创建在WEB-INF目录下新建一个普通文件然后把扩展名改成.jspIDEA会按文本文件打开此时语法高亮会丢失但功能上不影响开发和调试。另外Idea的Settings里确实有Editor - File Types - JSP的配置入口里面可以添加识别.jsp后缀。某些精简版或老版本IDE首次创建JSP文件时没有着色去这里手动添加文件类型映射就能解决。5.4 离开页面时的提示怎么屏蔽网上关于屏蔽JSP离开页面提示搜出来的内容特别多其实这个问题跟JSP本身关系不大它是浏览器层面的事情。JSP页面上如果监听了beforeunload事件来弹确认框而你在开发调试阶段频繁刷新或者跳转会觉得这个提示特别烦人。一行代码就能解决在当前页面执行window.onbeforeunload null就行。或者更彻底一点直接在浏览器开发者工具的Console里输入window.onbeforeunload null;再回车。需要注意的是这个操作只对当前页面标签页有效刷新后监听器会重新挂载所以真正想关掉得去源代码里把绑定事件的代码注释掉。5.5 JSP个人信息展示页面的常见坑个人中心/个人信息展示页面通常要展示当前登录用户的信息常见问题是页面刷新后用户信息消失或者编辑完头像/昵称后页面数据不更新。这两个问题的根源都在于Session对象中存放的数据没有和数据库同步更新。我记得有个学员的商城项目就出现过这样的Bug用户在个人中心修改昵称后数据库里已经更新了但页面上仍然显示旧昵称直到重新登录才恢复正常。原因是更新成功后只做了userService.updateUser(user)却没有执行session.setAttribute(loginUser, newUser)。记住一个原则凡涉及用户信息修改的操作成功之后必须同步刷新Session中的用户对象否则JSP页面上通过${loginUser.nickname}取到的永远是旧数据。6. 从课程设计到真实上线还差什么6.1 安全性的三道坎SSM商城做出来能跑是第一步但离真正部署到服务器对外提供服务还有明显差距。安全性首当其冲。第一道坎是SQL注入。MyBatis的#{}和${}的区别必须清楚简单说#{}是预编译占位符安全${}是字符串拼接有注入风险。所有用户输入的值都应该走#{}只有表名、列名这类不能预编译的场景才用${}而且这些位置绝对不能直接使用前端传参。第二道坎是越权访问。后台管理页面如果没有拦截器任何用户都能通过手工输入URL直接访问管理接口。处理办法是单独建一个管理员角色标识后台所有路径都通过拦截器做角色校验要求Session中的用户对象必须带有管理员标记。第三道坎是XSS攻击。JSP页面渲染用户输入内容时注意转义。EL表达式默认不转义HTML。如果你允许用户提交商品评论评论内容里包含script标签存储后直接在页面上输出会导致脚本执行。解决方案是输出时用fn:escapeXml()或者后端做HTML标签过滤。6.2 性能优化的几个方向JSP商城在低并发场景下完全够用但如果你想在答辩的时候展示一些性能思考可以从几个方向去优化商品详情等热点数据可以用Redis做缓存把查询压力从数据库分担出去首页静态资源CSS、JS、图片设置浏览器缓存减少重复请求数据库中表加上合适的索引比如product表的category_id、orders表的user_idSQL要避免SELECT *只查询需要的字段这些点不一定在课程设计阶段全部实现但答辩时能说出优化思路比分强很多。6.3 图片上传和虚拟目录配置如果后台商品管理的图片上传功能用独立目录方式存储在Tomcat中配置虚拟目录映射。编辑conf/server.xml在Host标签内增加Context path/upload docBaseD:/shop_upload reloadabletrue/这样浏览器访问http://localhost:8080/upload/product/001.jpg时Tomcat会从D:/shop_upload/product/001.jpg读取文件。注意docBase指向的目录要存在否则启动日志会报错但不影响Tomcat主进程启动要细心留意这一点。6.4 数据库脚本和初始化数据的准备一个合格的项目必须附带完整的数据库初始化脚本。我见过太多人在答辩前才发现项目导入后发现表结构不一致、演示数据缺失导致页面白屏或查询报错。建议在项目的doc/sql目录下放两个文件shop_schema.sql建库建表语句包含字段注释shop_data.sql初始化的分类数据、测试商品、测试账号测试账号固定写一个admin/admin123和user/123456并用MD5加密后的密文存库同时在演示文档里写明如何登录。这虽然看起来是个小事但答辩现场翻车的概率一半以上都出在数据库准备环节。写在最后这套JSP SSM商城系统写下来看起来工作量不小但真正的核心逻辑其实就集中在购物车、订单、商品管理这几个模块上其他都属于标准的增删改查。对于还在做课程设计或者毕设的同学我建议把这个项目当作一条完整的业务线去理解而不是仅仅为了跑起来交差从用户注册登录到浏览商品加购物车再到下单支付最后后台发货整条链路走通了你对Java Web开发的理解会有一个非常明显的提升。最后再分享一个实用的小建议不管你的项目代码写得多完善规划时间时一定要给环境配置留出至少半天。JDK版本、Tomcat版本、Maven仓库下载、MySQL字符集这些环节任何一个出问题都可能让你在答辩前一晚熬夜到凌晨两三点。提前把开发环境和项目跑通多看几遍控制台日志这个项目就算稳稳拿下了。