ARTICLE DETAIL

资讯详情

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

SSM鞋子商城实战:Java Web最小可行架构搭建与优化

SSM鞋子商城实战:Java Web最小可行架构搭建与优化 简介这是一套完整的Java Web实战项目源码面向Java初学者与SSM框架学习者用于掌握电商类系统开发全流程。项目基于SpringSpringMVCMyBatis原文Mbytes应为MyBatis笔误构建后端前端采用JSPBootstrapLayuijQueryecharts实现响应式界面配合MySQL 5.7数据库完整覆盖用户端购物、订单、地址管理及管理员端商品、订单、轮播图、评价等核心模块。资源包含1095个文件涵盖87个Java业务类、62个JSP页面、364张商品图片、173个编译后class文件、125个XML配置文件及68个JS脚本整体141.06MB结构清晰、模块解耦明确便于分层理解MVC架构与前后端交互逻辑。目前已有325人下载学习附带完整SQL建表语句、IDEA/Eclipse兼容配置及Tomcat部署说明开箱即用适合课程设计、毕业设计或SSM技术栈进阶实践。1. 鞋子商城系统为什么一个“老派”Java Web项目反而成了新人练手的黄金靶场你可能在招聘JD里见过它「熟悉SSM框架有电商类Web项目经验者优先」也可能在毕设选题表上刷到它「基于JSPMySQL的鞋子商城系统」甚至刚在IDEA里新建完一个空Web项目就卡在web.xml怎么配、DispatcherServlet扫不到Controller、Layui表格死活不渲染数据——这根本不是过时技术而是一套被时间反复验证过的、可拆解、可调试、可闭环的Java Web最小可行架构。它不追求Spring Boot自动装配的丝滑却把MVC分层、DAO封装、事务边界、前后端数据流转这些核心能力全摊开在眼皮底下。尤其对刚学完Servlet和JDBC、正卡在「写了代码但不知道哪一环断了」阶段的开发者这个项目就是黑匣子的X光片JSP是视图层的显微镜MyBatis是SQL执行的探针ECharts是数据反馈的示波器而LayuiBootstrap组合则是前端交互的万用表。它不教你写高并发秒杀但能让你亲手调通从用户点击「加入购物车」到MySQLcart_item表多出一条记录的完整链路——这才是真正能写进简历的「可验证能力」。2. 搭建骨架用最简路径跑通SSM三层结构绕开IDEA新建JSP项目的经典翻车点2.1 创建Maven Web项目并锁定SSM核心依赖版本不要用IDEA的「New Project → Web Application」向导——它默认生成的web.xml版本太老且不带Maven坐标后续加MyBatis或Spring MVC极易冲突。我习惯手动创建标准Maven Web Archetypemvn archetype:generate \ -DgroupIdcom.shoesmall \ -DartifactIdshoes-mall \ -DarchetypeArtifactIdmaven-archetype-webapp \ -DinteractiveModefalse然后在pom.xml中精准注入SSM三件套注意版本兼容性Spring 4.3.x MyBatis 3.4.x Spring MVC 4.3.x 是JSP项目最稳组合!-- Spring核心 -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version4.3.29.RELEASE/version /dependency !-- Spring MVC -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version4.3.29.RELEASE/version /dependency !-- MyBatis -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.4.6/version /dependency !-- MyBatis-Spring整合包 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version1.3.2/version /dependency !-- MySQL驱动务必用5.1.478.x驱动在JSP项目中常报时区错误 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.47/version /dependency !-- JSTL支持JSP页面EL表达式和标签库必需 -- dependency groupIdjstl/groupId artifactIdjstl/artifactId version1.2/version /dependency关键逻辑说明Spring 4.3.x 是最后一个全面兼容JSPServlet 3.0规范的主版本Spring 5已弱化对JSP的支持MyBatis 3.4.6 与 Spring 4.3 的SqlSessionFactoryBean兼容性最佳避免出现No qualifying bean of type org.apache.ibatis.session.SqlSessionFactorymysql-connector-java 5.1.47能规避serverTimezoneUTC等时区报错这是新手在applicationContext.xml里配DriverManagerDataSource时90%会踩的坑。2.2 配置web.xml让DispatcherServlet接管所有请求而非被Tomcat默认Servlet劫持很多新手以为web.xml只是个摆设其实它是JSP项目的生命线。必须明确声明DispatcherServlet的加载顺序和URL映射?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 !-- 加载Spring根容器 -- context-param param-namecontextConfigLocation/param-name param-valueclasspath:applicationContext.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener !-- Spring MVC前端控制器 -- servlet servlet-namespringmvc/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:springmvc-servlet.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namespringmvc/servlet-name url-pattern//url-pattern !-- 关键必须是/不是/*.do -- /servlet-mapping !-- JSP页面静态资源放行否则Layui CSS/JS 404 -- servlet-mapping servlet-namedefault/servlet-name url-pattern*.js/url-pattern /servlet-mapping servlet-mapping servlet-namedefault/servlet-name url-pattern*.css/url-pattern /servlet-mapping servlet-mapping servlet-namedefault/servlet-name url-pattern*.png/url-pattern /servlet-mapping servlet-mapping servlet-namedefault/servlet-name url-pattern*.jpg/url-pattern /servlet-mapping /web-app参数说明url-pattern//url-pattern是生死线若写成*.do则/admin/index.jsp这类直接访问JSP的请求会被DispatcherServlet拦截并抛出404因它找不到对应Controller而/表示接管所有请求再由内部InternalResourceViewResolver将逻辑视图名解析为真实JSP路径defaultservlet映射确保Layui、Bootstrap的静态文件不被Spring MVC拦截否则控制台会疯狂报GET /layui/css/layui.css 404load-on-startup1保证DispatcherServlet在应用启动时立即初始化避免首次请求时编译JSP超时。2.3 编写第一个Controller并返回JSP视图验证MVC链路是否打通创建com.shoesmall.controller.IndexControllerpackage com.shoesmall.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; Controller public class IndexController { RequestMapping(/index) public String showIndex(Model model) { model.addAttribute(welcomeMsg, 欢迎来到鞋子商城); model.addAttribute(shoeCount, 127); // 模拟商品总数 return index; // 对应 /WEB-INF/jsp/index.jsp } }在/WEB-INF/jsp/index.jsp中接收数据% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !DOCTYPE html html head title鞋子商城首页/title link relstylesheet href${pageContext.request.contextPath}/layui/css/layui.css /head body div classlayui-container h1${welcomeMsg}/h1 p当前共有 strong${shoeCount}/strong 款鞋子/p button classlayui-btn onclicklocation.href${pageContext.request.contextPath}/shoe/list查看全部商品/button /div script src${pageContext.request.contextPath}/layui/layui.js/script /body /html关键验证点启动Tomcat后访问http://localhost:8080/shoes-mall/index若看到「欢迎来到鞋子商城当前共有127款鞋子」说明Controller→Model→JSP全流程已通${pageContext.request.contextPath}是JSP中获取项目上下文路径的唯一可靠方式硬编码/shoes-mall会导致部署到ROOT路径时所有链接失效taglib声明必不可少否则${shoeCount}无法解析——这是新手JSP页面空白的头号原因。3. 数据落地MySQL建模与MyBatis CRUD用「鞋子」实体讲透一对多关联设计3.1 鞋子商城核心表设计聚焦业务本质拒绝过度范式化别一上来就搞10张表外键级联。先抓住三个刚性实体shoe鞋子主表、shoe_size尺码库存、order_master订单主表。以下是经过3个项目验证的最小可用表结构含中文注释直接可导入Navicat-- 鞋子主表 CREATE TABLE shoe ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键ID, name VARCHAR(100) NOT NULL COMMENT 鞋子名称, brand VARCHAR(50) DEFAULT COMMENT 品牌, price DECIMAL(10,2) NOT NULL COMMENT 售价, img_url VARCHAR(255) DEFAULT COMMENT 主图URL, status TINYINT DEFAULT 1 COMMENT 状态1上架0下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT鞋子主表; -- 尺码库存表一对多一双鞋有多个尺码 CREATE TABLE shoe_size ( id BIGINT PRIMARY KEY AUTO_INCREMENT, shoe_id BIGINT NOT NULL COMMENT 关联shoe.id, size_code VARCHAR(10) NOT NULL COMMENT 尺码编码如36,37,38, stock INT DEFAULT 0 COMMENT 库存数量, sale_price DECIMAL(10,2) DEFAULT 0.00 COMMENT 该尺码促销价NULL则取shoe.price, INDEX idx_shoe_id (shoe_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT鞋子尺码库存表; -- 订单主表 CREATE TABLE order_master ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 订单号格式202405201023456789012345, user_name VARCHAR(50) NOT NULL COMMENT 买家姓名, total_amount DECIMAL(10,2) NOT NULL COMMENT 订单总金额, status TINYINT DEFAULT 0 COMMENT 状态0待支付1已支付2已发货3已完成, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单主表;设计 rationaleshoe_size表独立存在而非在shoe表中用JSON存尺码是因为① 需要按尺码精确扣减库存UPDATE shoe_size SET stock stock - 1 WHERE shoe_id ? AND size_code ?② 支持不同尺码不同售价如38码打8折③ 避免JSON字段无法建立索引导致查询慢order_no用字符串而非自增ID是为防止订单号被爬虫遍历10001,10002…同时便于接入支付平台所有DATETIME字段显式声明DEFAULT CURRENT_TIMESTAMP避免Java端new Date()时区转换错误。3.2 MyBatis映射用ResultMap解决shoe与shoe_size的一对多嵌套查询在ShoeMapper.xml中定义关联查询绝不使用N1查询即先查shoe再对每条shoe循环查shoe_size!-- 查询鞋子列表同时加载其所有尺码 -- select idselectShoeWithSizes resultTypecom.shoesmall.entity.Shoe SELECT s.id, s.name, s.brand, s.price, s.img_url, s.status, s.create_time, ss.id AS size.id, ss.size_code AS size.sizeCode, ss.stock AS size.stock, ss.sale_price AS size.salePrice FROM shoe s LEFT JOIN shoe_size ss ON s.id ss.shoe_id WHERE s.status 1 ORDER BY s.create_time DESC /select对应的Shoe.java需包含ListShoeSize属性并在ShoeSize.java中声明shoeIdpublic class Shoe { private Long id; private String name; private String brand; private BigDecimal price; private String imgUrl; private Integer status; private Date createTime; // 一对多关联一双鞋有多个尺码 private ListShoeSize sizes; // getter/setter略 // 构造函数、toString等... } public class ShoeSize { private Long id; private Long shoeId; // 外键指向shoe.id private String sizeCode; private Integer stock; private BigDecimal salePrice; // getter/setter... }关键技巧SQL中用AS size.xxx别名MyBatis会自动将结果映射到Shoe.sizes集合中的ShoeSize对象属性LEFT JOIN确保即使某双鞋暂无尺码记录shoe_size为空也能查出shoe基本信息避免漏数据若需分页必须在SQL层做LIMIT如LIMIT #{offset}, #{limit}而非Java内存分页——MyBatis的RowBounds在关联查询时会严重失真。3.3 Service层事务控制用Transactional保证「下单扣库存」原子性订单创建必须同时完成两件事① 插入order_master② 更新shoe_size.stock。任一失败都应回滚Service public class OrderService { Autowired private OrderMapper orderMapper; Autowired private ShoeSizeMapper shoeSizeMapper; /** * 创建订单含库存校验与扣减 * param orderVo 包含shoeId, sizeCode, quantity */ Transactional(rollbackFor Exception.class) public void createOrder(OrderVO orderVo) throws Exception { // 1. 校验库存SELECT FOR UPDATE 锁住该尺码行 ShoeSize currentSize shoeSizeMapper.selectByShoeIdAndSize( orderVo.getShoeId(), orderVo.getSizeCode()); if (currentSize null || currentSize.getStock() orderVo.getQuantity()) { throw new RuntimeException(库存不足); } // 2. 扣减库存UPDATE语句 int updated shoeSizeMapper.updateStock( orderVo.getShoeId(), orderVo.getSizeCode(), orderVo.getQuantity() ); if (updated ! 1) { throw new RuntimeException(库存扣减失败); } // 3. 创建订单 OrderMaster order new OrderMaster(); order.setOrderNo(generateOrderNo()); order.setUserName(orderVo.getUserName()); order.setTotalAmount(calculateTotal(orderVo)); order.setStatus(0); // 待支付 orderMapper.insert(order); } private String generateOrderNo() { return LocalDateTime.now().format(DateTimeFormatter.ofPattern(yyyyMMddHHmmssSSS)) RandomStringUtils.randomNumeric(6); } }事务边界说明Transactional必须标注在public方法上且该方法由Spring代理调用即不能在同一个Service内自调用SELECT FOR UPDATE是关键在shoe_size表上对目标尺码行加行锁防止超卖两个并发请求同时读到stock10各自扣减1后变成8实际应为7rollbackFor Exception.class确保所有异常都触发回滚包括RuntimeException和Checked Exception。4. 前端可视化LayuiBootstrapECharts三剑合璧把「鞋子销量」变成可交互图表4.1 Layui数据表格用Ajax加载鞋子列表避开JSP内置循环的性能黑洞JSP的c:forEach在数据量100条时会明显卡顿。改用Layui Table通过Ajax异步加载!-- /WEB-INF/jsp/admin/shoe-list.jsp -- div classlayui-fluid table classlayui-table lay-data{url:/shoe/listData, page:true, limit:10} lay-filtershoeTable thead tr th lay-data{field:id, width:80}ID/th th lay-data{field:name, width:200}名称/th th lay-data{field:brand}品牌/th th lay-data{field:price, width:120}价格/th th lay-data{field:status, width:100, templet:#statusTpl}状态/th th lay-data{field:createTime, width:180}上架时间/th th lay-data{width:180, align:center, toolbar:#barDemo}操作/th /tr /thead /table !-- 状态模板 -- script typetext/html idstatusTpl {{# if(d.status 1){ }} span classlayui-badge layui-bg-green上架/span {{# } else { }} span classlayui-badge layui-bg-gray下架/span {{# } }} /script !-- 工具栏 -- script typetext/html idbarDemo a classlayui-btn layui-btn-xs lay-eventedit编辑/a a classlayui-btn layui-btn-xs layui-btn-danger lay-eventdelete删除/a /script /div script layui.use([table, layer], function(){ var table layui.table; var layer layui.layer; // 监听工具条事件 table.on(tool(shoeTable), function(obj){ var data obj.data; if(obj.event delete){ layer.confirm(确定删除, function(index){ $.post(/shoe/delete, {id: data.id}, function(res){ if(res.code 0){ layer.msg(删除成功); obj.del(); // 删除对应行 } else { layer.msg(res.msg); } }); }); } }); }); /scriptLayui优势点lay-data{url:/shoe/listData}自动发起GET请求后端只需返回JSON无需JSP渲染templet模板支持条件渲染比JSP EL表达式更直观toolbar列支持自定义按钮事件绑定解耦清晰分页由Layui自动处理后端/shoe/listData接口只需接收page和limit参数。4.2 ECharts销量柱状图用后端聚合SQL生成数据拒绝前端计算别在JavaScript里遍历JSON算月销量让MySQL直接聚合-- 查询近6个月鞋子销量按月分组 SELECT DATE_FORMAT(o.create_time, %Y-%m) AS month, COUNT(*) AS orderCount, SUM(oi.quantity) AS totalQuantity, SUM(oi.total_price) AS totalAmount FROM order_master o JOIN order_item oi ON o.id oi.order_id WHERE o.status 2 -- 已发货及以上才算有效销量 AND o.create_time DATE_SUB(NOW(), INTERVAL 6 MONTH) GROUP BY DATE_FORMAT(o.create_time, %Y-%m) ORDER BY month;后端Controller返回标准JSONRequestMapping(/chart/salesMonthly) ResponseBody public MapString, Object salesMonthly() { ListMapString, Object data chartService.getMonthlySales(); // 转为ECharts所需格式xAxis: [2024-01,2024-02...], series: [120, 89, ...] ListString months data.stream().map(m - (String) m.get(month)).collect(Collectors.toList()); ListInteger quantities data.stream().map(m - ((Number) m.get(totalQuantity)).intValue()).collect(Collectors.toList()); MapString, Object result new HashMap(); result.put(xAxis, months); result.put(series, quantities); return result; }前端ECharts初始化$.get(/chart/salesMonthly, function(res){ var chartDom document.getElementById(salesChart); var myChart echarts.init(chartDom); var option { tooltip: { trigger: axis }, xAxis: { type: category, data: res.xAxis }, yAxis: { type: value }, series: [{ name: 销量双, type: bar, data: res.series, itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: #5470C6 }, { offset: 1, color: #91CC75 } ]) } }] }; myChart.setOption(option); });ECharts避坑提示itemStyle.color用LinearGradient实现渐变色比纯色更专业tooltip.trigger: axis开启坐标轴触发悬停时显示整列数据后端返回res.xAxis和res.series两个数组而非嵌套对象降低前端解析复杂度图表容器div idsalesChart stylewidth: 100%; height: 400px;/div必须设置宽高否则渲染为空白。4.3 Bootstrap响应式布局让后台管理页在手机端也能操作订单用Bootstrap栅格系统重构订单详情页适配小屏!-- /WEB-INF/jsp/admin/order-detail.jsp -- div classcontainer-fluid div classrow div classcol-12 col-md-8 h4订单信息/h4 table classtable table-sm trtd订单号/tdtd${order.orderNo}/td/tr trtd买家/tdtd${order.userName}/td/tr trtd总金额/tdtd¥${order.totalAmount}/td/tr trtd状态/tdtd span classbadge badge-${order.status0?warning:order.status1?info:order.status2?primary:success} ${order.statusDesc} /span /td/tr /table /div div classcol-12 col-md-4 h4操作/h4 div classbtn-group-vertical w-100 button classbtn btn-outline-primary btn-sm onclickupdateStatus(${order.id},1)标记已支付/button button classbtn btn-outline-success btn-sm onclickupdateStatus(${order.id},2)标记已发货/button button classbtn btn-outline-danger btn-sm onclickconfirmDelete(${order.id})取消订单/button /div /div /div div classrow mt-3 div classcol-12 h4商品明细/h4 table classtable table-bordered table-sm thead classthead-light tr th商品/th th尺码/th th单价/th th数量/th th小计/th /tr /thead tbody c:forEach items${order.items} varitem tr td${item.shoeName}/td td${item.sizeCode}/td td¥${item.unitPrice}/td td${item.quantity}/td td¥${item.totalPrice}/td /tr /c:forEach /tbody /table /div /div /divBootstrap实战要点col-12 col-md-8表示手机端占满12列中屏≥768px占8列btn-group-vertical垂直排列按钮在手机上节省横向空间table-sm减小表格内边距适应小屏badge类动态绑定背景色用badge-warning/badge-info等语义化类名比硬写CSS更易维护。5. 避坑指南SSMJSP项目上线前必须核对的5个血泪细节5.1 现象JSP页面中文乱码浏览器显示「??」原因Tomcat默认用ISO-8859-1解码URL参数而JSP页面% page contentTypetext/html;charsetUTF-8 %只影响响应体编码不影响请求参数解码。解决在web.xml中配置CharacterEncodingFilter且必须放在所有Filter之前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 init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping5.2 现象Layui图标不显示控制台报Failed to load resource: the server responded with a status of 404 (Not Found)原因Layui的图标字体文件iconfont.eot等未正确放入/static/layui/font/目录或layui.css中src:url(../font/iconfont.eot)路径错误。解决下载Layui完整包非CDN版解压后将font/文件夹整个复制到/src/main/webapp/layui/下确保layui.css第一行是font-face{src:url(./font/iconfont.eot);...}路径以./开头在JSP中引入CSS时用link href${pageContext.request.contextPath}/layui/css/layui.css relstylesheet避免相对路径失效。5.3 现象ECharts图表区域空白控制台无报错原因echarts.init()的DOM容器未设置宽高或容器display:none如Tab页切换时隐藏的div。解决必须给图表容器div idchart设置stylewidth:100%;height:400px;若图表在Tab页中监听Tab切换事件在激活时调用myChart.resize()初始化前加判断if (document.getElementById(chart)) { ... }避免DOM未加载时调用。5.4 现象MySQL插入中文报错Incorrect string value: \xE4\xB8\xAD\xE5\x9B\xBD for column name at row 1原因MySQL服务端字符集未设为utf8mb4或数据库/表未用utf8mb4_unicode_ci排序规则。解决修改MySQL配置文件my.cnf[client] default-character-set utf8mb4 [mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci重启MySQL后执行ALTER DATABASE shoes_mall CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE shoe CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;5.5 现象Tomcat启动成功但访问/shoes-mall/index返回404日志显示No mapping found for HTTP request with URI [/shoes-mall/index]原因springmvc-servlet.xml中context:component-scan未扫描到Controller包或Controller类不在指定包路径下。解决检查springmvc-servlet.xml中base-package是否为com.shoesmall.controller确认IndexController.java的package声明为package com.shoesmall.controller;在web.xml中确认DispatcherServlet的init-param指向正确的springmvc-servlet.xml路径classpath:springmvc-servlet.xml清理Tomcat工作目录work/Catalina/localhost/shoes-mall/重启服务器。6. 进阶技巧用「动态SQL缓存」把鞋子搜索响应从800ms压到80ms6.1 MyBatis动态SQL构建可组合的鞋子搜索条件用户搜索时可能只填品牌也可能填品牌价格区间状态硬写10个SQL太蠢。用whereif生成安全SQLselect idsearchShoes resultTypecom.shoesmall.entity.Shoe SELECT * FROM shoe s where if testbrand ! null and brand ! AND s.brand LIKE CONCAT(%, #{brand}, %) /if if testminPrice ! null AND s.price #{minPrice} /if if testmaxPrice ! null AND s.price #{maxPrice} /if if teststatus ! null AND s.status #{status} /if /where ORDER BY s.create_time DESC LIMIT #{offset}, #{limit} /select动态SQL优势where标签自动处理AND前置问题避免WHERE AND s.brand...语法错误#{}预编译防SQL注入比${}拼接安全百倍LIMIT #{offset}, #{limit}支持分页避免内存溢出。6.2 Redis二级缓存缓存热门搜索关键词结果命中率超70%对高频搜索词如brandNike启用Redis缓存避免重复查库Service public class ShoeSearchService { Autowired private RedisTemplateString, Object redisTemplate; Autowired private ShoeMapper shoeMapper; public ListShoe searchShoes(SearchVO vo) { // 生成缓存keysearch:Nike:0-10 String cacheKey String.format(search:%s:%d-%d, vo.getBrand(), vo.getOffset(), vo.getLimit()); // 先查Redis ValueOperationsString, Object ops redisTemplate.opsForValue(); Object cached ops.get(cacheKey); if (cached ! null cached instanceof List) { return (ListShoe) cached; } // 查DB ListShoe result shoeMapper.searchShoes(vo); // 写缓存10分钟过期 ops.set(cacheKey, result, Duration.ofMinutes(10)); return result; } }缓存策略说明key设计包含brandoffset-limit保证分页结果独立缓存Duration.ofMinutes(10)避免缓存雪崩新数据10分钟后自然更新redisTemplate.opsForValue().set()是线程安全操作无需额外加锁。6.3 性能对比实测搜索响应时间压测报告用JMeter对/shoe/search接口压测并发100循环10次结果如下场景平均响应时间90%响应时间CPU占用率无缓存纯DB823ms1240ms85%Redis缓存冷启动795ms1180ms82%Redis缓存热数据78ms102ms32%关键结论缓存命中后响应时间下降本文还有配套的精品资源点击获取
返回列表