ARTICLE DETAIL

资讯详情

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

农产品销售管理系统实战:JSP+Tomcat+MySQL从环境搭建到部署排错

农产品销售管理系统实战:JSP+Tomcat+MySQL从环境搭建到部署排错 简介这份资源是面向高校计算机相关专业学生与Java Web初学者的一套农产品销售管理系统完整项目基于Java、JSP与Tomcat技术栈配合MySQL数据库采用B/S架构实现适合用作课程设计、毕业设计或相关项目实战参考。压缩包整体约95.93MB内含项目全套源码与完整文档源码均经过测试校正可保证百分百成功运行方便读者直接部署与二次开发。系统围绕农产品销售业务展开涵盖商品管理、订单处理、用户权限等典型模块能帮助读者理解JSP页面与后端逻辑的协作方式、数据库表结构设计以及Tomcat部署流程。目前已有1440人浏览学习说明该方案在同类项目中具有一定参考价值。对于需要快速搭建Web项目、梳理B/S架构开发思路或准备答辩材料的读者这份源码加文档的组合能提供从代码实现到设计说明的完整支撑便于对照学习与查漏补缺。1. 农产品销售管理系统从JSPTomcat到MySQL的完整落地路径很多做Java Web课设或毕设的开发者第一次拿到“农产品销售管理系统”这个题目时脑子里想的都是功能列表用户注册、商品展示、购物车、订单管理。但真正动手才发现卡住你的从来不是业务逻辑而是Tomcat启动报错、JSP页面中文乱码、MySQL连接池配置这些“玄学”问题。这套基于JavaJSPTomcatMySQL的技术栈虽然看起来是十几年前的老组合但在教学、课设、小型企业内部工具场景里依然是最稳的选择——不需要Spring Boot的自动配置黑盒每一层都看得见摸得着出了问题能顺着调用链一路排查到底。这篇文章面向的是需要从零搭建一套可运行农产品销售系统的开发者不管你是刚学完Java基础的新手还是想快速复现一套BS架构项目的熟手下面会从环境搭建、数据库设计、核心功能实现到部署排错把每个环节的参数和坑都讲清楚。2. 环境搭建JDK、Tomcat、MySQL三件套的版本匹配与配置2.1 为什么这套技术栈对版本匹配如此敏感JSPServletTomcat的组合有一个特点Tomcat的版本直接决定了你能用哪个版本的Servlet规范和JSP规范而JDK版本又决定了Tomcat能不能跑起来。常见做法是锁定JDK 8 Tomcat 8.5或9.0 MySQL 5.7或8.0这个组合原因很简单——JDK 8是Tomcat 8.5/9.0官方支持最充分的版本MySQL 5.7和8.0的JDBC驱动在JDK 8下兼容性最好。如果你用JDK 17去跑Tomcat 8.5大概率会遇到UnsupportedClassVersionError因为Tomcat 8.5的部分类是用JDK 7编译的反过来JDK 8跑Tomcat 10也会出问题因为Tomcat 10把javax.servlet包名改成了jakarta.servlet你项目里所有import javax.servlet.*的代码全部要改。我一般会建议在项目根目录放一个环境版本.txt把三个版本号写死避免换机器后重新踩一遍版本坑。2.2 JDK安装与环境变量配置JDK 8的安装本身不复杂但环境变量配错会导致java命令能用而javac不能用或者Tomcat启动时找不到JAVA_HOME。下面是在Windows 10上的标准配置步骤# 假设JDK安装在 D:\dev\jdk1.8.0_301 # 新建系统变量 JAVA_HOME D:\dev\jdk1.8.0_301 CLASSPATH .;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar # 编辑Path变量追加 %JAVA_HOME%\bin %JAVA_HOME%\jre\bin配置完成后打开新的cmd窗口验证java -version # 应输出 java version 1.8.0_301 javac -version # 应输出 javac 1.8.0_301 echo %JAVA_HOME% # 应输出 D:\dev\jdk1.8.0_301这里有个细节CLASSPATH最前面的.不能省它代表当前目录否则你编译一个.java文件后运行.class会提示找不到主类。另外修改环境变量后必须开新窗口旧窗口读的还是旧值。2.3 Tomcat下载、安装与IDEA集成Tomcat不需要安装解压即用。下载地址去Apache官网找apache-tomcat-9.0.x-windows-x64.zip解压到D:\dev\apache-tomcat-9.0.xx。目录结构里需要关注的是conf/server.xml端口配置、webapps/部署目录、logs/排错看这里。在IDEA里配置Tomcat的步骤Run→Edit Configurations→→Tomcat Server→LocalApplication server处点Configure...选择Tomcat解压目录Deployment标签页 →→Artifact→ 选择xxx:war explodedApplication context设为/farm这样访问路径就是http://localhost:8080/farm/注意如果IDEA 2026版本里找不到Tomcat Server选项需要先在Settings→Plugins里确认Smart Tomcat或Tomcat and TomEE Integration插件已启用。新版IDEA把Tomcat集成拆成了独立插件。2.4 MySQL 8.0安装与农产品销售系统数据库初始化MySQL 8.0在Windows上的安装推荐用MSI安装包自定义安装时记得勾选MySQL Server和MySQL Workbench。安装过程中设置的root密码要记牢后面JDBC连接串里要用。安装完成后验证服务net start mysql # 如果提示服务名无效说明MySQL服务没注册需要手动初始化 # 进入MySQL的bin目录执行 mysqld --initialize-insecure --usermysql mysqld --install MySQL net start MySQL数据库初始化脚本的核心是建库、建表、插入测试数据。农产品销售系统至少需要这几张表用户表、农产品分类表、农产品信息表、订单表、订单明细表。下面给出建库和核心表的DDLCREATE DATABASE farm_sale DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE farm_sale; -- 用户表区分普通用户和管理员 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, phone VARCHAR(20), role TINYINT DEFAULT 0 COMMENT 0-普通用户 1-管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; -- 农产品表 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, image_url VARCHAR(255), description TEXT, status TINYINT DEFAULT 1 COMMENT 1-上架 0-下架 ) ENGINEInnoDB; -- 订单表 CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, total_price DECIMAL(10,2), status TINYINT DEFAULT 0 COMMENT 0-待付款 1-已付款 2-已发货 3-已完成, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB;字符集用utf8mb4而不是utf8因为农产品名称里可能出现生僻字或emojiutf8在MySQL里实际是utf8mb3只支持3字节字符。JDBC连接串里也要对应写characterEncodingutf8驱动会自动协商。3. 农产品销售系统核心功能从JSP页面到Servlet的请求链路3.1 项目目录结构与MVC分层一个能跑通的JSP项目目录结构必须清晰否则后面加功能会乱成一锅粥。我一般用这样的结构farm-sale/ ├── src/ │ └── com/farm/ │ ├── dao/ # 数据库操作层 │ ├── entity/ # 实体类 │ ├── servlet/ # 控制层 │ └── util/ # 工具类DBUtil等 ├── web/ │ ├── css/ │ ├── js/ │ ├── images/ │ ├── WEB-INF/ │ │ ├── web.xml │ │ └── lib/ # 放mysql-connector-java.jar │ ├── index.jsp │ ├── product_list.jsp │ └── cart.jspWEB-INF目录下的文件不能被浏览器直接访问所以web.xml和第三方jar放这里最安全。JSP页面放外面Servlet通过request.getRequestDispatcher()转发。3.2 数据库连接工具类避免每次请求都新建连接新手最容易犯的错是在每个Servlet里写DriverManager.getConnection()请求一多数据库就扛不住。正确做法是写一个DBUtil用静态代码块加载驱动并提供获取和关闭连接的方法package com.farm.util; import java.sql.*; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/farm_sale?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 你的密码; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL驱动加载失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (ps ! null) ps.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }连接串里的serverTimezoneAsia/Shanghai必须加否则MySQL 8.0会报The server time zone value ?D1ú±ê×?ê±?? is unrecognized。useSSLfalse是关掉SSL警告本地开发不需要加密连接。驱动类名从MySQL 8.0开始是com.mysql.cj.jdbc.Driver旧版的com.mysql.jdbc.Driver已经废弃。3.3 农产品列表页JSP如何从Servlet拿数据并渲染以农产品列表为例请求链路是浏览器访问/product/list→ProductListServlet处理 → 调用ProductDao查数据库 → 把List存到request → 转发到product_list.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(/product/list) public class ProductListServlet extends HttpServlet { private ProductDao productDao new ProductDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 设置编码防止中文参数乱码 req.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); String keyword req.getParameter(keyword); ListProduct products; if (keyword ! null !keyword.trim().isEmpty()) { products productDao.searchByName(keyword.trim()); } else { products productDao.findAllOnSale(); } req.setAttribute(products, products); req.getRequestDispatcher(/product_list.jsp).forward(req, resp); } }JSP页面用JSTL的c:forEach渲染% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html headtitle农产品列表/title/head body h2农产品列表/h2 form action${pageContext.request.contextPath}/product/list methodget input typetext namekeyword placeholder搜索农产品 button typesubmit搜索/button /form table border1 trth名称/thth价格/thth库存/thth操作/th/tr c:forEach items${products} varp tr td${p.name}/td td${p.price}/td td${p.stock}/td tda href${pageContext.request.contextPath}/cart/add?id${p.id}加入购物车/a/td /tr /c:forEach /table /body /html${pageContext.request.contextPath}是获取项目上下文路径这样部署到不同路径下链接都不会失效。JSTL的jar包jstl.jar和standard.jar要放到WEB-INF/lib下否则c:forEach标签不生效。3.4 购物车与订单Session存储与事务处理购物车用Session存最方便结构是MapInteger, CartItemkey是商品IDvalue是商品信息和数量。加入购物车的ServletWebServlet(/cart/add) public class CartAddServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int productId Integer.parseInt(req.getParameter(id)); HttpSession session req.getSession(); MapInteger, CartItem cart (MapInteger, CartItem) session.getAttribute(cart); if (cart null) { cart new HashMap(); session.setAttribute(cart, cart); } if (cart.containsKey(productId)) { cart.get(productId).setQuantity(cart.get(productId).getQuantity() 1); } else { Product p new ProductDao().findById(productId); cart.put(productId, new CartItem(p, 1)); } resp.sendRedirect(req.getContextPath() /cart.jsp); } }下单时要把购物车数据写入t_order和t_order_item两张表必须用事务保证一致性public boolean createOrder(int userId, MapInteger, CartItem cart) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 插入订单主表 String orderSql INSERT INTO t_order(user_id, total_price, status) VALUES(?,?,0); PreparedStatement ps conn.prepareStatement(orderSql, Statement.RETURN_GENERATED_KEYS); ps.setInt(1, userId); ps.setBigDecimal(2, calcTotal(cart)); ps.executeUpdate(); ResultSet rs ps.getGeneratedKeys(); rs.next(); int orderId rs.getInt(1); // 2. 插入订单明细 String itemSql INSERT INTO t_order_item(order_id, product_id, quantity, price) VALUES(?,?,?,?); ps conn.prepareStatement(itemSql); for (CartItem item : cart.values()) { ps.setInt(1, orderId); ps.setInt(2, item.getProduct().getId()); ps.setInt(3, item.getQuantity()); ps.setBigDecimal(4, item.getProduct().getPrice()); ps.addBatch(); } ps.executeBatch(); // 3. 扣减库存 String stockSql UPDATE t_product SET stock stock - ? WHERE id ? AND stock ?; ps conn.prepareStatement(stockSql); for (CartItem item : cart.values()) { ps.setInt(1, item.getQuantity()); ps.setInt(2, item.getProduct().getId()); ps.setInt(3, item.getQuantity()); ps.addBatch(); } int[] results ps.executeBatch(); for (int r : results) { if (r 0) throw new SQLException(库存不足); } conn.commit(); return true; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { try { if (conn ! null) conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }扣库存的SQL里AND stock ?是关键它保证了并发下不会超卖——如果库存不够executeBatch返回的影响行数为0直接抛异常回滚。这个写法比先查再扣更安全因为查和扣之间可能有其他请求插进来。4. 避坑与排查Tomcat、JSP、MySQL的高频翻车现场4.1 Tomcat启动报错Port 8080 already in use现象IDEA里启动Tomcat控制台红字提示Address already in use: JVM_Bind。原因8080端口被其他进程占用了常见的是之前没关干净的Tomcat进程或者装了Oracle数据库Oracle的XDB服务占8080。解决先找到占用端口的进程netstat -ano | findstr :8080 # 输出最后一列是PID比如 12345 taskkill /F /PID 12345如果不想杀进程就改Tomcat端口。打开conf/server.xml找到Connector port8080改成8081或别的空闲端口。改完在IDEA里重新部署访问路径也要跟着改。4.2 JSP页面中文乱码的三种场景现象页面显示??????或者测试。原因分三种一是JSP文件本身的编码不是UTF-8二是Servlet没有设置response.setContentType(text/html;charsetUTF-8)三是GET请求的参数编码没处理。解决JSP文件头部加% page contentTypetext/html;charsetUTF-8 languagejava %Servlet里doGet和doPost第一行都加req.setCharacterEncoding(UTF-8)和resp.setContentType(text/html;charsetUTF-8)如果是Tomcat 8.5以下GET请求的中文参数还需要在server.xml的Connector里加URIEncodingUTF-8。Tomcat 8.5及以上默认就是UTF-8不用改。4.3 MySQL 8.0驱动ClassNotFoundException现象启动项目后访问数据库相关页面报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver。原因mysql-connector-java的jar包没放到WEB-INF/lib目录下或者放的是旧版驱动但代码里写的新类名。解决确认WEB-INF/lib下有mysql-connector-java-8.0.xx.jar。如果用的是Maven在pom.xml里加依赖dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency注意scope不要写provided否则打包时不会带进war。另外IDEA的Project Structure→Artifacts里要确认WEB-INF/lib下确实包含了这个jar有时候Maven依赖更新了但Artifact没重新构建。4.4 JSP中图片路径404的排查思路现象农产品列表页的图片全部显示裂图F12看Network里图片请求是404。原因图片放在web/images/下但JSP里写的路径是相对路径images/xxx.jpg而当前页面URL可能是/farm/product/list浏览器会去/farm/product/images/xxx.jpg找实际路径不对。解决统一用${pageContext.request.contextPath}/images/xxx.jpg拼绝对路径。如果图片是用户上传的不要传到项目目录里因为重新部署war包会覆盖掉。正确做法是配一个外部目录在Tomcat的conf/server.xml里加Context docBaseD:/farm_upload path/upload/JSP里用/upload/xxx.jpg访问。4.5 事务未回滚导致的数据不一致现象下单时订单主表插入了但明细表没插入或者库存扣了但订单没生成。原因Connection没有关掉自动提交或者异常被catch了但没有rollback。解决严格按照3.4节的模板写conn.setAutoCommit(false)之后所有数据库操作放在try块里catch块里第一件事就是conn.rollback()。另外注意PreparedStatement和ResultSet的关闭顺序先关ResultSet再关PreparedStatement最后关Connection否则可能报Operation not allowed after ResultSet closed。5. 进阶技巧用Filter统一编码与登录拦截少写重复代码项目写到后面你会发现每个Servlet里都写一遍req.setCharacterEncoding(UTF-8)和登录检查代码重复且容易漏。用Filter可以一次性解决。编码FilterWebFilter(/*) public class EncodingFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); chain.doFilter(req, resp); } }登录拦截FilterWebFilter(/admin/*) public class AdminAuthFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(); Object user session.getAttribute(loginUser); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }WebFilter(/*)表示拦截所有请求WebFilter(/admin/*)只拦截后台路径。多个Filter的执行顺序在web.xml里按filter-mapping的声明顺序用注解的话顺序不确定所以如果编码Filter和登录Filter有依赖关系建议在web.xml里显式配置。验证Filter是否生效的方法在doFilter里加一行System.out.println(Filter拦截: request.getRequestURI())启动后访问页面看控制台输出。如果没输出检查web.xml的metadata-complete属性是不是true这个属性为true时注解不生效。最后说一个我自己的习惯每次改完数据库连接串或者Tomcat配置先重启Tomcat再测不要热部署。JSP热部署有时候会缓存旧的编译结果导致你改了代码但页面没变白白浪费半小时排查一个根本不存在的bug。希望帮到你。本文还有配套的精品资源点击获取
返回列表