简介:一套基于JSP+Java+MySQL的家用电器销售网站完整项目,面向计算机相关专业毕业设计、课程设计以及Java Web初学者,也可作为商城类项目的参考模板。系统采用动态网页开发技术,分为管理员、普通用户和前台首页三层业务模块,管理员可管理用户、商品分类、品牌、商品信息、订单评价、留言板等,用户可进行商品浏览、购物车、下单、订单评价和收藏管理,覆盖了电商系统常见核心流程。压缩包内包含完整源码、说明文档、LW(论文)、PPT以及演示视频等多种文件,整体大小约119.19MB,其中源码可直接部署运行,文档与PPT便于理解设计思路,演示视频则可快速掌握操作流程。目前已有80人学习下载,对于需要完成相似课题或想深入JSP开发与MySQL数据库设计的人来说,是一份内容完备、结构清晰的成体系方案。
1. 家用电器销售网站:一个 JavaWeb 课程设计的完整闭环
做 Java 课程设计或毕业设计的同学,最怕的不是写不出代码,而是东拼西凑的功能凑不成一个完整的项目闭环。家用电器销售网站这个选题,看起来普通,实际是 JavaWeb 里最典型的全流程系统:前台有商品浏览、购物车、下单,后台有商品管理、订单处理、用户管理,角色清晰、模块完整,JSP 做页面、Java 做逻辑、MySQL 存数据,一套下来把增删改查、Session、事务、关联查询这些必考知识点全占了。它适合三类人:正在做课程设计或毕业设计需要交源码和论文的在校生,准备 Java 初级岗位面试想找个完整项目讲清楚的求职者,以及想快速复现一套 JavaWeb 经典项目练手的从业者。这篇笔记我按实际拆包顺序来讲,先看架构和数据库,再讲核心代码,然后走通部署,最后说避坑和答辩要点。
2. 架构与数据库先行:角色、模块与核心数据表设计
2.1 三种角色与模块边界:管理员、注册用户、游客各自能做什么
这套系统的角色划分很典型,不同于简单的单用户 CRUD 项目,它把权限边界做成了三层:未登录的游客只允许浏览首页、商品信息和商品资讯,看到购物车和下单入口后会被引导去登录;注册用户登录后可以使用购物车、提交订单、管理个人中心里的订单和收藏;管理员走独立的后台入口,负责商品分类、品牌、商品信息、订单、评价、留言板这些核心数据的维护。
模块设计上,后台管理员的功能最重,包含个人中心、用户管理、商品分类管理、品牌管理、商品信息管理、订单评价管理、留言板管理、系统管理、订单管理。用户端的个人中心聚焦在订单评价、我的收藏和订单管理三块,前台首页则聚合了商品信息、商品资讯、留言反馈、购物车和跳转后台的入口。角色功能对照可以看下表:
| 角色 | 核心模块 | 典型操作 |
|---|---|---|
| 游客 | 首页、商品信息、商品资讯 | 浏览、搜索、查看详情 |
| 注册用户 | 购物车、订单、收藏、评价 | 加购、下单、付款、评价、留言 |
| 管理员 | 商品、分类、品牌、订单、用户、留言 | 商品的增删改查、订单状态流转 |
从课程设计答辩的角度看,这种角色划分是有讲究的。评委大概率会问的一个问题是:游客和用户的操作权限在哪里控制的?答案是通过 Session 里是否存有登录用户信息来判断,后台的页面则统一加访问控制过滤器。这个设计在代码里是完整落地的,后面第 3 章我会把实现代码贴出来。
2.2 数据库表设计:从商品到订单的关联关系拆分
家用电器销售要跑通,数据库至少需要八张核心表:用户表、商品分类表、品牌表、商品信息表、订单表、订单项表、订单评价表、留言板表,另外还有收藏表和资讯表。我拆解这套源码时最关注的是订单相关的表拆分,因为它直接决定了系统能不能支持一个订单里买多件不同商品。
订单主表和订单项表是分离的,这是正确做法。订单表存订单编号、下单用户、总金额、状态、下单时间;订单项表存商品 ID、数量、单价、小计。为什么要拆两张表?因为一个订单包含多个商品,如果合在一张表里,要么一条记录塞多个商品导致无法统计,要么一个商品一条记录导致重复下单信息冗余。拆开之后,用户查询订单时先查主表拿到订单列表,再根据订单号查订单项表取明细。
商品分类和品牌也都独立成表,商品信息表通过外键关联到分类表和品牌表。这里的关联设计我建议在复现时注意看 JSP 页面是怎么取值的——商品列表页通常需要同时显示分类名称和品牌名称,如果直接用商品表的分类 ID,页面上显示的是数字而不是名称,需要做关联查询或者单独查字典表。这套项目用的是 JSP + Servlet 的经典模式,我一般会先去翻实体类和 DAO 层的 SQL 语句,看看关联查询写在哪里。
2.3 建库脚本与初始化数据:拿到的源码怎么让数据库跑起来
源码包里通常附带 SQL 脚本文件,但常见的一个问题是脚本编码混乱或者表名大小写不一致。我习惯的做法是先创建数据库,再选择脚本执行。MySQL 命令行执行方式:
-- 创建数据库,字符集统一用 utf8mb4,避免中文乱码 CREATE DATABASE IF NOT EXISTS household_appliance DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE household_appliance; -- 用户表:角色字段区分管理员与普通用户 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID', username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录名', password VARCHAR(64) NOT NULL COMMENT '密码,建议MD5存储', role TINYINT DEFAULT 0 COMMENT '0普通用户 1管理员', phone VARCHAR(11) COMMENT '手机号', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';执行完成后,检查一下各表的数据是否正常关联,重点是商品表和分类表、品牌表的外键是否有对应数据。如果脚本执行报错,优先看是不是 MySQL 版本差异导致,5.7 和 8.0 对某些默认值语法要求不同。建库这一步做完,整个项目的数据底座就算立住了,后续改数据库连接配置、启动 Tomcat 才有意义。
3. 商品、购物车与订单:三条必问流程的代码实现
3.1 商品列表与分页查询:前端展示与后端分页参数配合
商品信息是前台的门面,核心逻辑是分类筛选加关键词搜索加分页。这套源码里的商品查询实现沿用了 JSP + Servlet 的经典分层结构,页面先提交搜索条件和页码,Servlet 接收参数后调用 DAO 层拼接条件查询语句。分页参数通常是两个:当前页码 currentPage 和每页条数 pageSize,后端返回总记录数 totalCount 和当前页数据列表。
// 商品分页查询的核心方法,pageNum从1开始 public List<Goods> findGoodsByPage(int categoryId, String keyword, int pageNum, int pageSize) { StringBuilder sql = new StringBuilder("SELECT * FROM t_goods WHERE 1=1 "); List<Object> params = new ArrayList<>(); // 分类条件,categoryId为0时表示全部分类 if (categoryId > 0) { sql.append("AND category_id = ? "); params.add(categoryId); } // 关键词模糊查询,商品名或品牌名匹配 if (keyword != null && !keyword.trim().isEmpty()) { sql.append("AND (goods_name LIKE ? OR brand_id IN (SELECT id FROM t_brand WHERE brand_name LIKE ?)) "); String likePattern = "%" + keyword.trim() + "%"; params.add(likePattern); params.add(likePattern); } // 分页参数,MySQL语法:limit后第一个数字是偏移量,第二个是每页条数 sql.append("ORDER BY id DESC LIMIT ?, ?"); params.add((pageNum - 1) * pageSize); params.add(pageSize); // 执行查询并返回结果 return jdbcTemplate.query(sql.toString(), params.toArray(), goodsRowMapper); }这段代码里我特意说明两个点。第一个是WHERE 1=1,看起来是废话,实际是用来拼接动态条件时不用判断是否已有 where 关键字,直接追加AND就能拼上。第二个是分页参数的计算,(pageNum - 1) * pageSize是标准写法,第一页查 offset 0、第二页查 offset pageSize,很多人第一次写分页会在这里算错。前端 JSP 页面通过c:forEach标签循环输出商品卡片,配合c:if判断是否有下一页。
3.2 购物车的 Session 存储方案:为什么不用数据库
这套系统的购物车没有建数据库表,而是存在 Session 里的 Map 结构中。我拆代码的时候留意到,加购请求进入 Servlet,将商品 ID 和数量存进 session 里一个Map<Integer, Integer>,key 是商品 ID,value 是购买数量。选择 Session 存储的理由很直接:购物车是临时性的,用户不登录也可以加购,等结算时再要求登录,存数据库反而会产生大量未登录状态下的垃圾数据。
// 加入购物车:从Session取购物车Map,没有则新创建 @SuppressWarnings("unchecked") public String addToCart(HttpServletRequest request) { HttpSession session = request.getSession(); // 购物车结构:Map<商品ID, 购买数量> Map<Integer, Integer> cart = (Map<Integer, Integer>) session.getAttribute("cart"); if (cart == null) { cart = new HashMap<>(); } int goodsId = Integer.parseInt(request.getParameter("goodsId")); int quantity = Integer.parseInt(request.getParameter("quantity")); // 如果商品已在购物车中,数量累加,否则新增 cart.put(goodsId, cart.getOrDefault(goodsId, 0) + quantity); session.setAttribute("cart", cart); return "redirect:cart.jsp"; }这里有一个值得在答辩时讲的细节:getOrDefault方法避免了先判断再赋值的繁琐写法,直接在 Map 上做数量累加。购物车存 Session 的代价是用户关闭浏览器后购物车消失,这是可以接受的取舍,因为课程设计不需要做到持久化购物车这种电商级需求。购物车页面通过遍历这个 Map,再拿商品 ID 去数据库查出商品名称、价格、图片来展示。
3.3 下单事务:多张表写入不能“东边成功西边失败”
下单是整个系统里最需要强调事务的环节。一次下单操作会同时写订单主表、订单项表,还要更新商品库存,如果中途某个环节失败,就会出现订单创建了但明细没写入的脏数据。这套源码里的写法值得学习:在 Service 层用事务控制,要么全部成功,要么全部回滚。
// 下单操作:事务控制,确保订单主表、明细表、库存更新同时成功或同时失败 @Transactional(rollbackFor = Exception.class) public boolean createOrder(Order order, List<OrderItem> items) { // 第一步:插入订单主表,拿到生成的订单ID int orderId = orderDao.insertOrder(order); if (orderId <= 0) { return false; } // 第二步:批量插入订单明细,这里不能漏掉任何一件商品 for (OrderItem item : items) { item.setOrderId(orderId); orderDao.insertOrderItem(item); } // 第三步:扣减库存,扣减失败说明库存不足,主动抛异常触发回滚 for (OrderItem item : items) { int rows = goodsDao.reduceStock(item.getGoodsId(), item.getQuantity()); if (rows == 0) { throw new RuntimeException("商品库存不足:" + item.getGoodsId()); } } return true; }在这套 JSP + Servlet 的实现里,事务是通过 JDBCConnection的setAutoCommit(false)、commit()和rollback()手动控制的。有些从 Spring Boot 转过来的同学可能不习惯,但原理完全一样。我在阅读时看到,每个 DAO 方法都会从线程绑定的连接池拿同一个 Connection,保证事务范围内的操作走同一个数据库连接。这一步如果做不好,测试时多买两件商品就容易复现脏数据问题,建议跑通之后专门做一次取消事务的对比实验,感受会更深。
4. 从 ZIP 到跑通:源码部署的完整路径与连接配置
4.1 环境版本匹配:JDK、Tomcat、MySQL 的组合选择
这类 JSP 课程设计项目对环境版本非常敏感。我拿到一个 JavaWeb 源码包,第一步永远是确认 JDK 和 Tomcat 的版本兼容性,而不是直接导入 IDEA。这套家用电器销售网站基于 JSP 和 Servlet 规范,最稳妥的组合是 JDK 1.8 + Tomcat 8.5 + MySQL 5.7,如果你已经在用 JDK 11 或更高版本也没问题,但要注意 Tomcat 版本得跟着升级到 9.x,否则可能出现 Servlet API 不兼容的报错。
需要注意的是,Tomcat 10 及以后的版本把包名从javax.servlet改成了jakarta.servlet,老项目直接部署会报 ClassNotFoundException。建议优先使用 Tomcat 8.5 或 9.0,这不算技术落后,而是课程设计项目十年的沉淀都在 javax.servlet 体系下。数据库方面 MySQL 8.0 也可以用,但驱动配置要改成新版驱动类名,第 5 章避坑部分会细说。
4.2 导入工程与配置数据库连接:最容易翻车的三个配置项
把源码包解压后,用 Eclipse 或 IDEA 导入 Maven 或普通 Web 工程。导入后第一个要改的是数据库连接配置文件,常见位置是src/jdbc.properties或者src/db.properties,也有直接在 DAO 工具类里硬编码的情况,拆包时先全局搜jdbc:mysql把这几个参数找出来。
# 数据库连接配置:host、端口、库名、用户名、密码 jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/household_appliance?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=123456这五个配置项里最容易翻车的是 URL 中的characterEncoding=utf8。如果漏掉,Tomcat 返回页面时中文正常,但写入数据库的数据会变问号,排查起来很隐蔽。还有密码如果包含&符号,在 properties 文件里需要转义写成\&,否则后面的参数解析会被切断。配置文件改好之后,需要把项目用的 JDBC 驱动 jar 包放进WEB-INF/lib目录,这一步有些教程不会提醒,但实际上缺了驱动会在启动后第一次查询数据库时报ClassNotFoundException。
4.3 启动顺序与验证路径:先登录还是先进后台
部署完成后启动 Tomcat,在浏览器输入http://localhost:8080/项目名/,能看到首页说明部署成功。但真正验证系统跑通,我建议按一条固定路径走:先注册一个新用户,然后在前台浏览商品、加入购物车、提交订单,再去个人信息里查看订单状态;接着退出来用管理员账号登录后台,在订单管理中看到刚刚提交的订单。这条路径覆盖了用户、商品、订单、后台管理四个最核心的功能模块,任何一个环节失败都能快速定位问题出在前台还是后台。
管理员账号通常在 SQL 脚本里初始化好了,如果不确定账号和密码,可以在 user 表里查一下,密码多为 MD5 加密,也可以在代码的登录逻辑里临时打印明文日志来确定。演示视频如果拍得够完整,这个时候可以对照着看每个操作序列是否一致。我一般会给学员的复现建议是:先看视频里的操作顺序,再对着说明文档跑一遍,最后才翻源码,这样的节奏能避免被代码细节绕晕。
5. 避坑记录:五个常见启动失败问题与排查顺序
5.1 Tomcat 启动报错:java.lang.ClassNotFoundException: javax.servlet.jsp
现象:Tomcat 启动时控制台直接抛java.lang.ClassNotFoundException,指向javax.servlet.jsp相关的类。
原因:项目里引入的 Tomcat 运行时版本和编译时的 Servlet API 版本不一致,最常见的是把 Tomcat 9 的项目跑在 Tomcat 10 上,或者项目的WEB-INF/lib里放了一份老版本的 servlet-api.jar,跟 Tomcat 自带的冲突。
解决:先用mvn dependency:tree或直接看WEB-INF/lib目录排查有没有多余的 servlet-api.jar,有就删掉。确认 Tomcat 版本与项目编译目标一致:JDK 1.8 用 Tomcat 8.5 或 9.0,不用 10。我从拆包角度说一句:遇到 Servlet 类找不到时,优先怀疑 Tomcat 版本选错了,其次是 lib 下存在版本冲突。
5.2 MySQL 连不上:Access denied for user 'root'@'localhost'
现象:系统启动不报错,但点击商品列表或登录时页面报Access denied或Communications link failure。
原因:数据库连接配置里的密码错误,或者 MySQL 8.0 的默认认证插件是caching_sha2_password,老版本的 JDBC 驱动不认识这种认证方式。
解决:先检查jdbc.properties的密码与数据库实际密码是否一致,可以在命令行用同样的账号密码手动连一下。如果是 MySQL 8.0,把连接驱动换成com.mysql.cj.jdbc.Driver,并把 URL 加上useSSL=false&serverTimezone=Asia/Shanghai避免时区告警。这条是血泪经验,MySQL 8 的驱动配置跟 5.7 完全是两套写法,不要混用。
5.3 中文乱码:页面正常写入数据库变问号,以及反向情况
现象:前台页面展示正常,但后台管理员添加商品后,数据库里存的中文变成???,或者反过来页面显示乱码。
原因:MySQL 连接 URL 少了characterEncoding=utf8,或者数据库本身的字符集不是 utf8mb4,又或者 JSP 页面没设置pageEncoding。
解决:三层都要检查。第一层看jdbc.properties的 URL 里有没有useUnicode=true&characterEncoding=utf8;第二层看建库语句字符集是否utf8mb4;第三层在 JSP 文件头部确认<%@ page contentType="text/html; charset=UTF-8" %>存在。按这个顺序排查,五分钟内能搞定,我以前第一次跑这类项目时在这上面卡了整整一个晚上。
5.4 端口被占用:Tomcat 启动后立刻失败,日志显示 8080 端口被占用
现象:启动 Tomcat 几秒后自动停止,日志里有Port 8080 required by Tomcat v8.5 Server at localhost is already in use。
原因:上一个 Tomcat 实例没有正常关闭,或者其它程序占用了 8080 端口。
解决:在命令行执行netstat -ano | findstr 8080找到占用端口的进程 PID,再用taskkill /PID <PID> /F结束进程。如果不想关进程,也可以改 Tomcat 的server.xml里的<Connector port="8080",比如改成8081,但改端口之后访问 URL 要同步变化,建议直接杀进程省事。
5.5 JSP 编译失败:org.apache.jasper.JasperException: Unable to compile class for JSP
现象:访问某个具体页面时报JasperException,指向 JSP 编译错误,而不是页面直接 404。
原因:JSP 文件里用了 JDK 高版本的语法特性,或者某个自定义标签引用的类没有成功编译,还有可能是源码包里的 .java 文件没有预先编译,JSP 页面引用的类不存在。
解决:在 IDEA 或 Eclipse 里先执行Build -> Rebuild Project,确保障编译通过再部署到 Tomcat。有些源码依赖 lib 目录里的 jar,检查WEB-INF/lib是否完整。如果是 JDK 版本高于编译版本导致的语法不兼容,把项目编译级别调到与目标 JDK 一致,比如 1.8。这个问题涉及的变量比较多,我建议排错时先看 Tomcat 日志里具体是哪一行 JSP 编译失败,它通常会给出行号和列号,比盲试效率高很多。
6. 答辩与面试怎么讲:验证路径、追问点与改造方向
整套系统跑通之后,最容易被追问的其实不是某个函数怎么写,而是设计决策。第一个高频问题是:为什么商品分类不直接存在商品表里,而要单独建一张分类表?答案是因为同一分类下有多个商品,单独建表才能保证分类名称只维护一次,商品表里存分类 ID 来关联。
第二个高频问题是购物车为什么不存数据库。我习惯用一句话回答:购物车是用户临时行为,Session 存储不需要读库,性能更好,而且未登录用户也能加购,等结算时强制登录即可。如果面试官继续问 Session 购物车有什么缺陷,可以主动提丢失风险和高并发下的性能瓶颈,然后补充说如果要做持久化购物车,需要增加购物车表和登录后合并逻辑。
第三个问题通常落在订单事务上。你需要说清楚为什么createOrder方法要加事务:订单主表、明细表、库存扣减是三张表的写入,任何一步失败都会造成数据不一致。明白了这一点,再看 Spring Boot 改造方向就很简单:把 JSP 换成 Thymeleaf 或 Vue 前后端分离,把 Servlet 换成 Controller,把手动事务换成@Transactional,DAO 换成 MyBatis 的 Mapper 接口。这套项目里的实体类、表结构、业务逻辑都可以沿用,这是它作为课程设计源码最值钱的部分——它是一个完整的业务模型,而不只是零散的页面。
验证收尾时我会跑一遍黄金链路:注册新用户,搜索“冰箱”,加购两件,提交订单,后台确认订单,用同一个账号评价商品。整个过程能走通,说明前台的用户体系、商品模块、订单模块和后台的管理模块全部正常。从那以后我每次拿到这类 JavaWeb 源码包,都强制自己先跑通这条链路再去翻其它功能,避免被单点功能的假象误导。希望这篇拆解帮到你,踩坑少一点,答辩顺一点。
本文还有配套的精品资源,点击获取