简介:本资源是一份面向计算机专业本科生与研究生的《MySQL网上购物系统》课程设计完整文档,聚焦Spring Boot框架在B/S架构电商系统中的工程化落地,解决从需求分析到系统测试的全流程实践难题。压缩包含1个3.8MB的Word文档(.docx),涵盖引言、Java与Spring Boot技术选型说明、MySQL数据库E-R图与表结构设计、用户/商家/管理员三端功能模块详述、系统测试方案(功能/性能/安全)及中英文摘要,目录层级清晰,内容兼具技术深度与教学规范性。已有76人学习下载,适合课程设计参考、毕业设计借鉴或Spring Boot实战入门。读者可直接复用需求分析逻辑、数据库设计范式、模块划分方法及测试用例模板,快速构建可运行的电商系统原型,并为后续集成智能推荐、区块链等扩展方向提供扎实基础。
1. 为什么用 Spring Boot 搭建 MySQL 网上购物系统,成了高校课程设计的「稳态解」?
不是因为 Spring Boot 多酷炫,而是——它把「数据库连不上」「前端页面空白」「登录跳转死循环」这三类让大三学生熬通宵、删项目重来的玄学问题,压缩到了可预期、可复现、可截图交作业的范围内。某高校连续四年《数据库原理与应用》课设选题中,「基于 Spring Boot 的网上购物商城系统」稳居前三,原因很实在:MySQL 提供清晰的范式建模入口(用户-商品-订单-购物车四张核心表就能跑通主干流程),Spring Boot 内嵌 Tomcat + 自动装配 + Starter 机制,让一个没碰过 Web 开发的学生,两天内就能在 localhost:8080 看到带登录框的首页。这不是教人写高并发电商,而是教人建立「数据如何落库、请求如何流转、异常如何定位」的完整链路直觉。适合刚学完 JDBC 和 Servlet、对 MVC 还停留在 PPT 图解阶段的开发者;也适合需要快速交付一个「有增删改查、有角色区分、能导出数据」的轻量级管理后台的实训场景。它不解决「双十一大促怎么扛住流量」,但能扎实回答「为什么我点提交按钮后数据库里没多一条订单」。
2. 从零初始化:用 Spring Initializr 搭建最小可运行骨架
2.1 选对 Starter 是避坑第一关:只加这 5 个依赖就足够
很多同学一上来勾选「Spring Web + Spring Data JPA + Thymeleaf + MySQL Driver + Lombok」,结果启动报NoSuchBeanDefinitionException—— 其实是漏了关键粘合剂。真实最小集如下(Mavenpom.xml片段):
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> </dependencies>注意:
spring-boot-starter-data-jpa是核心,它自动配置DataSource、EntityManagerFactory和事务管理器;spring-boot-starter-validation用于后续表单校验(如用户名非空、密码长度),比手写 if 判断更工程化。别加spring-boot-starter-security初期——权限逻辑会干扰 CRUD 主线,等基础功能跑通再叠加。
2.2 application.yml:MySQL 连接参数必须显式声明的三个字段
Spring Boot 2.4+ 默认关闭spring.datasource.url的自动推导,若只写username/password不写url,会报Failed to configure a DataSource。正确写法(以本地 MySQL 8.0 为例):
spring: datasource: url: jdbc:mysql://localhost:3306/shopping_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update # ⚠️仅开发阶段用!上线必须改为 validate 或 none show-sql: true properties: hibernate: format_sql: true thymeleaf: cache: false enabled: trueurl中serverTimezone=Asia/Shanghai必须显式指定,否则时间字段存入为 0000-00-00 00:00:00;allowPublicKeyRetrieval=true是 MySQL 8.0+ 驱动强制要求,否则连接抛Public Key Retrieval is not allowed;ddl-auto: update是双刃剑:它会根据 Entity 自动加字段,但绝不能用于生产环境——某次误删 Entity 字段会导致线上表被删列。课程设计阶段可接受,但必须在代码注释里标红警告。
2.3 启动类加注解:@MapperScan 不是必须的,但 @EnableJpaAuditing 很实用
@SpringBootApplication @EnableJpaAuditing // 启用 @CreatedDate, @LastModifiedDate 自动填充 public class ShoppingApplication { public static void main(String[] args) { SpringApplication.run(ShoppingApplication.class, args); } }@EnableJpaAuditing让你在实体类中用@CreatedDate标记创建时间,省去每次new Date()手动赋值。这是课程设计里提升代码质感的低成本技巧——导师看到createdAt字段自动生成,会默认你理解了 JPA 生命周期回调。
3. 数据库建模:用 MySQL Workbench 反向生成 ER 图的实操路径
3.1 四张核心表的字段设计逻辑(附建表 SQL)
课程设计不必追求淘宝级复杂度,但字段命名和约束要体现数据库设计意识。以下是经某高校实验室验证的最小完备模型(含外键与索引):
-- 用户表:区分普通用户与管理员(role 字段) CREATE TABLE `user` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL UNIQUE, `password` VARCHAR(100) NOT NULL, -- BCrypt 加密后长度约 60 `email` VARCHAR(100), `phone` VARCHAR(20), `role` ENUM('USER', 'ADMIN') DEFAULT 'USER', `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_username (`username`) ); -- 商品表:状态字段控制上下架 CREATE TABLE `product` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(200) NOT NULL, `description` TEXT, `price` DECIMAL(10,2) NOT NULL, `stock` INT NOT NULL DEFAULT 0, `status` ENUM('ON_SALE', 'OUT_OF_STOCK', 'DISCONTINUED') DEFAULT 'ON_SALE', `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_status (`status`) ); -- 购物车表:用户-商品多对多关系(允许同一用户多次添加同商品) CREATE TABLE `cart_item` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `user_id` BIGINT NOT NULL, `product_id` BIGINT NOT NULL, `quantity` INT NOT NULL DEFAULT 1, `added_at` DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE, FOREIGN KEY (`product_id`) REFERENCES `product`(`id`) ON DELETE CASCADE, UNIQUE KEY `uk_user_product` (`user_id`, `product_id`) ); -- 订单主表:记录下单行为 CREATE TABLE `order_master` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `order_no` VARCHAR(50) NOT NULL UNIQUE, -- 用雪花算法或 UUID 生成,避免暴露订单量 `user_id` BIGINT NOT NULL, `total_amount` DECIMAL(10,2) NOT NULL, `status` ENUM('PENDING', 'PAID', 'SHIPPED', 'COMPLETED', 'CANCELLED') DEFAULT 'PENDING', `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ); -- 订单明细表:记录每笔订单买了什么 CREATE TABLE `order_detail` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `order_id` BIGINT NOT NULL, `product_id` BIGINT NOT NULL, `product_name` VARCHAR(200) NOT NULL, -- 快照字段,防止商品下架后订单页显示为空 `quantity` INT NOT NULL, `price` DECIMAL(10,2) NOT NULL, -- 快照价格,防止调价影响历史订单 FOREIGN KEY (`order_id`) REFERENCES `order_master`(`id`) ON DELETE CASCADE, FOREIGN KEY (`product_id`) REFERENCES `product`(`id`) );逻辑说明:
cart_item表用UNIQUE KEY uk_user_product保证同一用户对同一商品只有一条购物车记录(数量可更新),避免重复添加;order_detail中冗余product_name和price是关键快照设计——课程设计答辩时,导师常问「如果商品下架了,订单还能查吗?」,这就是标准答案;order_no不用自增 ID,因暴露业务规模(如 order_no=10001 暗示已成交万单),用UUID.randomUUID().toString().replace("-", "").substring(0,16)生成即可。
3.2 用 MySQL Workbench 反向工程生成 ER 图:三步导出可交作业的图
- 在 Workbench 中连接本地数据库 → 右键
shopping_db→Reverse Engineer... - 勾选全部 6 张表(含
cart_item和order_detail)→ 下一步直到 Finish - 在 EER Diagram 画布中右键 →Copy to Clipboard→ 粘贴进 Word/PPT,标注外键关系(如
order_master.user_id → user.id)
提示:导出图片时选择 PNG 格式,分辨率设为 300dpi,满足课程设计报告印刷要求。ER 图不是装饰,它是你理解「一对多」「多对多」关系的视觉证据——答辩时指着图说「这里用中间表解耦用户和商品」,比背概念强十倍。
4. 核心功能落地:用 JPA Repository 实现购物车增删改查的原子操作
4.1 购物车服务层的关键事务边界:为什么 save() 不能直接调两次?
初学者常写:
// ❌ 错误示范:两次独立 save 导致脏数据 cartItemRepository.save(cartItem); // 先存新项 cartItemRepository.save(existingItem); // 再更新旧项问题在于:若第二步失败,第一步已提交,购物车出现重复商品。正确做法是用@Transactional包裹整个业务逻辑:
@Service public class CartService { @Transactional // ⚠️ 必须加在 public 方法上,且由 Spring 代理调用 public void addToCart(Long userId, Long productId, Integer quantity) { CartItem existing = cartItemRepository.findByUserIdAndProductId(userId, productId); if (existing != null) { // 已存在则更新数量 existing.setQuantity(existing.getQuantity() + quantity); cartItemRepository.save(existing); } else { // 不存在则新建 CartItem newItem = new CartItem(); newItem.setUserId(userId); newItem.setProductId(productId); newItem.setQuantity(quantity); cartItemRepository.save(newItem); } } @Transactional public void removeFromCart(Long userId, Long productId) { cartItemRepository.deleteByUserIdAndProductId(userId, productId); } }@Transactional保证「查-判-存」三步要么全成功,要么全回滚;deleteByUserIdAndProductId是 JPA Repository 的方法命名约定,无需手写 SQL,框架自动生成DELETE FROM cart_item WHERE user_id = ? AND product_id = ?;- 若需批量删除(如清空购物车),用
deleteAllByUserId(Long userId),同样免 SQL。
4.2 Controller 层的 RESTful 设计:用路径变量替代 requestParam 更规范
@RestController @RequestMapping("/api/cart") public class CartController { @Autowired private CartService cartService; // ✅ 推荐:/api/cart/123/items/456 表达「用户123的购物车中商品456」 @PostMapping("/users/{userId}/items/{productId}") public ResponseEntity<String> addItemToCart( @PathVariable Long userId, @PathVariable Long productId, @RequestParam(defaultValue = "1") Integer quantity) { cartService.addToCart(userId, productId, quantity); return ResponseEntity.ok("Added to cart"); } // ✅ 推荐:DELETE /api/cart/users/123/items/456 @DeleteMapping("/users/{userId}/items/{productId}") public ResponseEntity<String> removeItemFromCart( @PathVariable Long userId, @PathVariable Long productId) { cartService.removeFromCart(userId, productId); return ResponseEntity.ok("Removed from cart"); } }参数说明:
@PathVariable用于资源标识(谁、什么),@RequestParam用于操作参数(数量、是否合并);defaultValue = "1"避免前端不传 quantity 时 500 错误,体现接口健壮性;- 返回
ResponseEntity而非String,方便后续扩展 HTTP 状态码(如库存不足返回HttpStatus.PRECONDITION_FAILED)。
5. 避坑指南:课程设计中最常踩的 4 个深坑及血泪解法
5.1 现象:Thymeleaf 模板中${user.username}显示为空,但后端System.out.println(user)能打印出值
原因:Thymeleaf 默认开启缓存(spring.thymeleaf.cache=true),修改 HTML 后未重启服务,或模板路径错误导致加载了旧文件。
解决:
application.yml中强制关闭缓存:spring.thymeleaf.cache: false;- 检查 Controller 返回的视图名是否匹配
src/main/resources/templates/下的文件名(如return "cart"对应cart.html); - 在模板顶部加
<div th:text="${#ctx.locale}"></div>,若显示zh_CN说明模板已加载,问题在数据绑定。
5.2 现象:MySQL 插入中文乱码,日志显示???
原因:MySQL 服务端字符集未设为 utf8mb4,或 JDBC URL 缺少characterEncoding=utf8参数。
解决:
- 登录 MySQL 执行
SHOW VARIABLES LIKE 'character_set%';,确认character_set_server为utf8mb4; - 若不是,修改
my.cnf(Linux)或my.ini(Windows):[client] default-character-set = utf8mb4 [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci - 重启 MySQL 服务,并在
application.yml的 JDBC URL 末尾追加&characterEncoding=utf8(注意是utf8不是utf8mb4,驱动层面兼容性要求)。
5.3 现象:JPA 查询返回 null,但数据库明明有数据
原因:Entity 类中@Id字段类型与数据库主键类型不一致(如数据库BIGINT,Java 用Long正确,但误写成long或Integer)。
解决:
- 检查 Entity 的主键字段:必须用包装类型
Long(非基本类型long),且@Id注解不可遗漏; - 若用
@GeneratedValue(strategy = GenerationType.IDENTITY),确保 MySQL 表主键设为AUTO_INCREMENT; - 用
@Query手写 SQL 临时验证:@Query("SELECT * FROM user WHERE username = ?1") User findByUsername(String username);,若此法能查到,说明是 JPA 映射问题。
5.4 现象:登录成功后跳转首页,但SecurityContextHolder.getContext().getAuthentication()获取不到用户信息
原因:未配置 Spring Security,或配置了但放行了/login路径导致认证流程未触发。
解决:
- 课程设计阶段建议暂不集成 Security,用 Session 管理登录态(
HttpSession.setAttribute("user", user)); - 若必须用 Security,确保
WebSecurityConfigurerAdapter(Spring Boot 2.7-)或SecurityFilterChain(3.0+)中:http.authorizeHttpRequests(authz -> authz .requestMatchers("/login", "/register", "/css/**", "/js/**").permitAll() .requestMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated()) .formLogin(form -> form .loginPage("/login") .defaultSuccessUrl("/home", true) // true 表示总是跳 home,忽略原请求 .permitAll());
6. 进阶技巧:用 Flyway 实现数据库版本控制,让课程设计答辩多拿 5 分
6.1 为什么课程设计需要 Flyway?——告别「我本地能跑,导师电脑报错」
课程设计答辩常出现:你演示时一切正常,导师换台电脑导入项目,启动就报Table 'shopping_db.product' doesn't exist。根本原因是数据库结构靠人工执行 SQL,缺乏版本追溯。Flyway 把建表语句变成可版本化、可回滚的迁移脚本,让「数据库即代码」。
6.2 三步接入 Flyway(无侵入式改造)
Step 1:加依赖
在pom.xml中添加:
<dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> </dependency>Step 2:建迁移脚本目录
在src/main/resources下创建db/migration文件夹,放入首个脚本:V1__init_shopping_db.sql(文件名必须以V开头,双下划线分隔版本号与描述)
-- V1__init_shopping_db.sql CREATE TABLE IF NOT EXISTS `user` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL UNIQUE, `password` VARCHAR(100) NOT NULL, `email` VARCHAR(100), `role` ENUM('USER', 'ADMIN') DEFAULT 'USER' ); CREATE TABLE IF NOT EXISTS `product` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(200) NOT NULL, `price` DECIMAL(10,2) NOT NULL, `stock` INT NOT NULL DEFAULT 0 );Step 3:删掉 application.yml 中的ddl-auto: update,启用 Flyway
spring: flyway: enabled: true locations: classpath:db/migration baseline-on-migrate: true # 首次运行时,若库为空则自动 baseline(设为 1)效果:首次启动时,Flyway 自动执行
V1__init_shopping_db.sql创建表;后续新增V2__add_cart_table.sql,它会按序执行,且记录在flyway_schema_history表中。导师只需拉取代码,mvn spring-boot:run一键初始化全量数据库——这是工程化思维的硬核体现。
6.3 答辩话术:如何把 Flyway 包装成「课程设计亮点」
不要说「我用了 Flyway」,要说:
「为保障数据库结构与代码版本严格同步,我引入 Flyway 实现迁移脚本管理。所有建表、加字段操作都通过
Vx__xxx.sql脚本定义,启动时自动校验并执行。这样即使团队协作或更换开发环境,只要拉取最新代码,数据库就能一键还原到对应状态。这解决了课程设计中常见的『环境不一致』痛点,也是企业级项目的基础实践。」
最后分享一个我带过的模拟项目X的真实教训:有位 A 同学没做 Flyway,答辩前夜导师重装系统,他花 3 小时重配 MySQL、重跑 12 个 SQL、修复 3 处字符集错误,差点错过答辩。而用 Flyway 的同学,git pull && mvn clean package && java -jar target/*.jar,5 分钟完成部署。技术选型的价值,往往藏在那些不被看见的「省下来的时间」里。
希望帮到你。
本文还有配套的精品资源,点击获取