简介:这是一套基于SpringBoot的Java全栈体育用品商城实战项目,面向Java初学者与Web开发入门者,提供从用户端到后台管理的完整电商系统实现方案。项目采用JSP+HTML+jQuery构建前端页面,SpringBoot整合MyBatis与MySQL实现后端逻辑,支持管理员与普通用户双角色操作,并集成协同过滤推荐算法与百度地图API下单功能,具备良好扩展性,可快速适配其他垂直品类商城。资源包共698个文件,含58个JSP页面(核心业务视图)、46个Java类(Service/Controller/Entity层)、51个JS脚本(交互逻辑)、25个PNG/GIF图片(界面素材)及1个SQL建表脚本(含初始化数据),整体压缩包仅4.9MB,结构清晰、模块解耦度高。已有156人学习下载,读者可直接导入IDEA或Eclipse运行调试,掌握MVC分层开发、前后端交互、数据库设计及常见第三方API集成等关键技能。
1. 这不是又一个“学生作业式商城”,而是一套可落地的 Java Web 开发闭环实践路径
你搜“Java体育用品商城”,大概率会看到一堆带截图、没源码、连登录都卡在 JSP 表单提交的 demo 项目。但真实企业级 Java Web 应用里,SpringBoot 不是摆设,JSP 不是历史包袱,MySQL 不是只建三张表的玩具——它得能处理商品分类树形展开、库存并发扣减、订单状态机流转、用户浏览行为埋点,还得让前端工程师能改 HTML/CSS 不报错、运维能通过 Maven profile 切换测试/生产数据库、DBA 能从 SQL 日志里快速定位慢查询。本项目标题里的java+SpringBoot+jsp+html+maven+mysql不是技术堆砌清单,而是当前中小型企业 Java 后端团队仍在高频使用的稳定型技术栈组合:SpringBoot 提供快速启动与自动装配,JSP 承担轻量级服务端渲染(尤其适合内部管理后台或对 SEO 无强需求的 B2B 场景),HTML 是页面骨架,Maven 管理依赖与构建生命周期,MySQL 存储核心业务数据。本文不讲“如何新建 SpringBoot 工程”,而是聚焦于如何让这六种技术在体育用品商城这个具体业务场景中真正协同工作——从 JSP 页面如何安全接收 SpringBoot Controller 返回的 JSON 数据,到 Maven 的pom.xml中 MyBatis 与 JSP 标签库的版本兼容性陷阱;从 MySQL 的utf8mb4字符集对运动鞋型号“NIKE Air Max 270 ✨”的支持,到web.xml中 JSP 编译参数对中文注释乱码的修复。适合正在维护老系统、参与传统行业数字化升级、或准备 Java 面试题中“JSP 与 SpringBoot 混合开发”考点的开发者。
2. SpringBoot 与 JSP 共存的底层逻辑:为什么不能直接用@RestController返回 HTML?
2.1 JSP 在 SpringBoot 中的定位不是“淘汰品”,而是“可控渲染层”
SpringBoot 官方文档明确指出:内嵌 Tomcat 默认不支持 JSP。这不是 Bug,而是设计取舍——SpringBoot 推崇前后端分离,JSP 属于传统服务端模板引擎。但在体育用品商城这类内部运营系统中,JSP 仍有不可替代价值:运营人员需实时修改商品详情页的促销文案(如“夏季清仓:篮球袜满 99 减 30”),若强制走 Vue/React 前端框架,每次修改都要重新构建部署;而 JSP 文件放在src/main/webapp/WEB-INF/jsp/下,Tomcat 热加载后即可生效,配合web.xml中<jsp-config>设置,可实现“改完保存,F5 刷新即见效果”。关键在于理解 SpringBoot 的 DispatcherServlet 如何接管 JSP 请求:它不直接解析.jsp文件,而是将请求转发给容器内置的 JSP 引擎(如 Jasper)。因此,必须显式配置InternalResourceViewResolver,并确保prefix指向WEB-INF/jsp/(安全起见,JSP 文件绝不能放在webapp根目录下,防止 URL 直接访问)。
2.2 实现 SpringBoot Controller 向 JSP 传递数据的最小可行配置
2.2.1pom.xml中必须引入的依赖组合
<!-- SpringBoot Web 启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- JSP 支持核心:Tomcat JSP API --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency> <!-- JSTL 标签库(用于 JSP 中循环展示商品列表) --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <!-- Servlet API(编译 JSP 所需) --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <scope>provided</scope> </dependency>提示:
tomcat-embed-jasper的scope=provided是关键。它告诉 Maven:该依赖由运行时容器(如外部 Tomcat)提供,打包时不打入jar,否则内嵌 Tomcat 会因类冲突启动失败。若部署到独立 Tomcat,此依赖可移除。
2.2.2application.properties中的视图解析器配置
# 关闭默认的 Thymeleaf 模板引擎(避免冲突) spring.thymeleaf.enabled=false # 配置 JSP 视图解析器 spring.mvc.view.prefix=/WEB-INF/jsp/ spring.mvc.view.suffix=.jsp # 启用 JSP 编译调试(开发期必需) server.jsp-servlet.init-parameters.development=true2.2.3 Controller 返回 Model 数据的典型写法
@Controller @RequestMapping("/product") public class ProductController { @Autowired private ProductService productService; // 返回商品列表页:数据 + 视图名 @GetMapping("/list") public String listProducts(Model model) { // 查询体育用品分类(球类、健身器材、户外装备等) List<Category> categories = productService.getCategories(); // 查询热门商品(按销量排序前 10) List<Product> hotProducts = productService.getHotProducts(10); // 将数据放入 Model,JSP 通过 ${categories} 访问 model.addAttribute("categories", categories); model.addAttribute("hotProducts", hotProducts); return "product/list"; // 对应 /WEB-INF/jsp/product/list.jsp } }逻辑说明:
return "product/list"中的字符串是逻辑视图名,InternalResourceViewResolver会自动拼接为/WEB-INF/jsp/product/list.jsp。Model是 Spring 封装的 Map,addAttribute()方法本质是modelMap.put("key", value),JSP 中通过 EL 表达式${key}获取。注意:不要用@ResponseBody注解此方法,否则 Spring 会将其序列化为 JSON 并返回给浏览器,而非跳转到 JSP 页面。
2.3 JSP 页面中安全使用 JavaBean 与集合的语法规范
2.3.1list.jsp中展示商品列表的标准写法
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <title>体育用品商城 - 商品列表</title> </head> <body> <h1>全部商品</h1> <!-- 使用 JSTL 的 forEach 遍历 hotProducts --> <c:forEach items="${hotProducts}" var="product" varStatus="status"> <div class="product-item"> <h3>${product.name}</h3> <p>价格:¥${product.price}</p> <p>品牌:<c:out value="${product.brand}"/></p> <!-- c:out 可防止 XSS,自动转义特殊字符 --> <a href="/product/detail?id=${product.id}">查看详情</a> </div> <!-- 每 3 个商品加一个分隔线 --> <c:if test="${status.count % 3 == 0}"> <hr/> </c:if> </c:forEach> </body> </html>参数说明:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>:声明 JSTL 核心标签库,c:前缀用于forEach、if等控制逻辑。<c:out value="${product.brand}"/>:比直接${product.brand}更安全,自动过滤<script>等危险 HTML 标签,防止用户输入恶意内容污染页面。varStatus="status":提供循环状态对象,status.count返回当前迭代序号(从 1 开始),用于实现“每 3 行分隔”。
3. Maven 构建与 MySQL 集成:从pom.xml依赖管理到application.yml数据源配置
3.1pom.xml中 MySQL 驱动与持久层框架的版本协同策略
体育用品商城涉及商品、订单、用户三张核心表,需支持事务与连接池。Maven 依赖选择必须考虑SpringBoot 版本兼容性。以 SpringBoot 2.7.x(当前企业主流 LTS 版本)为例:
| 组件 | 推荐版本 | 选择理由 |
|---|---|---|
mysql:mysql-connector-java | 8.0.33 | 支持 MySQL 8.0 的caching_sha2_password认证插件,避免连接拒绝 |
mybatis-spring-boot-starter | 2.2.0 | 与 SpringBoot 2.7.x 完全兼容,自动配置 DataSource |
druid-spring-boot-starter | 1.2.18 | 替代 HikariCP,提供更丰富的监控指标(如 SQL 执行时间、慢 SQL 报警) |
<!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- MyBatis 整合 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <!-- Druid 连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.18</version> </dependency>注意:
mysql-connector-java的scope=runtime表示仅在运行时需要,编译期无需。若使用 MySQL 5.7,驱动版本应降为5.1.49,否则连接时抛出Communications link failure。
3.2application.yml中 MySQL 数据源的生产级配置项
spring: datasource: url: jdbc:mysql://localhost:3306/sports_store?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false username: root password: your_secure_password driver-class-name: com.mysql.cj.jdbc.Driver # Druid 连接池参数 druid: initial-size: 5 min-idle: 5 max-active: 20 # 连接泄漏检测(体育用品商城高并发下单场景必备) remove-abandoned-on-borrow: true remove-abandoned-timeout: 60 # SQL 监控开关 stat-view-servlet: enabled: true url-pattern: /druid/* web-stat-filter: enabled: true exclusions: "*.js,*.css,/druid/*" # MyBatis 配置 mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true # 数据库字段 user_name → Java 属性 userName log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印 SQL关键参数解释:
characterEncoding=utf8mb4:确保存储 emoji(如运动图标 ⚽️)和生僻汉字(如“骉”、“犇”)不乱码,这是体育用品品牌名(如“彪马”、“李宁”)的刚需。remove-abandoned-on-borrow: true:当连接被借出超过 60 秒未归还,Druid 自动回收,防止库存扣减事务未提交导致连接耗尽。log-impl: ...StdOutImpl:开发期开启,可直观看到 MyBatis 生成的 SQL,例如SELECT * FROM product WHERE category_id = ?,便于排查“为什么篮球分类下没显示足球”这类问题。
3.3 创建体育用品商城核心表结构的 MySQL DDL 脚本
-- 商品表(含运动品类特有字段) CREATE TABLE `product` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `name` varchar(100) NOT NULL COMMENT '商品名称,如"耐克男子跑步鞋"', `category_id` bigint NOT NULL COMMENT '分类ID,关联category表', `price` decimal(10,2) NOT NULL COMMENT '售价', `stock` int NOT NULL DEFAULT '0' COMMENT '库存数量', `brand` varchar(50) DEFAULT NULL COMMENT '品牌,如"Nike"', `sport_type` enum('RUNNING','BASKETBALL','FOOTBALL','TENNIS','GYM') NOT NULL COMMENT '运动类型枚举', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_category` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品信息表'; -- 分类表(支持多级,如一级"球类"→二级"篮球"→三级"篮球鞋") CREATE TABLE `category` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `parent_id` bigint DEFAULT '0' COMMENT '父分类ID,0表示一级分类', `level` tinyint NOT NULL DEFAULT '1' COMMENT '分类层级', PRIMARY KEY (`id`), KEY `idx_parent` (`parent_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;实战技巧:执行 DDL 前,先在 MySQL 中运行
SET NAMES utf8mb4;,确保客户端连接使用正确字符集。若出现Incorrect string value错误,说明表或列未设置CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci。
4. HTML 与 JSP 协同:如何让静态页面具备动态数据能力
4.1<!doctype html><html lang="zh-cn">结构下嵌入 JSP 动态内容的规范流程
体育用品商城首页需展示轮播图、热销榜、新品预告三个区块。纯 HTML 无法动态加载数据,必须与 JSP 结合。标准做法是:HTML 作为骨架,JSP 标签注入动态内容。
4.1.1index.jsp中复用 HTML 结构并注入数据
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>体育用品商城 - 首页</title> <link rel="stylesheet" href="/static/css/bootstrap.min.css"> </head> <body> <!-- 1. 轮播图:从 Controller 传入的 bannerList --> <div id="carouselExampleIndicators" class="carousel slide">@Component public class PriceUtils { /** * 将 BigDecimal 价格转为带¥符号的字符串 * @param price 价格数值 * @return 格式化后的字符串,如 "¥199" */ public String formatPrice(BigDecimal price) { if (price == null) return "¥0"; return "¥" + price.setScale(0, RoundingMode.HALF_UP).toBigInteger(); } }4.2.2 在 JSP 中通过spring:eval标签调用工具方法
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <!-- 在 JSP 头部声明 spring 标签库 --> <c:forEach items="${hotProducts}" var="p"> <div class="card-body"> <h5 class="card-title">${p.name}</h5> <!-- 调用 PriceUtils.formatPrice --> <p class="card-text"> <spring:eval expression="@priceUtils.formatPrice(p.price)" /> </p> </div> </c:forEach>参数说明:
@priceUtils是 Spring 的 Bean 名称(默认为类名首字母小写),p.price是 JSP 中的变量。spring:eval在服务端执行 Java 方法,结果插入 HTML 流。禁止在 JSP 中写new BigDecimal()或数据库查询逻辑,所有业务计算应在 Service 层完成。
5. 排查 JSP 修改不生效与 MySQL 连接超时的典型故障链
5.1 “JSP 改了不生效”的三层排查法:从缓存到编译再到部署
当修改list.jsp后刷新页面仍是旧内容,按以下顺序检查:
| 层级 | 检查点 | 命令/操作 | 预期结果 |
|---|---|---|---|
| 浏览器层 | 是否禁用缓存 | Chrome 开发者工具 → Network → 勾选 "Disable cache" | 刷新后list.jsp请求响应头含Cache-Control: no-cache |
| Tomcat 层 | JSP 是否重新编译 | 查看target/tomcat/work/Catalina/localhost/ROOT/org/apache/jsp/WEB_002dINF/jsp/product/list_jsp.java文件修改时间 | 该文件时间应与list.jsp最后保存时间一致 |
| Maven 层 | 是否启用热部署 | pom.xml中spring-boot-devtools依赖是否存在 | 存在则mvn spring-boot:run启动时,修改 JSP 会触发自动重编译 |
关键命令:若
list_jsp.java未更新,手动删除target/tomcat/work/目录,重启应用。devtools依赖如下:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency>
5.2 MySQL 连接超时的根因分析与wait_timeout参数调整
体育用品商城在凌晨低峰期可能触发 MySQLwait_timeout(默认 28800 秒 = 8 小时),导致次日首次请求报CommunicationsException: Connection reset。
5.2.1 验证当前 MySQL 的wait_timeout值
-- 登录 MySQL 后执行 SHOW VARIABLES LIKE 'wait_timeout'; -- 查看当前连接的空闲时长 SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO FROM information_schema.PROCESSLIST WHERE COMMAND = 'Sleep' AND TIME > 3600;5.2.2 在application.yml中配置连接池保活策略
spring: datasource: druid: # 每 30 分钟执行一次心跳检测 time-between-connect-error-millis: 1800000 # 连接空闲 5 分钟后校验有效性 min-evictable-idle-time-millis: 300000 # 校验 SQL(MySQL 8.0+ 推荐 useServerPrepStmts=false 时用 SELECT 1) validation-query: SELECT 1 test-while-idle: true test-on-borrow: false test-on-return: false参数逻辑:
min-evictable-idle-time-millis=300000表示连接空闲 5 分钟后,Druid 会执行validation-query(SELECT 1)检测其是否有效。若 MySQL 的wait_timeout设为 3600(1 小时),则此参数必须小于 3600,否则连接在被校验前已被 MySQL 主动断开。
5.3 用curl快速验证 SpringBoot + JSP + MySQL 端到端连通性
# 1. 检查应用是否启动成功(HTTP 200) curl -I http://localhost:8080/ # 2. 检查 JSP 页面能否渲染(返回 HTML 内容) curl -s http://localhost:8080/product/list | head -20 # 3. 检查数据库查询是否正常(返回 JSON,证明 MyBatis 工作) curl -s http://localhost:8080/api/products/hot | jq '.[0].name' # 4. 检查 MySQL 连接池状态(Druid 监控页) curl -s http://localhost:8080/druid/index.html | grep -i "active"技巧说明:
curl -s静默模式避免进度条干扰;jq '.[0].name'解析 JSON 提取第一个商品名称,验证数据链路完整。若第 3 步失败,说明 MyBatis 或 MySQL 配置有误;若第 2 步返回空白,检查InternalResourceViewResolver的prefix路径是否正确。
本文还有配套的精品资源,点击获取