ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java+JSP+Tomcat+MySQL农产品销售管理系统全链路实战

Java+JSP+Tomcat+MySQL农产品销售管理系统全链路实战 简介这份资源是面向高校计算机相关专业学生与Java Web初学者的一套农产品销售管理系统完整项目基于Java、JSP与Tomcat技术栈开发采用MySQL数据库与B/S架构适合用作课程设计、毕业设计或相关项目实战参考。压缩包整体约95.93MB内含项目全套源码与完整文档源码均经过测试校正可保证百分百成功运行便于读者直接部署与二次开发。系统围绕农产品销售业务展开涵盖商品管理、订单处理、用户权限等典型模块能帮助读者理解JSP页面与后端逻辑的协作方式、数据库表结构设计以及Tomcat环境下的项目部署流程。目前已有1440人学习下载说明该方案在同类项目设计中具有一定参考价值。对于需要快速搭建Web项目、梳理B/S架构开发思路或寻找可运行案例的读者这份源码加文档的组合能提供从代码实现到设计说明的完整支撑节省自行摸索的时间。1. 农产品销售管理系统从 JSP 页面到 MySQL 落库一套能跑通的 B/S 全链路很多同学做毕设或接私活时一看到「基于 JavaJSPTomcat 的农产品销售管理系统」就头大JSP 到底还要不要学Tomcat 怎么配MySQL 表怎么设计才不返工我当年第一次搭这套东西光是把一个农产品列表页从数据库读到浏览器上就折腾了一整天翻车翻在 JDBC 驱动没放进WEB-INF/lib。这套技术栈虽然老但它是理解 B/S 架构最直白的路径——浏览器发请求Tomcat 接住JSP 渲染MySQL 存数据四层职责清清楚楚。本文面向要交毕设、要做课程设计、或者想用最低成本搭一个内部销售管理工具的开发者把环境搭建、表结构设计、JSP 页面开发、Tomcat 部署这条链路一步步拆开参数怎么设、坑在哪都写清楚。2. 环境选型与工程骨架JDK、Tomcat、MySQL 三件套怎么配不打架2.1 版本组合为什么比「装最新版」更重要这套技术栈最大的玄学就是版本兼容。JSP 本质是 Servlet 的视图层Tomcat 负责把它编译成 Servlet 再执行而 JDK 版本决定了编译目标字节码。我一般推荐 JDK 8 Tomcat 8.5 或 9 MySQL 5.7 或 8.0 这个组合原因是 JDK 8 对 JSP 的% page %指令支持最稳Tomcat 8.5 之后对 Servlet 3.1 规范实现完整MySQL 8.0 的驱动类名从com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.Driver这个细节后面会专门讲。如果你用 IDEA 2026 本地部署 Tomcat 9 却发现找不到 Tomcat Server 选项通常是因为社区版 IDEA 不内置应用服务器插件需要手动在 Settings 的 Plugins 里确认 Application Servers 相关支持已启用或者改用 Tomcat 的startup.bat独立启动再通过浏览器访问。这个坑很多人踩过不是配置错了是工具链本身的功能边界。MySQL 在 Windows 10 上的安装推荐用 ZIP 压缩包方式而不是 MSI 安装器因为 ZIP 方式对my.ini的控制更直接。下载地址认准官方归档页5.7.44 和 8.0 系列都有。安装过程核心就三步解压、初始化、注册服务。# 解压到 D:\tool\mysql-8.0.46-winx64 后以管理员身份进入 bin 目录 mysqld --initialize-insecure --console # --initialize-insecure 表示 root 用户初始密码为空方便本地开发 # --console 把初始化日志打到控制台方便看报错 mysqld --install MySQL80 --defaults-fileD:\tool\mysql-8.0.46-winx64\my.ini # 注册为 Windows 服务服务名 MySQL80 net start MySQL80 # 启动服务如果提示服务正在启动 .后卡住多半是 my.ini 里 basedir 或 datadir 路径写错my.ini里必须显式写basedir和datadir否则 MySQL 会去默认路径找数据目录导致启动失败。初始化完成后用mysql -u root -p登录第一次直接回车然后立刻改密码。2.2 工程目录结构一个能直接导入 IDEA 的骨架农产品销售管理系统的典型模块包括用户管理、农产品分类、农产品信息、订单管理、销售统计。工程按 MVC 分层目录结构如下nongchanpin/ ├── src/ │ └── com/farm/ │ ├── dao/ # 数据库访问层 │ ├── entity/ # 实体类 │ ├── servlet/ # 控制层 │ └── util/ # DBUtil 工具类 ├── WebContent/ │ ├── WEB-INF/ │ │ ├── lib/ # 放 mysql-connector-java.jar │ │ └── web.xml # 部署描述符 │ ├── css/ │ ├── js/ │ └── *.jsp # 视图层WEB-INF/lib这个目录是血泪经验JDBC 驱动 jar 包必须放在这里不能只放在项目的 Libraries 里。因为 Tomcat 运行时只认WEB-INF/lib和 Tomcat 自己的lib目录IDEA 的 Libraries 配置只在编译期生效运行期不生效。这就是为什么很多人编译通过但一访问就报ClassNotFoundException: com.mysql.cj.jdbc.Driver。2.3 数据库连接工具类的写法与参数说明不要在每个 Servlet 里都写一遍DriverManager.getConnection抽一个DBUtilpackage com.farm.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class DBUtil { // MySQL 8.0 驱动类名带 cj5.7 用 com.mysql.jdbc.Driver private static final String DRIVER com.mysql.cj.jdbc.Driver; // useSSLfalse 关闭 SSL 警告serverTimezone 必须设否则时间字段会差 8 小时 private static final String URL jdbc:mysql://localhost:3306/farm_db ?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 你的密码; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConn() throws Exception { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement st, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (Exception e) {} try { if (st ! null) st.close(); } catch (Exception e) {} try { if (conn ! null) conn.close(); } catch (Exception e) {} } }serverTimezoneAsia/Shanghai这个参数不加插入的时间字段会比实际时间少 8 小时因为 MySQL 8.0 默认用 UTC 时区解析。characterEncodingutf8不加中文农产品名称会变成问号。这两个参数是必调项不是可选项。3. 农产品销售系统的表结构设计与 JSP 页面联动3.1 五张核心表从分类到订单的字段取舍农产品销售系统的数据模型不复杂但字段设计要考虑实际业务。比如农产品表要不要存库存要。订单表要不要存收货地址快照要因为用户可能改地址历史订单必须保留当时的地址。CREATE DATABASE farm_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE farm_db; -- 用户表 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, -- 存 MD5 值不存明文 real_name VARCHAR(50), phone VARCHAR(20), role TINYINT DEFAULT 0, -- 0 普通用户 1 管理员 create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 农产品分类表 CREATE TABLE t_category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, sort_order INT DEFAULT 0 ); -- 农产品表 CREATE TABLE t_product ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, category_id INT, price DECIMAL(10,2) NOT NULL, stock INT DEFAULT 0, unit VARCHAR(10) DEFAULT 斤, -- 农产品按斤/箱卖单位要留 image VARCHAR(200), description TEXT, status TINYINT DEFAULT 1, -- 1 上架 0 下架 FOREIGN KEY (category_id) REFERENCES t_category(id) ); -- 订单表 CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, user_id INT, total_amount DECIMAL(10,2), address VARCHAR(200), -- 地址快照 status TINYINT DEFAULT 0, -- 0 待付款 1 已付款 2 已发货 3 已完成 create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES t_user(id) ); -- 订单明细表 CREATE TABLE t_order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT, product_id INT, quantity INT, price DECIMAL(10,2), -- 下单时的单价快照 FOREIGN KEY (order_id) REFERENCES t_order(id), FOREIGN KEY (product_id) REFERENCES t_product(id) );utf8mb4而不是utf8因为农产品描述里可能有 emoji 或特殊符号。DECIMAL(10,2)而不是FLOAT金额计算不能用浮点数否则会出现 0.10.20.30000000000000004 的问题。订单明细里的price是下单时的价格快照不能关联查产品表当前价格否则产品调价后历史订单金额就错了。3.2 JSP 页面读取农产品列表从 Servlet 到 EL 表达式农产品列表页是这套系统最核心的页面链路是浏览器请求/productList→ Servlet 查数据库 → 把 List 放进 request → 转发到 JSP → JSP 用 JSTL 遍历渲染。Servlet 端package com.farm.servlet; import com.farm.dao.ProductDao; import com.farm.entity.Product; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; WebServlet(/productList) public class ProductListServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ProductDao dao new ProductDao(); // 支持按分类筛选categoryId 为空则查全部 String cid req.getParameter(categoryId); ListProduct list (cid null || cid.isEmpty()) ? dao.findAll() : dao.findByCategory(Integer.parseInt(cid)); req.setAttribute(products, list); // 转发而不是重定向request 域中的数据才能带到 JSP req.getRequestDispatcher(/product_list.jsp).forward(req, resp); } }JSP 端% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % html headtitle农产品列表/title/head body table border1 trth名称/thth单价/thth库存/thth单位/thth操作/th/tr c:forEach items${products} varp tr td${p.name}/td tdfmt:formatNumber value${p.price} pattern#,##0.00//td td${p.stock}/td td${p.unit}/td tda hrefaddCart?productId${p.id}加入购物车/a/td /tr /c:forEach /table /body /htmlc:forEach的items属性对应 Servlet 里setAttribute(products, list)的 key。fmt:formatNumber用来格式化金额保留两位小数。如果页面显示乱码检查三处JSP 的pageEncoding、contentType的 charset、以及数据库连接 URL 里的characterEncoding三处必须统一为 UTF-8。3.3 图片上传与坐标定位农产品图片怎么存怎么显示农产品系统少不了上传产品图片。JSP 页面用enctypemultipart/form-data表单提交Servlet 端用MultipartConfig注解接收WebServlet(/uploadProduct) MultipartConfig(maxFileSize 5 * 1024 * 1024) // 单文件最大 5MB public class UploadServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Part part req.getPart(image); String fileName System.currentTimeMillis() _ part.getSubmittedFileName(); // 存到 webapp 下的 upload 目录不要存到项目外的绝对路径 String savePath getServletContext().getRealPath(/upload); part.write(savePath File.separator fileName); // 数据库只存相对路径不存绝对路径 String dbPath upload/ fileName; // ... 后续把 dbPath 存入 t_product.image } }数据库只存相对路径upload/xxx.jpg不存D:\tomcat\webapps\...这种绝对路径否则换一台机器部署图片全挂。JSP 里用img src${pageContext.request.contextPath}/${p.image}显示contextPath保证项目名变化时路径仍然正确。如果要做图片坐标定位比如在农产品图片上标注产地位置前端用 CSS 的position: relative容器加position: absolute的标记点坐标存百分比而不是像素值这样图片缩放时标记点不会跑偏。4. Tomcat 部署与 JSP 编译期常见问题排查4.1 部署方式war 包、exploded、还是直接跑三种部署方式各有场景。开发阶段用 IDEA 的 exploded 模式改 JSP 不用重启测试阶段打 war 包丢进webapps目录生产环境用 war 包加 Tomcat 的autoDeploy。打 war 包用 Maven 的mvn package或者 IDEA 的 Artifacts 配置。Tomcat 的server.xml里Connector标签的port默认 8080如果被占用改成 8081。URIEncoding在 Tomcat 8 以后默认就是 UTF-8不用手动加。context.xml里可以配数据源但小系统直接用DBUtil就够了配 JNDI 数据源反而增加复杂度。如果遇到「tomcat 后台页面上传 war 被限制 IP」那是manager应用的context.xml里RemoteAddrValve在起作用默认只允许本机访问。本地开发不用管远程部署时改allow属性。4.2 JSP 第一次访问慢编译机制与预编译配置JSP 不是解释执行的Tomcat 第一次收到 JSP 请求时会把它编译成 Java 文件再编译成 class所以第一次访问特别慢。这是正常现象不是性能问题。如果想让生产环境第一次访问就快可以在web.xml里配jsp-config做预编译或者在部署后手动访问一遍所有 JSP 页面触发编译。work目录是 Tomcat 存放编译后 JSP 的地方清理缓存时删work/Catalina/localhost/项目名即可。有时候改了 JSP 但页面没变化就是work目录里的旧 class 没被清掉重启 Tomcat 或手动删work目录能解决。4.3 中文乱码的三层排查法中文乱码是这套系统最高频的问题按三层排查第一层数据库层。建库时用utf8mb4连接 URL 加characterEncodingutf8。用SHOW VARIABLES LIKE character%确认character_set_server是utf8mb4。第二层请求层。POST 请求在 Servlet 第一行加req.setCharacterEncoding(UTF-8)。GET 请求在 Tomcat 8 以后默认 UTF-8不用额外处理。如果用的是 Tomcat 7需要在server.xml的 Connector 里加URIEncodingUTF-8。第三层响应层。JSP 页面头部contentTypetext/html;charsetUTF-8Servlet 输出前resp.setContentType(text/html;charsetUTF-8)。三层都对齐了乱码基本不会出现。5. 避坑与常见问题那些让我返工三次的细节5.1 驱动类名不匹配导致连接失败现象启动报ClassNotFoundException: com.mysql.jdbc.Driver。 原因MySQL 8.0 的驱动 jar 里类名是com.mysql.cj.jdbc.Driver旧教程里写的com.mysql.jdbc.Driver在 8.0 驱动里虽然保留了兼容类但会打警告某些版本直接移除。 解决确认mysql-connector-java版本8.0 以上用com.mysql.cj.jdbc.Driver5.7 用com.mysql.jdbc.Driver。同时确认 jar 包在WEB-INF/lib下。5.2 时间字段差 8 小时现象插入数据库的时间比实际时间少 8 小时。 原因MySQL 8.0 的 JDBC 驱动默认用 UTC 时区解析DATETIME而服务器在东八区。 解决连接 URL 加serverTimezoneAsia/Shanghai。或者把 MySQL 全局时区设为8:00但改 URL 更简单。5.3 JSP 页面 EL 表达式不解析现象页面上直接显示${p.name}而不是实际值。 原因web.xml 的web-app标签版本太低2.3 或以下EL 表达式默认不启用。 解决把 web.xml 的 schema 声明改成 Servlet 3.1 对应的版本或者在该 JSP 的 page 指令里加isELIgnoredfalse。5.4 订单金额用 double 计算出现精度丢失现象购物车总价算出99.99999999这种值。 原因Java 的double和float是浮点数不能精确表示十进制小数。 解决金额计算用BigDecimal数据库字段用DECIMAL。BigDecimal的add、multiply方法做运算最后setScale(2, RoundingMode.HALF_UP)保留两位。5.5 Tomcat 启动报端口占用现象java.net.BindException: Address already in use: JVM_Bind。 原因8080 端口被其他程序占用常见的是另一个 Tomcat 实例或某些开发工具。 解决netstat -ano | findstr 8080找到占用进程的 PIDtaskkill /PID xxx /F杀掉或者改 Tomcat 的server.xml里 Connector 的 port 为 8081。6. 销售统计的 SQL 写法与一个可复用的分页技巧农产品销售系统做到最后最有价值的模块是销售统计。管理员需要看「每个分类的销售额」「每月订单量」「库存预警」。这些用 SQL 聚合直接出结果比在 Java 里循环计算高效得多。按分类统计销售额SELECT c.name AS category_name, COUNT(o.id) AS order_count, SUM(oi.quantity * oi.price) AS total_sales FROM t_order_item oi JOIN t_product p ON oi.product_id p.id JOIN t_category c ON p.category_id c.id JOIN t_order o ON oi.order_id o.id WHERE o.status IN (1, 2, 3) -- 排除待付款订单 GROUP BY c.id, c.name ORDER BY total_sales DESC;WHERE在GROUP BY之前过滤把待付款订单排除掉统计的才是真实成交额。SUM(oi.quantity * oi.price)用的是订单明细里的价格快照不是产品表当前价格保证历史数据准确。库存预警查询SELECT name, stock, unit FROM t_product WHERE stock 10 AND status 1 ORDER BY stock ASC;分页是列表页必备的。MySQL 用LIMIT实现SELECT * FROM t_product WHERE status 1 ORDER BY id DESC LIMIT #{offset}, #{pageSize};offset (pageNum - 1) * pageSize。pageSize 一般设 10 或 20pageNum 从 1 开始。JSP 页面渲染分页条时总页数用SELECT COUNT(*)单独查一次不要用LIMIT查出来的结果数去算。我自己的习惯是所有列表查询都带分页哪怕现在数据只有几条。因为数据量涨起来是很快的等到几千条再改分页所有调用点都要动。一开始就写好后面省事。另外统计类 SQL 不要在 JSP 里直接写放到 DAO 层JSP 只负责展示这样 SQL 出问题的时候好定位。希望帮到你。本文还有配套的精品资源点击获取
返回列表