1. 项目概述:SSM架构的电商平台实现方案
这个基于SSM框架的网上商城系统,是我在2018年实际交付给本地一家母婴用品连锁店的项目改造方案。当时客户需要将线下会员体系与线上销售打通,而SSM框架的轻量级特性和快速开发能力完美匹配了这个需求。系统采用经典的MVC分层设计,前端用JSP实现动态页面,后端通过Spring整合MyBatis完成业务逻辑与数据持久化,MySQL则承担了商品、订单、用户等核心数据的存储任务。
特别说明:虽然现在Spring Boot已成主流,但理解SSM框架的整合原理对掌握Java Web开发体系至关重要,这也是我坚持分享这个"老"项目的原因。
整套系统包含完整的电商功能模块:用户注册登录、商品分类展示、购物车管理、订单处理、支付对接(模拟)、后台管理等。在性能方面,我们通过连接池优化、页面静态化、缓存策略等手段,使这个教学级项目能够支撑2000+商品和日均3000次访问的基本商业需求。
2. 技术栈选型与架构设计
2.1 为什么选择SSM组合?
2016-2019年间,SSM(Spring+SpringMVC+MyBatis)是JavaWeb开发的实际标准组合。相较于早期的SSH框架,SSM具有以下显著优势:
- 配置简化:Spring的注解驱动替代了Struts的XML配置
- ORM灵活:MyBatis的SQL可控性比Hibernate更适合电商这类复杂查询场景
- 性能更优:SpringMVC的请求处理效率明显高于Struts2
在我们的商城项目中,技术栈分工明确:
- Spring:作为核心容器,管理所有Bean的生命周期,提供AOP事务支持
- SpringMVC:处理前端请求路由,实现Controller与JSP视图的交互
- MyBatis:通过Mapper接口与XML映射文件操作MySQL数据库
- JSP:配合JSTL标签库实现动态页面渲染
2.2 系统架构分层设计
项目采用标准的三层架构,但针对电商特点做了特殊优化:
表示层(JSP) ↓ 控制层(SpringMVC Controller) ↓ 业务层(Spring Service) ↓ 持久层(MyBatis Mapper) ↓ 数据库(MySQL)关键设计决策:
- 前后端耦合:没有采用前后端分离,而是用JSP直接渲染页面。这在2018年仍是主流做法,优点是开发效率高,适合小型团队。
- 会话管理:使用HttpSession存储购物车数据,而非数据库。虽然存在集群扩展问题,但对初期项目足够。
- 事务控制:在Service层通过@Transactional注解管理订单相关的事务。
3. 数据库设计与核心表结构
3.1 MySQL表关系规划
电商系统的数据库设计需要平衡范式约束与查询效率。我们最终采用部分反范式设计,主要包含以下核心表:
-- 用户表 CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(100) NOT NULL, `phone` varchar(20) DEFAULT NULL, `email` varchar(100) DEFAULT NULL, `created_at` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username_UNIQUE` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 商品表 CREATE TABLE `product` ( `id` int(11) NOT NULL AUTO_INCREMENT, `category_id` int(11) NOT NULL, `name` varchar(100) NOT NULL, `subtitle` varchar(200) DEFAULT NULL, `price` decimal(10,2) NOT NULL, `stock` int(11) NOT NULL, `status` int(11) DEFAULT '1', `main_image` varchar(255) DEFAULT NULL, `detail` text, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 订单主表 CREATE TABLE `order` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_no` bigint(20) NOT NULL, `user_id` int(11) NOT NULL, `payment` decimal(20,2) DEFAULT NULL, `status` int(10) DEFAULT '10', `payment_time` datetime DEFAULT NULL, `end_time` datetime DEFAULT NULL, `created_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `order_no_index` (`order_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;3.2 索引优化实践
针对电商查询特点,我们添加了以下关键索引:
- 商品表的
category_id和status联合索引,加速分类查询 - 订单表的
user_id和created_time倒序索引,优化用户订单列表查询 - 商品名称的全文索引,支持搜索功能
踩坑记录:最初没有为订单号(order_no)添加唯一索引,导致在并发测试时出现重复订单号。解决方法是在生成订单号时使用"时间戳+用户ID+随机数"的组合策略。
4. 核心功能模块实现
4.1 用户认证与权限控制
采用经典的RBAC模型实现权限管理,核心流程:
// SpringMVC拦截器示例 public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String uri = request.getRequestURI(); if(uri.startsWith("/admin") && !checkAdminAuth(request)){ response.sendRedirect("/login?redirect="+URLEncoder.encode(uri,"UTF-8")); return false; } return true; } private boolean checkAdminAuth(HttpServletRequest request){ User user = (User) request.getSession().getAttribute("currentUser"); return user != null && user.getRole() == UserRole.ADMIN; } }安全注意事项:
- 密码存储使用BCrypt加密,而非MD5等可逆算法
- 关键操作(如密码修改)需要二次验证
- 管理员后台采用IP白名单限制访问
4.2 商品展示与搜索
商品列表采用分页查询,MyBatis实现示例:
<!-- MyBatis Mapper XML --> <select id="selectByCategoryId" resultMap="BaseResultMap"> SELECT id,name,price,main_image FROM product WHERE category_id = #{categoryId} AND status = 1 ORDER BY id DESC LIMIT #{page.start}, #{page.limit} </select>搜索功能实现方案对比:
- 数据库LIKE查询:简单但性能差,适合小规模数据
- 全文索引:MySQL 5.6+支持,中等规模数据适用
- 外部搜索引擎:如Elasticsearch,适合大型商城
我们最终选择方案2,通过MATCH AGAINST语法实现基础搜索:
SELECT * FROM product WHERE MATCH(name,subtitle) AGAINST('婴儿车' IN NATURAL LANGUAGE MODE) LIMIT 0,104.3 购物车与订单系统
购物车数据结构设计:
public class CartItem { private Integer productId; private String productName; private String productImage; private BigDecimal currentPrice; private Integer quantity; private BigDecimal totalPrice; // getters & setters }订单生成的关键事务处理:
@Service public class OrderServiceImpl implements OrderService { @Transactional(rollbackFor = Exception.class) public Order createOrder(Integer userId, List<CartItem> cartItems) { // 1. 库存检查 checkStock(cartItems); // 2. 扣减库存 reduceStock(cartItems); // 3. 生成订单 Order order = generateOrder(userId, cartItems); // 4. 清空购物车 cleanCart(userId); return order; } }重要经验:订单号生成必须考虑并发场景,我们使用"时间戳(13位)+用户ID(后4位)+随机数(3位)"的19位数字方案,既保证唯一性又便于排查问题。
5. 性能优化实战记录
5.1 数据库连接池配置
采用Druid连接池的推荐配置:
# application.properties spring.datasource.druid.initial-size=5 spring.datasource.druid.max-active=20 spring.datasource.druid.min-idle=5 spring.datasource.druid.max-wait=60000 spring.datasource.druid.time-between-eviction-runs-millis=60000 spring.datasource.druid.min-evictable-idle-time-millis=300000 spring.datasource.druid.test-while-idle=true spring.datasource.druid.test-on-borrow=false spring.datasource.druid.test-on-return=false5.2 多级缓存实现
缓存策略设计:
- 本地缓存:使用Guava Cache缓存热门商品
- 分布式缓存:Redis缓存分类数据和用户会话
- 页面片段缓存:对商品详情页的非实时部分进行静态化
Guava Cache配置示例:
LoadingCache<Integer, Product> productCache = CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES) .build(new CacheLoader<Integer, Product>() { @Override public Product load(Integer id) throws Exception { return productMapper.selectByPrimaryKey(id); } });5.3 JSP性能调优
通过以下措施提升页面响应速度:
- 启用JSP预编译:在web.xml中配置
jsp-config - 使用JSTL替代Scriptlet:保持代码整洁
- 静态资源版本化:避免浏览器缓存问题
- 合并CSS/JS文件:减少HTTP请求
6. 典型问题排查手册
6.1 中文乱码问题解决方案
SSM项目中的编码问题主要出现在三个环节:
数据库连接:jdbc.url需要指定characterEncoding
jdbc.url=jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=UTF-8请求参数:在web.xml中添加编码过滤器
<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter>JSP页面:确保page指令设置正确
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
6.2 事务失效的常见原因
在项目中我们遇到过多次事务不生效的情况,总结主要有以下原因:
- 异常类型不匹配:默认只回滚RuntimeException,需通过
@Transactional(rollbackFor=Exception.class)修改 - 方法访问权限:非public方法的事务注解无效
- 自调用问题:类内部方法调用不会触发代理逻辑
- 数据库引擎:MyISAM不支持事务,必须使用InnoDB
6.3 并发修改冲突处理
电商系统中最典型的并发问题是超卖,我们采用三种防御方案:
乐观锁:通过version字段控制
UPDATE product SET stock=stock-1, version=version+1 WHERE id=1001 AND version=5悲观锁:在查询时加锁
@Select("SELECT * FROM product WHERE id=#{id} FOR UPDATE") Product selectForUpdate(Integer id);Redis队列:将请求串行化处理
实际测试发现,乐观锁在中等并发下表现最好,最终采用方案1作为主要防御手段。
7. 项目部署与运维要点
7.1 生产环境部署清单
标准部署需要以下组件:
- Web服务器:Tomcat 8.5+
- 数据库:MySQL 5.7+ 配置主从复制
- 缓存:Redis 4.0+ 开启持久化
- 监控:Prometheus + Grafana监控JVM指标
关键JVM参数建议:
-Xms1024m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:+UseG1GC7.2 日志收集方案
采用Logback+ELK栈实现日志集中管理:
<!-- logback-spring.xml --> <appender name="ELK" class="net.logstash.logback.appender.LogstashTcpSocketAppender"> <destination>192.168.1.100:5000</destination> <encoder class="net.logstash.logback.encoder.LogstashEncoder" /> </appender> <logger name="com.mall" level="DEBUG" additivity="false"> <appender-ref ref="ELK"/> </logger>7.3 压力测试数据
使用JMeter进行基准测试(4核8G服务器):
| 场景 | 线程数 | 平均响应时间 | 吞吐量 | 错误率 |
|---|---|---|---|---|
| 商品列表 | 100 | 238ms | 412/sec | 0% |
| 下单流程 | 50 | 856ms | 58/sec | 1.2% |
优化后通过增加Redis缓存和数据库连接池,性能提升40%以上。
8. 从SSM到Spring Boot的演进建议
虽然SSM框架现在仍在使用,但新项目建议采用Spring Boot简化配置。主要改进点:
依赖管理:通过starter简化依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>自动配置:无需手动配置DataSource等Bean
内嵌容器:无需单独部署Tomcat
Actuator监控:内置健康检查和管理端点
迁移过程中的注意事项:
- JSP支持需要额外配置
- MyBatis配置方式有所变化
- 静态资源位置默认改变
这个SSM商城项目虽然采用"传统"技术栈,但完整实现了电商核心功能。在开发过程中,最大的收获是对事务控制、并发处理和性能优化有了更深刻的理解。特别是库存扣减的并发问题,让我们意识到分布式环境下的复杂性,这也促使团队后续学习了分布式锁等进阶技术。