简介:面向JavaWeb初学者的水果销售系统完整项目源码包,适合课程设计、毕业设计或日常练手。项目以真实水果销售业务为背景,完整覆盖Servlet、JSP、JavaBean、JDBC数据库交互与MVC分层设计,同时涉及前端页面渲染、Session会话管理、安全防护和Tomcat部署思路,能帮助学习者把零散知识点串联成完整项目。压缩包共843个文件、16.2MB,页面与交互部分以jsp、html、js、css为主,后端业务逻辑由java及class承担,sql文件提供建库建表与示例数据,jar为依赖库,gif/png/jpg等作为界面和文档素材,整体目录清晰、便于按模块检索。资源还附带演示视频,可对照源码逐步理解商品展示、购物车、订单管理、用户登录等典型功能的具体实现,也能从环境配置、数据库导入到部署运行完整走通一遍。已有3002人学习下载,对巩固JavaWeb理论、积累课程设计实战经验很有帮助。
1. 水果销售系统源码:先搞清楚它是一套什么结构的 JavaWeb 项目
很多刚学完 Servlet/JSP 的朋友,手里最缺的是一个能完整跑起来的「JavaWeb 项目源码水果销售系统」——功能不能太复杂,但登录注册、商品展示、购物车、下单结算、后台管理又都得有。这类源码在网上流传很广,也正是课程设计和毕业设计的常客。
它解决的问题很具体:把散装的知识点串成一个闭环。从前端 JSP 页面发请求,到 Servlet 接收参数,再到 DAO 层用 JDBC 读写 MySQL,最后把结果渲染回页面。跑通它,JavaWeb 的主干道你就走过一遍了。
适合谁?准备答辩的学生、想找完整案例练手的自学者、以及想快速搭一套演示系统出去谈项目的开发。接下来我会把它的技术栈、运行配置、核心代码和常见坑按落地顺序拆开讲,保证你照着做能把它从压缩包变成浏览器里能点的页面。
2. 技术栈拆解:Servlet + JSP + MySQL 为什么撑得起一套销售系统
2.1 Servlet + JSP + MySQL:这套组合是源码的默认选择
「JavaWeb项目源码」这个标签下,出现频率最高的技术组合就是 Servlet + JSP + MySQL,而不是 Spring Boot + MyBatis。原因有两条:第一,它是 JavaWeb 课程里最标准的教学路线,Servlet 生命周期、JSP 内置对象、JDBC 操作数据库都是必考知识点;第二,这类源码主要面向课程设计和毕设,用上框架以后,答辩时老师第一个问题往往就是「这行代码底层在做什么」,纯 Servlet 反而更容易答得清楚。
MySQL 在这里承担的是库存和订单的持久化。水果销售系统的数据量不大,单机 MySQL 完全够用,而且 JDBC 直连的方式代码量少,学生能在两三百行内看懂一条完整的数据流。相比之下,如果引入 MyBatis 或 Hibernate,多出来的 mapper 和 session 工厂反而把主逻辑盖住了。
我拿到任何一份这类源码,会先看一眼是否满足三个特征:有 sql 脚本、有 JDBC 工具类、用 Tomcat 做容器。三者齐全,说明这份源码是完整的,值得继续折腾;缺了 sql 脚本的,后面所有表结构都要自己猜,建议直接换一份。
2.2 拿到源码先认目录:src、web 与 sql 脚本
不管压缩包叫什么名字,典型结构基本一致。src 下是 Java 代码,按 bean、dao、service、servlet、filter、util 分层;web 目录下是 JSP 页面和静态资源;根目录或 doc 目录里放一个 .sql 文件。阅读顺序建议是:先打开 sql 脚本看表结构,再按「bean → dao → service → servlet → jsp」的顺序读代码。
拿到源码后第一件事不是点开 IDEA,而是用压缩软件看一眼有没有 web.xml。Servlet 3.0 之前的路由配置全靠它,很多老源码没有用 @WebServlet 注解,一旦你导入项目时没把 web.xml 收入工程,所有请求都会 404。
还有一层容易漏掉:有些源码把 JSP 直接放在 web 根目录下,有些放在 WEB-INF 下面。放根目录的可以直接访问,放 WEB-INF 下的必须经过 Servlet 转发,否则浏览器直接敲路径永远打不开。这决定了你后面排查 404 时要不要往路由配置方向想。
2.3 数据库表设计:水果销售闭环至少需要这六张表
水果销售系统的表结构不算复杂,但它是整份源码里信息量最大的一处。基本盘是六张表,你可以对着手里的 sql 脚本核一遍,缺了哪张,对应功能一定是残缺的。
| 表名 | 核心字段 | 作用 |
|---|---|---|
| user | id, username, password, phone, address | 前台用户注册登录 |
| category | id, name | 水果分类,如热带水果、时令水果 |
| fruit | id, category_id, name, price, stock, image, sales | 水果商品主表 |
| cart_item | id, user_id, fruit_id, count | 购物车明细 |
| orders | id, order_no, user_id, total_price, status, create_time | 订单主表 |
| order_item | id, order_id, fruit_id, price, count | 订单快照明细 |
订单表里 status 字段值得多说一句。很多课程设计把它设计成 int 型,0 代表待付款,1 代表已付款待发货,2 代表已完成。这个设计简单直观,但要注意:一旦订单状态大于 1,商品就不能再删除或改价,否则订单明细会跟着变。所以真正合理的做法是 order_item 里存下单那一刻的价格快照,而不是下单时去关联 fruit 表的当前价格。你读源码时可以看看它有没有这么做,没有的话可以作为一个改进点写进课程设计报告里。
数据脚本里的字符集也要注意。老一点的源码建表语句是 DEFAULT CHARSET=utf8,在 MySQL 8.0 里依然能用,但如果你插入 emoji 表情或者特殊符号,会直接报 Incorrect string value。建议导入后手动改成 utf8mb4,后面章节我会再讲导入时具体的坑。
3. 把源码跑起来:IDEA + Tomcat + MySQL 的配置顺序与最小改动
3.1 环境版本搭配:JDK 8 + Tomcat 8/9 + MySQL 5.7/8.0
运行这类 JavaWeb 源码,环境版本搭配是第一个玄学点。我一般推荐的组合是 JDK 8 + Tomcat 8.5 或 9.0 + MySQL 5.7 或 8.0,用 IDEA 2020 之后的版本都能正常跑。JDK 版本不建议直接用 17,因为老源码里如果用了 Tomcat 7 或某些旧版 JSTL 依赖,在高版本 JDK 下会遇到模块化相关的报错。
Tomcat 版本和 JDK 8 搭配时有个细节:Tomcat 10 及以上的包名从 javax.servlet 改成了 jakarta.servlet,而绝大多数 JavaWeb 项目源码都是用 javax.servlet 写的。你以为只是导个包,实际上是所有 import 全部报红,所以看到「源码 + Tomcat 10」的组合,建议直接换回 Tomcat 9,省掉一堆麻烦。
MySQL 8.0 和 5.7 的区别集中在连接驱动和 URL 参数上,后面配置章节会细说。如果你本机装的是 MySQL 5.7,跑这类源码最省心;装的是 8.0 也不怕,改两行配置就行,但别装 MySQL 5.5 或更老版本,默认存储引擎和排序规则会让脚本导入时各种报错。
3.2 导入 SQL 脚本:先建库再导入
拿到 sql 脚本的第一步是在 MySQL 里建一个空库,再导入数据。不要直接双击 .sql 文件用文本编辑器打开复制粘贴,尤其是脚本比较大的时候,编码不一致会导致表结构建出来了、数据全是乱码。
CREATE DATABASE IF NOT EXISTS fruit_sales DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE fruit_sales; SOURCE /path/to/fruit_sales.sql;这段 SQL 的含义是:先创建一个名为 fruit_sales 的数据库,字符集用 utf8mb4,然后切换到该库,再执行脚本文件。用 SOURCE 而不是 mysql 命令行的输入重定向,是因为它能显示每条语句执行的成功或报错,方便定位是哪张表出了问题。
如果你习惯用命令行导入,也可以这样写:
mysql -u root -p fruit_sales < fruit_sales.sql这里 -p 后面会交互式要求输入密码,fruit_sales 必须提前创建好。很多人在这一步翻车是因为直接用 root 账号从别处复制了别人的 sql,脚本里带了原有的 CREATE DATABASE 语句,导致在你自己机器上执行时错乱。所以先建空库再用 USE 指向,是最稳妥的做法。
3.3 修改 JDBC 配置:db.properties 里三个必查项
这类源码的数据库连接信息通常集中在 src 下的 db.properties 或 JDBCUtil.java 里。你需要改的是四个值:驱动类、连接地址、用户名、密码。
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/fruit_sales?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8 jdbc.username=root jdbc.password=123456上面这组配置是针对 MySQL 8.0 的写法。如果你的 MySQL 是 5.7,驱动那行改成 com.mysql.jdbc.Driver,URL 里的 serverTimezone 可以不写,但 useSSL=false 建议留着,避免出现 SSL 握手相关的警告刷屏。
参数说明:useSSL=false 是关闭 SSL 连接,本地开发没必要开加密,开了反而慢;serverTimezone=Asia/Shanghai 是为了解决 MySQL 8.0 默认时区与 JVM 时区不一致导致的报错;characterEncoding=utf8 是保证写入数据库的中文不乱码。三条缺一不可,尤其时区参数,不加的话控制台会直接抛 SQLException,报错信息里明确写着 serverTimezone 相关字样。
还有一个隐蔽位置:如果源码用了 C3P0 或 Druid 连接池,配置文件可能是 c3p0-config.xml 或 druid.properties,改动逻辑一样,但要注意连接池的初始连接数、最大连接数如果设置得过小,高并发下单时会报连接不够用。本地调试建议把最大连接数调到 20 以上。
3.4 IDEA 运行配置:Artifact 与 Application context
数据库配好以后,最大的门槛在 IDEA 里把项目挂到 Tomcat 上。很多人卡在 IDEA 运行 javaweb 项目配置这一步,常见做法是:
- 用 IDEA 打开项目根目录,选择信任项目;
- 打开 Project Structure,确认 Artifacts 里已经有一个 war exploded 类型的输出;
- 打开 Run/Debug Configurations,新增 Tomcat Server → Local;
- 在 Deployment 标签页里把上面的 Artifact 加进去,Application context 设为
/fruit_sales或者干脆/; - 修改浏览器打开的 URL,确保与 Application context 一致;
- 点击运行,等 Tomcat 控制台输出启动成功。
为什么选 war exploded 而不是 war?因为 exploded 是解压目录,修改 JSP 或静态资源后刷新浏览器就能生效,不需要重新打包。war 格式每次改动都要重新构建,调试效率低很多。
应用上下文(Application context)是 404 的重灾区。假设你设的是/fruit_sales,那么登录页的访问路径就是http://localhost:8080/fruit_sales/login.jsp,而代码里如果写死跳转/login.jsp,就会找 Tomcat 根路径下的资源,直接 404。所以要么你在 IDEA 里把 Application context 设为/,要么改代码里所有跳转路径,二选一,别混着来。
4. 核心代码阅读路线:登录、购物车、下单与状态流转是怎么串起来的
4.1 登录与 Session:过滤器把未登录用户挡在页面外
登录模块是所有 JavaWeb 项目源码里套路最统一的部分:用户提交用户名密码,Servlet 从数据库比对,成功就把 user 对象塞进 Session,失败就返回错误提示。但真正值得读的是过滤器,它是整个访问控制的阀门。
正常的登录过滤器长这样:
public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; HttpSession session = request.getSession(); Object user = session.getAttribute("user"); String uri = request.getRequestURI(); if (user != null || uri.endsWith("login.jsp") || uri.contains("LoginServlet")) { chain.doFilter(req, resp); } else { response.sendRedirect(request.getContextPath() + "/login.jsp"); } } }这段逻辑的意图是:已经登录的用户直接放行;未登录的用户如果访问的是登录页或登录接口本身,也放行,否则统一重定向回登录页。注意它这里用了 getRequestURI 做后缀匹配,而不是写死完整路径,这样项目换上下文名时过滤器还能正常工作。
在实际项目里我会建议把静态资源也放行掉,比如 CSS、JS、图片路径,否则未登录用户连登录页的样式都加载不出来。判断方式一般是在 uri 里包含/static/或/css/、/images/时直接放行,源码里如果没有这段,你在登录页看到「裸奔」的界面就是过滤器拦截了静态资源。
4.2 购物车:Session 存储还是数据库表存储
水果销售系统的购物车有两种实现方式,源码里哪个都不奇怪。一种是用 Session 存一个 Map,键是水果 id,值是购买数量;另一种是像我上面表设计里写的那样,单独建一张 cart_item 表,用户每次加购都写进 MySQL。
两种方案各有适用场景。Session 实现代码量少,不用建表,重启 Tomcat 购物车就清空,适合演示和答辩;数据库表实现可以做到用户换个设备购物车还在,但每个加购动作都要发一条 SQL,后面做并发时还要考虑脏读。
我读这类源码时会先看加购接口,找到购物车的存储结构是 Map 还是 List:
Map<Integer, Integer> cart = (Map<Integer, Integer>) session.getAttribute("cart"); if (cart == null) { cart = new HashMap<>(); session.setAttribute("cart", cart); } cart.put(fruitId, cart.containsKey(fruitId) ? cart.get(fruitId) + 1 : 1);这段代码是把水果 id 和数量放到 Session 的 Map 里。containsKey 判断是为了处理「同一水果加购两次」的情况,第一次加购数量直接为 1,第二次就在原数量上加 1。注意这里没有做库存校验,严谨一点应该在 put 之前查一次 fruit 表的 stock 字段,否则用户加购数量超过库存,下单时才发现不够。
最小值是 Session 方案的一个明显缺点:业务逻辑放在 Servlet 里,适合小项目;如果源码用的是 cart_item 表,那你重点看它的删除和更新操作有没有同步库存,加购时扣库存是错的,只有下单成功才应该扣。
4.3 下订单:事务是底线
下单是整个系统里最需要谨慎的部分,因为涉及多个表的写操作。一个完整下单流程是:扣减库存 → 生成订单主表记录 → 生成订单明细 → 清空购物车。这四步里任何一步失败,前面成功的操作都得回滚,否则就会出现「库存扣了订单没生成」的数据不一致。
源码的 service 层里,这段逻辑会关联到一个事务操作:
Connection conn = DBUtil.getConnection(); conn.setAutoCommit(false); try { fruitDao.updateStock(conn, fruitId, count); // 扣库存 orderDao.insert(conn, order); // 写订单主表 orderItemDao.insert(conn, orderItemList); // 写订单明细 cartDao.clear(conn, userId); // 清购物车 conn.commit(); } catch (Exception e) { conn.rollback(); throw new RuntimeException("下单失败"); } finally { conn.setAutoCommit(true); DBUtil.close(conn); }关键在 setAutoCommit(false) 和 commit/rollback 的配对。把自动提交关掉以后,四步操作在同一个数据库连接里执行,要么全部成功一起提交,要么其中一个抛异常后整体回滚。很多课程设计源码会在这里偷懒,四个 DAO 各拿各的连接,第一个失败了第二个照样执行,这是典型的逻辑漏洞。
源码里如果扣库存用的是 UPDATE fruit SET stock = stock - ? WHERE id = ?,那并发下单时会超卖。改进方案是把条件加上库存限制:
UPDATE fruit SET stock = stock - #{count} WHERE id = #{id} AND stock >= #{count}这条 SQL 的意思是只在库存充足时才扣减,affected rows 为 0 就说明库存不足,是处理并发扣库存最简单的一种做法。你可以在改造时把它写进报告,比单纯讲事务原理要实在得多。
4.4 后台管理:商品增删改查与图片上传
后台管理模块是这类源码里最容易缩水的一部分。很多源码只有一个商品列表页面加一个新增表单,编辑和删除功能都靠 Servlet 转发实现,没有真正的权限控制。你可以在后台入口的 Servlet 里加一道管理员校验。
图片上传是后台模块里比较麻烦的点。老式源码用 commons-fileupload 组件解析 multipart 请求,上传后的图片一般保存在 web 目录下的 upload 文件夹里。要注意的是:如果 IDEA 部署方式不同,upload 目录的实际物理路径可能不在你项目源码目录下,导致上传成功后浏览器访问图片还是 404。遇到这种情况,需要在 IDEA 的 Deployment 里额外配一个外部目录映射,或者在代码里把保存路径改成绝对路径。
后台管理阅读的重点不是增删改查本身,而是你有没有察觉到「删除水果」和「购物车/订单明细」之间的外键关联。如果删水果时没有检查 cart_item 和 order_item 里是否还有引用,数据库会报外键约束错误,或者留下悬空引用。这份源码如果没处理,正好可以作为你的第一个改 bug 练手点。
5. JavaWeb 项目源码五大常见坑:从乱码到 404 的现象、原因与解决
5.1 控制台中文乱码:Tomcat 日志编码和 IDEA 编码打架
现象:Tomcat 启动日志里全是「淇℃伮」之类的乱码,但页面显示正常。
原因:IDEA 的默认文件编码是 UTF-8,而 Tomcat 8 及以下版本的日志控制台默认用系统编码读取,在中文 Windows 上通常是 GBK。两边编码不一致,日志输出自然乱码。这和项目本身没有关系,纯粹是运行环境配置问题。
解决:在 IDEA 的 Help → Edit Custom VM Options 里加一行-Dfile.encoding=UTF-8,重启 IDEA;如果还不行,再去 Tomcat 的 conf/logging.properties 里把控制台 handler 的编码改成 UTF-8。加完这两处,90% 的日志乱码都能消失。
注意:这只是让控制台日志可读,不影响项目运行时 request/response 的中文编码。那部分由第 5.4 节的字符编码过滤器负责,两码事。
5.2 数据库连接失败:驱动、时区、密码三个坑
现象:启动 Tomcat 后访问商品列表,页面报错堆栈里能看到Communications link failure或Access denied for user。
原因:Communications link failure大概率是 MySQL 8.0 的时区问题,或者是驱动类版本过旧。Access denied则是用户名密码不匹配,或者密码里带了特殊字符没有转义。
解决:先确认 db.properties 里驱动类和 URL 参数和我 3.3 节给的一致,重点检查有没有 serverTimezone=Asia/Shanghai。再确认密码,建议先在命令行里用同样的用户名密码手动连一次 MySQL,确认能连上以后再去查代码。记住一个血泪经验:密码如果包含&、=这样的字符,在 .properties 文件里需要转义,否则从&开始的内容会被当成参数丢弃。
5.3 访问路径 404:Application context 和写死路径对不上
现象:能打开登录页,但点击登录按钮后浏览器地址栏跳到http://localhost:8080/LoginServlet,然后 404。
原因:代码里用了绝对路径LoginServlet,而你的应用部署上下文是/fruit_sales,所以正确地址应该是http://localhost:8080/fruit_sales/LoginServlet。把上下文去掉,请求自然找不到资源。
解决:在 JSP 页面顶部加一个 base 标签,让所有相对路径都基于它解析:
<base href="${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/">这行代码会把当前请求的协议、主机、端口和上下文路径拼成一个基础地址,页面上所有不以/开头的相对路径都会自动带上项目名。改完后,不管 Application context 是/还是/fruit_sales,页面内部的跳转和静态资源引用都不会串。
5.4 POST 中文乱码:request.setCharacterEncoding 放错位置
现象:注册用户时,用户名里的中文在数据库里变成??,或者 JSP 页面上显示乱码。
原因:POST 请求的参数编码取决于 request 的字符集,而request.setCharacterEncoding("UTF-8")必须在第一次调用getParameter()之前执行,否则请求体已经按默认编码解析过了,后设置的编码不再生效。
解决:不要在每个 Servlet 里重复写编码设置,用一个编码过滤器统一处理:
public class EncodingFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); chain.doFilter(req, resp); } }这个过滤器要在 web.xml 里注册,并且 filter-mapping 放在所有业务 Servlet 之前。另外,Tomcat 8 及以上版本对 GET 请求的 URI 编码已经默认按 UTF-8 处理,所以 GET 参数一般不会乱码;如果你用的是 Tomcat 7,还需要在 server.xml 的 Connector 里加URIEncoding="UTF-8"。
5.5 SQL 脚本导入报错:版本和字符集差异
现象:导入 sql 脚本时提示Unknown collation或Incorrect integer value,甚至某个字段的 DEFAULT 值不被识别。
原因:一是脚本可能是在 MySQL 5.7 里导出的,用了utf8mb4_0900_ai_ci这样的排序规则,MySQL 5.7 不认;二是脚本里的建表语句没有指定字符集,跟随了 MySQL 服务端的默认配置,而你的服务端默认是 latin1。
解决:导入前手动执行SET NAMES utf8mb4;,再按照我 3.2 节的方式先建库再导入。如果脚本里某张表指定了不兼容的排序规则,用文本编辑器全局替换成utf8mb4_general_ci再导入。时间字段如果用了DEFAULT CURRENT_TIMESTAMP,要注意 MySQL 5.6.5 之前一个表里只允许一个 TIMESTAMP 字段带默认值,老版本会直接语法报错,本质上是版本兼容问题,不是你的操作问题。
6. 拿到源码之后:把教学项目改造成可演示系统的四个动作
源码跑通只是第一步。如果你想让它从「课程设计水平」提升到「能拿去演示、能写进简历」的程度,建议按下面的顺序做四个小改造。
第一个动作是换成连接池。把 JDBCUtil 里的DriverManager.getConnection()替换成 Druid 连接池,配置文件只要一个druid.properties,核心三行:
driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/fruit_sales?useSSL=false&serverTimezone=Asia/Shanghai initialSize=5连接池的好处是连接复用,而不是每次请求都重新建立 TCP 连接。改造完以后,你可以在答辩时讲清楚「为什么连接池能提高性能」,这比讲 HashMap 原理更有说服力。
第二个动作是密码加密。现在源码里大概率是明文存储密码,你只需要在注册时对密码做一个 MD5 加盐处理,登录时再按相同规则比对。MD5 虽然不算安全,但至少能说明你懂「密码不能明文落库」这条底线,也顺带把注册、登录两个 Servlet 的代码改得更规范。
第三个动作是加一个简易分页。商品列表页如果只有十几条数据看不出来,但你可以让每页显示 8 条,用 MySQL 的LIMIT ? OFFSET ?实现。步骤是:Servlet 接收 page 参数,Service 层查总条数和当前页数据,JSP 页面上渲染上一页/下一页链接。
第四个动作是验证订单金额的精度。源码里 double 是最常见的类型,但做金额计算时 0.1 + 0.2 的浮点误差会让你对不上账。把所有涉及金额的字段统一改成BigDecimal,下单价、总价、订单明细价格都走这个类型,然后跑两遍完整的购物流验证一遍。
我当年拿到第一份 JavaWeb 源码时,就栽在乱码和 404 上,改了整整一天。现在每拿到一份新源码,第一件事是先看配置文件,再启动,跑通一遍主流程,然后才开始读代码。建议你也按这个顺序来,先确认它是个活项目,再谈改造。希望帮到你。
本文还有配套的精品资源,点击获取