JavaWeb水果销售系统源码解析:Servlet+JSP+MySQL实战 简介面向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 脚本核一遍缺了哪张对应功能一定是残缺的。表名核心字段作用userid, username, password, phone, address前台用户注册登录categoryid, name水果分类如热带水果、时令水果fruitid, category_id, name, price, stock, image, sales水果商品主表cart_itemid, user_id, fruit_id, count购物车明细ordersid, order_no, user_id, total_price, status, create_time订单主表order_itemid, order_id, fruit_id, price, count订单快照明细订单表里 status 字段值得多说一句。很多课程设计把它设计成 int 型0 代表待付款1 代表已付款待发货2 代表已完成。这个设计简单直观但要注意一旦订单状态大于 1商品就不能再删除或改价否则订单明细会跟着变。所以真正合理的做法是 order_item 里存下单那一刻的价格快照而不是下单时去关联 fruit 表的当前价格。你读源码时可以看看它有没有这么做没有的话可以作为一个改进点写进课程设计报告里。数据脚本里的字符集也要注意。老一点的源码建表语句是 DEFAULT CHARSETutf8在 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.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/fruit_sales?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 jdbc.usernameroot jdbc.password123456上面这组配置是针对 MySQL 8.0 的写法。如果你的 MySQL 是 5.7驱动那行改成 com.mysql.jdbc.DriverURL 里的 serverTimezone 可以不写但 useSSLfalse 建议留着避免出现 SSL 握手相关的警告刷屏。参数说明useSSLfalse 是关闭 SSL 连接本地开发没必要开加密开了反而慢serverTimezoneAsia/Shanghai 是为了解决 MySQL 8.0 默认时区与 JVM 时区不一致导致的报错characterEncodingutf8 是保证写入数据库的中文不乱码。三条缺一不可尤其时区参数不加的话控制台会直接抛 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 还是 ListMapInteger, Integer cart (MapInteger, 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.encodingUTF-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 节给的一致重点检查有没有 serverTimezoneAsia/Shanghai。再确认密码建议先在命令行里用同样的用户名密码手动连一次 MySQL确认能连上以后再去查代码。记住一个血泪经验密码如果包含、这样的字符在 .properties 文件里需要转义否则从开始的内容会被当成参数丢弃。5.3 访问路径 404Application 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 里加URIEncodingUTF-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核心三行driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/fruit_sales?useSSLfalseserverTimezoneAsia/Shanghai initialSize5连接池的好处是连接复用而不是每次请求都重新建立 TCP 连接。改造完以后你可以在答辩时讲清楚「为什么连接池能提高性能」这比讲 HashMap 原理更有说服力。第二个动作是密码加密。现在源码里大概率是明文存储密码你只需要在注册时对密码做一个 MD5 加盐处理登录时再按相同规则比对。MD5 虽然不算安全但至少能说明你懂「密码不能明文落库」这条底线也顺带把注册、登录两个 Servlet 的代码改得更规范。第三个动作是加一个简易分页。商品列表页如果只有十几条数据看不出来但你可以让每页显示 8 条用 MySQL 的LIMIT ? OFFSET ?实现。步骤是Servlet 接收 page 参数Service 层查总条数和当前页数据JSP 页面上渲染上一页/下一页链接。第四个动作是验证订单金额的精度。源码里 double 是最常见的类型但做金额计算时 0.1 0.2 的浮点误差会让你对不上账。把所有涉及金额的字段统一改成BigDecimal下单价、总价、订单明细价格都走这个类型然后跑两遍完整的购物流验证一遍。我当年拿到第一份 JavaWeb 源码时就栽在乱码和 404 上改了整整一天。现在每拿到一份新源码第一件事是先看配置文件再启动跑通一遍主流程然后才开始读代码。建议你也按这个顺序来先确认它是个活项目再谈改造。希望帮到你。本文还有配套的精品资源点击获取