简介:这是一份面向计算机专业本科生的毕业设计实战项目资源,基于SSM(Spring+SpringMVC+MyBatis)框架与MySQL数据库、Bootstrap前端技术栈开发的珠宝商城购物网站,完整覆盖电商类系统核心业务逻辑,适用于课程设计、毕设参考及Java Web进阶学习。压缩包共276个文件,包含48个Java源码、48个编译后Class文件、46个运行依赖Jar包、30个CSS样式与23个JS交互脚本、25个XML配置文件、19个JSP页面及1个SQL建库建表脚本,结构清晰体现MVC分层设计;整体15.37MB,开箱即用。已有90人学习下载。资源提供可直接部署运行的完整工程,涵盖用户登录验证、会员/商品类别/商品信息/订单四大后台管理模块,以及购物车、订单查看等前台功能,代码规范、注释充分,Servlet与Service实现类(如UserServlet.class、GoodsServiceImpl.class、OrderServicesImpl.class)均完整呈现,便于理解请求流转与业务分层逻辑。
1. 珠宝商城不是Demo:为什么用SSM+MyBatis+MySQL+Bootstrap做毕业设计,真能跑通真实购物流程?
你手里的“珠宝商城”毕业设计,绝不是改个登录页、填几条假数据就能交差的玩具项目。它得让顾客能搜到蓝宝石吊坠、加购3件、选顺丰发货、微信支付成功、后台看到订单状态变“已付款”,最后导出Excel对账——整条链路必须闭环。我带过17届毕设,翻车最多的是:前端用Bootstrap堆了响应式界面,后端SSM三层结构看着规范,但MyBatis一写多表关联就N+1爆炸,MySQL没建索引导致搜索卡死,用户刚点“立即购买”页面就500。这不是代码写得丑,是没把SSM当生产级框架用,没把MyBatis当SQL编译器调,没把MySQL当交易型数据库管,更没把Bootstrap当工程化工具链使。这篇笔记不讲Spring IOC原理,不列MyBatis XML标签大全,只告诉你:如何用这套组合,在本地Windows/Mac上,3天内跑通从首页浏览→下单→支付模拟→后台管理的全链路,且每个环节经得起导师现场提问——比如“你这张订单表的status字段为什么用tinyint而不是enum?”、“MyBatis里#{}和${}在价格排序场景下哪个会SQL注入?”、“Bootstrap栅格系统在移动端珠宝详情图缩放时怎么避免文字被裁切?”。适合Java初学者(能写CRUD)、想拿高分又怕答辩被问住的同学,也适合教毕设的老师快速核验学生项目是否真跑得起来。
2. 搭建不是复制粘贴:SSM环境初始化与MyBatis核心配置落地
2.1 Spring MVC DispatcherServlet注册:别再用web.xml硬编码了
很多同学直接拷贝老项目web.xml,里面还写着<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>。这在Spring 4.0+时代早该淘汰了。现代SSM项目必须用JavaConfig方式注册DispatcherServlet,否则后续拦截器、跨域配置全失效。
// src/main/java/config/ServletConfig.java public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{AppConfig.class}; // 根容器:Service/DAO层 } @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{WebConfig.class}; // Web容器:Controller层 } @Override protected String[] getServletMappings() { return new String[]{"/"}; // 拦截所有请求 } }注意:
getServletMappings()返回"/"而非"*.do",这是RESTful风格基础。若仍用.do后缀,后续Bootstrap AJAX调用会因路径不一致导致404——尤其在商品搜索接口/product/search这种纯路径下。
2.2 MyBatis-Spring整合:SqlSessionFactoryBean的3个致命参数
MyBatis官方文档说“配置SqlSessionFactoryBean即可”,但实际部署中80%的连接池报错源于这三个参数没设对:
| 参数名 | 必填 | 推荐值 | 为什么关键 |
|---|---|---|---|
configLocation | 否 | classpath:mybatis-config.xml | 必须显式指定!否则MyBatis默认加载空配置,二级缓存、类型别名全失效,导致<resultMap>里javaType="com.jewel.entity.Product"报ClassNotFoundException |
mapperLocations | 是 | classpath:mapper/*.xml | 路径必须带*.xml通配符,若写成classpath:mapper/ProductMapper.xml,新增OrderMapper.xml时需手动维护,极易漏配 |
typeAliasesPackage | 是 | com.jewel.entity | 解决MyBatis XML中resultType冗长问题。设此参数后,XML里可写resultType="Product"而非resultType="com.jewel.entity.Product",降低拼写错误率 |
<!-- src/main/resources/mybatis-config.xml --> <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.4//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <!-- 开启驼峰命名自动转换,避免price_yuan字段映射失败 --> <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 打印SQL日志,调试阶段必开 --> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> </configuration>2.3 MySQL驱动与连接池:HikariCP比Druid更适合作业项目
别再用com.mysql.jdbc.Driver!MySQL 8.0+必须用com.mysql.cj.jdbc.Driver,且URL要加serverTimezone=Asia/Shanghai,否则时间字段全乱码。
# src/main/resources/jdbc.properties jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/jewel_shop?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true jdbc.username=root jdbc.password=123456HikariCP配置要点(比Druid轻量,启动快,毕设答辩演示不卡):
// src/main/java/config/DataSourceConfig.java @Bean public DataSource dataSource() { HikariConfig config = new HikariConfig(); config.setJdbcUrl(jdbcUrl); config.setUsername(jdbcUsername); config.setPassword(jdbcPassword); config.setDriverClassName(jdbcDriver); // 关键:连接池大小设为CPU核心数*2,笔记本通常设4-8 config.setMaximumPoolSize(6); // 防止空闲连接被MySQL断开(MySQL默认wait_timeout=28800秒) config.setConnectionTimeout(30000); config.setIdleTimeout(600000); config.setMaxLifetime(1800000); return new HikariDataSource(config); }血泪经验:曾见学生用Druid,
maxActive=20,结果导师现场连刷10次首页,连接池耗尽,后台报Cannot get a connection, pool error Timeout waiting for idle object。HikariCP的maximumPoolSize=6在单机演示足够,且无Druid监控页面干扰答辩。
3. 数据库设计不是ER图拍照:珠宝商城MySQL表结构实战推演
3.1 珠宝业务特殊性:为什么订单表要拆3张表?
普通商城订单表可能就order_id,user_id,total_price,status。但珠宝类目必须考虑:
- 定制需求:客户买戒指要刻字(
engraving_text),刻字内容需单独存表,避免主订单表膨胀 - 质检流程:钻石有GIA证书号(
gia_certificate_no),证书图片存OSS,但证书号必须在订单中可查 - 售后追溯:同一颗钻石可能被多次销售(二手回收再售),需记录流转历史
因此采用三表结构:
| 表名 | 字段精要 | 为什么这样设计 |
|---|---|---|
t_order | order_id(PK),user_id,order_no(唯一单号),total_amount,status(tinyint),create_time | 主订单表,轻量,高频查询 |
t_order_item | item_id(PK),order_id(FK),product_id,quantity,unit_price,engraving_text | 订单项表,支持同一订单多商品+定制信息 |
t_order_trace | trace_id(PK),order_id(FK),event_type(enum: 'paid','shipped','received'),operator_id,event_time | 流水表,记录订单状态变更,替代status字段频繁更新 |
-- 创建t_order_trace表(关键:加联合索引加速按订单查流水) CREATE TABLE `t_order_trace` ( `trace_id` BIGINT PRIMARY KEY AUTO_INCREMENT, `order_id` BIGINT NOT NULL, `event_type` ENUM('created','paid','shipped','delivered','cancelled') NOT NULL, `operator_id` BIGINT DEFAULT NULL, `event_time` DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX `idx_order_event` (`order_id`, `event_type`) -- 必加!否则查“某订单所有状态”慢如蜗牛 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;3.2 商品表索引策略:搜索性能生死线
珠宝搜索高频场景:按价格区间(price BETWEEN 1000 AND 5000)、按材质(material='gold')、按品类(category_id=3)。若只在id建主键,搜索10万商品时响应超3秒。
-- 商品表t_product关键索引 ALTER TABLE `t_product` ADD INDEX `idx_price_category` (`price`, `category_id`), -- 覆盖价格+品类联合查询 ADD INDEX `idx_material_status` (`material`, `status`), -- 材质+上架状态,用于首页筛选 ADD FULLTEXT(`name`, `description`); -- 全文索引,支持“玫瑰金项链”模糊搜玄学提示:
FULLTEXT索引在MySQL 5.7+需innodb_ft_min_token_size=2(默认4),否则“金”字被忽略。修改后重启MySQL:# my.cnf中添加 [mysqld] innodb_ft_min_token_size=2
3.3 用户地址表:为什么用JSON字段存省市区?
传统做法:t_user_address表里建province_id,city_id,district_id三字段,再关联地区字典表。但毕设项目没必要搞复杂字典管理。MySQL 5.7+支持JSON,直接存结构化地址:
ALTER TABLE `t_user_address` ADD COLUMN `region_json` JSON COMMENT '{"province":"广东省","city":"深圳市","district":"南山区"}';Java实体类用@Column(columnDefinition = "json")映射,MyBatis无需额外处理。优势:增删地址时不用查字典表,导出数据时地址信息完整,且region_json->>"$.city"可直接在WHERE中用。
4. Bootstrap不是套模板:响应式珠宝详情页的3个避坑实战
4.1 图片懒加载:珠宝高清图卡顿的根源
珠宝详情页常含5-10张4K图,直接<img src="xxx.jpg">会导致首屏白屏3秒。Bootstrap 4+原生不支持懒加载,必须手写:
<!-- 商品详情页图片区域 --> <div class="row"> <div class="col-md-6"> <img>// 引入lazysizes.min.js后,自动监听data-src // 但需加CSS防止占位塌陷 .lazyload { opacity: 0; transition: opacity 0.3s; } .lazyloaded { opacity: 1; }翻车现场:学生用Bootstrap Carousel轮播图,每张图都
src=真实地址,结果页面加载时所有图并发请求,Chrome Network面板显示10个pending,导师点开就卡死。用>/* 自定义CSS修复 */ .product-description { word-break: break-word; /* 关键:允许长单词内断行 */ overflow-wrap: break-word; /* 兼容旧浏览器 */ }同时,Bootstrap 4的
<p>标签默认margin-bottom: 1rem,详情页段落太多会撑开高度。统一重置:.product-detail p { margin-bottom: 0.5rem; /* 压缩段间距 */ line-height: 1.6; /* 提升可读性 */ }4.3 表单验证:珠宝定制字段的动态校验逻辑
定制刻字字段
engraving_text要求:非空、长度2-10字符、不能含特殊符号。Bootstrap Validator插件已过时,直接用HTML5原生+JS增强:<input type="text" id="engraving" name="engravingText" maxlength="10" pattern="^[a-zA-Z0-9\u4e00-\u9fa5\s]{2,10}$" required title="请输入2-10个字母、数字、汉字或空格">// 动态提示(避免用户提交后才弹alert) $('#engraving').on('input', function() { const val = $(this).val(); if (val.length < 2) { $(this).addClass('is-invalid'); $('#engraving-feedback').text('刻字至少2个字符'); } else if (!/^[a-zA-Z0-9\u4e00-\u9fa5\s]{2,10}$/.test(val)) { $(this).addClass('is-invalid'); $('#engraving-feedback').text('仅支持字母、数字、汉字、空格'); } else { $(this).removeClass('is-invalid').addClass('is-valid'); $('#engraving-feedback').text(''); } });5. MyBatis开发不是写SQL:珠宝业务场景下的3大避坑指南
5.1 多表关联N+1:订单列表页性能崩盘的根因
典型写法:先查
t_order列表,循环中对每个order_id查t_order_item。100个订单触发101次SQL,数据库CPU飙升。正确解法:MyBatis
collection嵌套查询 +fetchType="lazy"<!-- OrderMapper.xml --> <resultMap id="OrderWithItems" type="Order"> <id property="orderId" column="order_id"/> <result property="orderNo" column="order_no"/> <result property="totalAmount" column="total_amount"/> <collection property="items" ofType="OrderItem" column="order_id" select="selectOrderItemsByOrderId" fetchType="lazy"/> <!-- 关键:延迟加载,需要时才查 --> </resultMap> <select id="selectOrders" resultMap="OrderWithItems"> SELECT order_id, order_no, total_amount FROM t_order WHERE status = #{status} ORDER BY create_time DESC LIMIT #{offset}, #{limit} </select> <select id="selectOrderItemsByOrderId" resultType="OrderItem"> SELECT * FROM t_order_item WHERE order_id = #{orderId} </select>排查技巧:开启MyBatis日志,看是否出现
selectOrderItemsByOrderId被调用100次。若出现,说明fetchType="lazy"未生效,检查MyBatis配置中是否关闭了延迟加载(<setting name="lazyLoadingEnabled" value="true"/>)。5.2 动态SQL防注入:价格排序的
ORDER BY安全写法前端传参
sortField=price&sortOrder=desc,若直接ORDER BY ${sortField} ${sortOrder},黑客传sortField=id; DROP TABLE t_product;--就完了。安全方案:白名单校验 +
bind标签预编译<select id="searchProducts" resultType="Product"> <bind name="safeSortField" value="'price' == sortField ? 'price' : 'create_time'" /> <bind name="safeSortOrder" value="'desc' == sortOrder ? 'DESC' : 'ASC'" /> SELECT * FROM t_product WHERE status = 1 <if test="minPrice != null and maxPrice != null"> AND price BETWEEN #{minPrice} AND #{maxPrice} </if> ORDER BY ${safeSortField} ${safeSortOrder} LIMIT #{offset}, #{limit} </select>5.3 事务边界失控:支付成功后库存没扣减的真相
常见错误:
@Transactional加在Service方法上,但方法内调用另一个Service方法时,若后者没加@Transactional,事务不传播,库存扣减失败。珠宝支付流程正确事务划分:
@Service public class OrderService { @Transactional(rollbackFor = Exception.class) public void createOrder(Order order) throws Exception { // 1. 插入订单(t_order) orderMapper.insert(order); // 2. 扣减库存(关键:必须在同一事务) for (OrderItem item : order.getItems()) { productMapper.reduceStock(item.getProductId(), item.getQuantity()); } // 3. 发送支付通知(此处可异步,但库存扣减必须同步完成) paymentService.notifyPayment(order.getOrderNo()); } }后悔药:若已部署发现库存不扣,紧急补救:在
reduceStock方法上加@Transactional(propagation = Propagation.REQUIRED),确保嵌套调用也纳入事务。6. 毕设答辩前的终极验证:用3个真实场景压测你的珠宝商城
6.1 场景一:高并发首页访问(模拟10人同时刷首页)
目的:验证MySQL连接池、MyBatis缓存、Bootstrap静态资源是否扛得住
操作步骤:
- 启动项目,清空浏览器缓存
- 打开Chrome开发者工具 → Network → Disable cache勾选
- 打开首页,按F5刷新10次(不要用Ctrl+R,那是强制刷新)
- 观察Network面板:
GET /响应时间应<800ms(含HTML+CSS+JS)GET /product/list(AJAX接口)响应时间应<300ms- 若出现
Failed或Pending,检查HikariCP连接池是否耗尽(日志搜HikariPool-1 - Connection is not available)关键指标:
指标 合格线 不合格表现 首页HTML加载 <1.2s 页面空白超2秒 商品列表API <400ms 控制台报 net::ERR_CONNECTION_REFUSEDCSS/JS文件 304 Not Modified 每次刷新都200(未启用Etag) 6.2 场景二:复杂搜索(价格+材质+品类三条件联查)
目的:验证MySQL索引是否生效、MyBatis分页是否准确
测试用例:
- URL:
/product/search?minPrice=2000&maxPrice=8000&material=gold&categoryId=2- 预期:返回
< 50条记录,且第1页数据中price全在2000-8000间,material='gold',category_id=2验证方法:
- 查看MyBatis日志输出的SQL,确认是否走了
idx_price_category索引(EXPLAIN显示type=range)- 在MySQL命令行执行相同SQL,加
EXPLAIN前缀:合格结果:EXPLAIN SELECT * FROM t_product WHERE price BETWEEN 2000 AND 8000 AND material='gold' AND category_id=2;key列显示idx_price_category,rows<5006.3 场景三:订单创建全流程(从加购到支付成功)
目的:验证事务一致性、数据库约束、前端交互闭环
必测路径:
- 浏览商品 → 点击“加入购物车” → 购物车数量+1
- 进入购物车 → 点击“去结算” → 填写收货地址 → 提交订单
- 查看
t_order表:新订单status=0(待支付)- 模拟支付回调:直接执行SQL
UPDATE t_order SET status=1 WHERE order_no='ORD20240001'- 刷新订单列表页:该订单状态变为“已支付”,且
t_order_trace表新增一条event_type='paid'记录血泪教训:
- 若订单状态不变,检查
OrderService.createOrder()是否被try-catch吞掉异常(毕设常见错误:捕获Exception却不抛出,事务自动回滚但前端无感知)- 若
t_order_trace无记录,确认@Transactional是否加在调用insertTrace()的方法上(事务方法内调用非事务方法,trace插入不回滚)我带过的最稳毕设,都是答辩前用这三个场景自己跑3遍:第一遍记下所有报错,第二遍针对性修,第三遍全程录屏给导师看。没有花哨的微服务架构,就靠SSM+MyBatis+MySQL+Bootstrap把珠宝电商的“搜-看-买-付-管”闭环跑通,反而让导师眼前一亮——因为真实项目从来不是炫技,而是把每个技术点扎进业务毛细血管里。希望帮到你。
本文还有配套的精品资源,点击获取