ARTICLE DETAIL

资讯详情

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

JSP农产品销售系统毕设实战:环境搭建、数据库设计与事务处理

JSP农产品销售系统毕设实战:环境搭建、数据库设计与事务处理 简介这份毕业设计资源是一套基于Web的农产品销售管理系统完整项目面向计算机相关专业学生及Java Web初学者可用于课程设计、毕业设计参考或Web开发实战练习。系统采用MyEclipse作为开发工具MySQL作为后台数据库结合JSP与Servlet技术实现前后台数据交互整体基于Java技术栈构建具备跨平台特性。功能上划分为前台销售与后台管理两大模块前台涵盖会员注册、购物车、顾客留言、商品浏览与订单管理后台则支持商品增删改、会员审核、订单处理、留言回复及库存与数据分析覆盖了用户交互、数据存储、订单处理等关键环节。资源以zip压缩包形式提供包体约21.73MB文件类型以Java源码、JSP页面及数据库脚本等为主便于直接导入开发环境运行调试。目前已有335人学习关注适合希望理解Java Web项目分层结构、掌握JSPMySQL整合开发流程的读者参考借鉴。1. 从一份 JSP 毕设拆起这套农产品销售系统到底能跑通什么每年毕业季Java Web 方向的毕设选题里「基于 WEB 的农产品销售管理系统」出现频率高得离谱。原因不复杂它同时踩中了电商业务逻辑和 Java 技术栈两个刚需既能展示 JSP Servlet 的前后端交互又能把 MySQL 的增删改查、事务、外键约束全用上。但真正拿到一份能跑的源码包之后多数人第一反应是懵的——MyEclipse 项目结构、JSP 页面里嵌 Java 代码、数据库连接写死在配置文件里跟现在主流的 Spring Boot 完全两个世界。这份资源就是一套典型的传统 Java Web 项目MyEclipse 作为 IDEMySQL 做数据存储JSP 负责页面渲染Servlet 处理请求转发和业务逻辑。前台覆盖会员注册登录、商品浏览、购物车、留言、订单管理后台覆盖商品维护、会员审核、订单处理、留言回复、库存和基础数据统计。它适合两类人一是毕设需要快速搭出可演示系统的学生二是想回头补一补 JSP/Servlet 底层交互的开发者。下面按「环境怎么搭 → 数据库怎么建 → 核心模块怎么跑 → 坑在哪 → 怎么改」的顺序拆一遍。2. 环境搭建与项目导入MyEclipse Tomcat MySQL 的版本对齐2.1 为什么这套老技术栈还值得跑一遍先说选型逻辑。JSP Servlet 这套东西在今天的企业级 web 开发里确实不主流了但它的价值在于「透明」——没有 Spring 的自动装配和注解扫描请求从浏览器到 Servlet 再到 JSP 渲染每一步都是显式的。对于要理解 HTTP 请求生命周期、Session 管理、JDBC 连接池这些基础概念的人来说跑通一个 JSP 项目比看十篇 Spring 原理分析都管用。这套系统的技术依赖很明确JDK 版本建议 1.8MyEclipse 对高版本 JDK 兼容性差Tomcat 用 8.5 或 9.0Tomcat 10 之后 Servlet API 包名从 javax 变成 jakarta老项目直接跑不起来MySQL 用 5.78.0 的驱动类和认证方式有变化需要额外改配置。这三个版本必须对齐否则会出现各种玄学报错。2.2 导入项目的具体步骤拿到源码包后不要急着往 MyEclipse 里拖。先确认目录结构典型的老式 Java Web 项目长这样AgriculturalSalesSystem/ ├── src/ # Java 源码目录 │ └── com/agri/ │ ├── dao/ # 数据库访问层 │ ├── servlet/ # 请求处理层 │ ├── entity/ # 实体类 │ └── util/ # 工具类DBUtil 等 ├── WebRoot/ # Web 资源根目录 │ ├── WEB-INF/ │ │ ├── web.xml # 部署描述符 │ │ └── lib/ # 第三方 jar 包 │ ├── css/ │ ├── js/ │ └── *.jsp # 页面文件 └── .project / .classpath # MyEclipse 项目配置导入流程按下面走# 第一步确认 JDK 版本 java -version # 输出应为 1.8.x如果是 11 或 17先切换 # 第二步确认 MySQL 服务状态 mysql -u root -p -e SELECT VERSION(); # 输出应为 5.7.x # 第三步把项目放到 MyEclipse 工作空间 # 不要直接 Open Project用 Import → Existing Projects into Workspace # 选中项目根目录勾选 Copy projects into workspace导入之后MyEclipse 会自动编译。如果报错大概率是 jar 包缺失。检查WEB-INF/lib下是否有这几个核心包mysql-connector-java-5.1.x.jar数据库驱动、jstl.jar和standard.jarJSP 标签库、servlet-api.jar如果 Tomcat 已配置为 Runtime这个可以不加。2.3 Tomcat 配置与项目部署在 MyEclipse 里配置 Tomcat 的路径是 Window → Preferences → MyEclipse → Servers → Tomcat。选好安装目录后重点改一个参数JDK 选项卡里指定用 1.8 的 JRE不要用 MyEclipse 自带的。部署项目时右键项目 → Run As → MyEclipse Server Application选择 Tomcat。控制台出现Server startup in xxx ms就说明启动成功。浏览器访问http://localhost:8080/AgriculturalSalesSystem/如果看到首页但样式全丢说明 CSS 路径用了绝对路径需要改成相对路径或者加上项目名。提示Tomcat 的默认端口 8080 如果被占用在 Servers 视图里双击 Tomcat 实例改 HTTP/1.1 的端口号即可。改完记得在项目里所有硬编码的 URL 同步修改。3. 数据库设计与初始化从建库到数据导入的完整链路3.1 核心表结构与字段设计这套系统的数据库设计是典型的电商简化模型核心表大概 7 到 8 张。先看最关键的三张表名用途关键字段注意事项member会员信息id, username, password, phone, address, reg_timepassword 建议存 MD5原文可能是明文product农产品信息id, name, price, origin, stock, category_id, imageprice 用 decimal(10,2)别用 floatorders订单主表id, member_id, total_price, status, create_timestatus 用 int 枚举0 待付款 1 已付款 2 已发货 3 已完成order_detail订单明细id, order_id, product_id, quantity, unit_price外键关联 orders 和 productcart购物车id, member_id, product_id, quantity同一用户同一商品应做唯一约束message留言id, member_id, content, reply, create_timereply 字段为空表示未回复admin管理员id, username, password与 member 表分离权限不同建库语句大致如下CREATE DATABASE agri_sales DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE agri_sales; CREATE TABLE member ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, phone VARCHAR(20), address VARCHAR(200), reg_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; CREATE TABLE product ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL DEFAULT 0.00, origin VARCHAR(100), stock INT NOT NULL DEFAULT 0, category_id INT, image VARCHAR(200), description TEXT ) ENGINEInnoDB; CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, member_id INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (member_id) REFERENCES member(id) ) ENGINEInnoDB;字符集必须用 utf8mb4因为农产品名称和产地可能包含生僻字或者特殊符号。引擎用 InnoDB因为订单和库存操作需要事务支持。3.2 数据导入与连接配置建完表之后把源码包里附带的.sql数据文件导入# 方式一命令行导入 mysql -u root -p agri_sales init_data.sql # 方式二在 MySQL 客户端里 source mysql -u root -p USE agri_sales; SOURCE /path/to/init_data.sql;导入之后检查数据量SELECT COUNT(*) FROM product;应该有几十条农产品测试数据。接下来改数据库连接配置。老项目一般把连接信息写在src/com/agri/util/DBUtil.java或者db.properties里// DBUtil.java 典型写法 public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/agri_sales?useUnicodetruecharacterEncodingutf8mb4useSSLfalse; private static final String USER root; private static final String PASSWORD your_password; static { try { Class.forName(com.mysql.jdbc.Driver); // MySQL 5.x 驱动类 } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这里有几个参数必须注意useUnicodetruecharacterEncodingutf8mb4解决中文乱码useSSLfalse避免连接时的 SSL 警告。驱动类名如果是 MySQL 8.0 的 jar 包要改成com.mysql.cj.jdbc.DriverURL 里还要加serverTimezoneAsia/Shanghai。3.3 验证数据库连通性在写业务代码之前先单独跑一个 JDBC 测试类确认能连上public class TestConnection { public static void main(String[] args) { try (Connection conn DBUtil.getConnection()) { System.out.println(数据库连接成功 conn.getMetaData().getURL()); try (Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(SELECT COUNT(*) FROM product)) { if (rs.next()) { System.out.println(商品表记录数 rs.getInt(1)); } } } catch (SQLException e) { System.err.println(连接失败 e.getMessage()); e.printStackTrace(); } } }如果报Access denied for user检查密码如果报Unknown database检查库名如果报Communications link failure检查 MySQL 服务是否启动、端口是否被防火墙拦截。这一步过了后面 JSP 页面里的数据库操作才有意义。4. 前台与后台核心模块Servlet 请求链路与 JSP 渲染逻辑4.1 会员注册登录与 Session 管理前台会员模块的核心是注册和登录。注册的 Servlet 接收表单 POST 请求做参数校验后写入member表// RegisterServlet.java 核心逻辑 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); String phone request.getParameter(phone); String address request.getParameter(address); // 参数校验 if (username null || username.trim().isEmpty() || password null || password.length() 6) { request.setAttribute(msg, 用户名不能为空密码至少6位); request.getRequestDispatcher(/register.jsp).forward(request, response); return; } Member member new Member(); member.setUsername(username); member.setPassword(MD5Util.encode(password)); // 密码加密 member.setPhone(phone); member.setAddress(address); MemberDao dao new MemberDao(); if (dao.findByUsername(username) ! null) { request.setAttribute(msg, 用户名已存在); request.getRequestDispatcher(/register.jsp).forward(request, response); return; } dao.insert(member); response.sendRedirect(request.getContextPath() /login.jsp); }登录成功后把用户对象存入 Sessionrequest.getSession().setAttribute(member, member);。后续所有需要登录才能访问的页面在 JSP 顶部加判断% Member member (Member) session.getAttribute(member); if (member null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } %这种写法虽然原始但逻辑清晰。注意request.getSession()默认会创建新 Session如果只是读取已有 Session用request.getSession(false)避免无谓的 Session 创建。4.2 购物车与订单生成的事务处理购物车模块的难点不在增删改查而在下单时的事务一致性。用户点击「提交订单」后需要同时完成写orders表、写order_detail表、扣减product表的库存。这三步必须在一个事务里// OrderServlet.java 下单逻辑 public void createOrder(HttpServletRequest request, HttpServletResponse response) throws SQLException, IOException { Member member (Member) request.getSession().getAttribute(member); ListCartItem cartItems cartDao.findByMemberId(member.getId()); Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 计算总价 BigDecimal totalPrice BigDecimal.ZERO; for (CartItem item : cartItems) { totalPrice totalPrice.add(item.getPrice().multiply(new BigDecimal(item.getQuantity()))); } // 2. 插入订单主表 String orderSql INSERT INTO orders(member_id, total_price, status) VALUES(?, ?, 0); PreparedStatement ps conn.prepareStatement(orderSql, Statement.RETURN_GENERATED_KEYS); ps.setInt(1, member.getId()); ps.setBigDecimal(2, totalPrice); ps.executeUpdate(); ResultSet rs ps.getGeneratedKeys(); int orderId 0; if (rs.next()) orderId rs.getInt(1); // 3. 插入订单明细并扣库存 for (CartItem item : cartItems) { String detailSql INSERT INTO order_detail(order_id, product_id, quantity, unit_price) VALUES(?,?,?,?); PreparedStatement ps2 conn.prepareStatement(detailSql); ps2.setInt(1, orderId); ps2.setInt(2, item.getProductId()); ps2.setInt(3, item.getQuantity()); ps2.setBigDecimal(4, item.getPrice()); ps2.executeUpdate(); // 扣库存同时检查库存是否充足 String stockSql UPDATE product SET stock stock - ? WHERE id ? AND stock ?; PreparedStatement ps3 conn.prepareStatement(stockSql); ps3.setInt(1, item.getQuantity()); ps3.setInt(2, item.getProductId()); ps3.setInt(3, item.getQuantity()); int affected ps3.executeUpdate(); if (affected 0) { throw new SQLException(库存不足商品ID item.getProductId()); } } // 4. 清空购物车 cartDao.deleteByMemberId(conn, member.getId()); conn.commit(); // 提交事务 response.sendRedirect(request.getContextPath() /order_list.jsp); } catch (SQLException e) { if (conn ! null) conn.rollback(); // 回滚 e.printStackTrace(); response.sendRedirect(request.getContextPath() /cart.jsp?errorstock); } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } }这段代码的关键点setAutoCommit(false)开启事务commit()提交rollback()回滚。扣库存的 SQL 用了stock ?条件避免超卖。如果任何一步失败整个事务回滚不会出现「订单生成了但库存没扣」或者「库存扣了但订单没生成」的情况。4.3 后台商品管理与留言回复后台管理模块的 Servlet 路径一般以/admin/开头在web.xml里配置过滤器做权限校验filter filter-nameAdminAuthFilter/filter-name filter-classcom.agri.filter.AdminAuthFilter/filter-class /filter filter-mapping filter-nameAdminAuthFilter/filter-name url-pattern/admin/*/url-pattern /filter-mapping过滤器里判断 Session 中是否有admin对象没有就跳转到后台登录页。商品管理的增删改查是标准的 Servlet JSP 模式留言回复则是更新message表的reply字段。后台的数据统计一般用简单的SELECT COUNT(*)和SUM()实现展示今日订单数、总销售额、库存预警等。5. 避坑与排查这套 JSP 毕设最容易翻车的五个地方5.1 中文乱码从请求到响应全链路排查现象注册时输入中文用户名存到数据库变成???或者页面显示乱码。原因JSP 页面编码、Servlet 请求编码、数据库连接编码、表字符集四者不一致。常见情况是 JSP 页面用了pageEncodingGBK但数据库是 utf8mb4。解决统一四处编码。JSP 顶部写% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Servlet 里第一行写request.setCharacterEncoding(UTF-8)数据库连接 URL 加characterEncodingutf8mb4建表时指定DEFAULT CHARSETutf8mb4。如果是 GET 请求乱码还需要在 Tomcat 的server.xml里给 Connector 加URIEncodingUTF-8。5.2 ClassNotFoundException驱动包放了但加载不到现象启动 Tomcat 后访问页面报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver。原因MySQL 驱动 jar 包没有放到WEB-INF/lib目录下或者放到了项目的src目录但没加入 Build Path。MyEclipse 有时候不会自动把src下的 jar 包部署到 Tomcat。解决确认mysql-connector-java-5.1.x.jar在WebRoot/WEB-INF/lib下。如果不在复制过去然后在 MyEclipse 里右键项目 → Build Path → Configure Build Path → Libraries确认 jar 包在 Web App Libraries 里。改完清理 Tomcat 缓存重新部署。5.3 数据库连接池耗尽Connection 没关闭现象系统运行一段时间后所有数据库操作都超时报Too many connections。原因DAO 层代码里Connection、Statement、ResultSet没有在finally块里关闭每次请求都泄漏一个连接累积到 MySQL 的最大连接数就崩了。解决所有 JDBC 操作必须用 try-with-resources 或者 finally 关闭。检查每个 DAO 方法确保rs.close()、ps.close()、conn.close()都有执行路径。如果项目用了连接池比如 DBCP 或 C3P0检查配置文件里的maxActive和maxWait参数。5.4 订单状态与库存不一致事务边界没覆盖全现象用户下单后订单列表里能看到订单但商品库存没变或者库存扣了订单却没生成。原因下单逻辑里插入订单和扣库存用了不同的 Connection或者没有开启事务。JSP 项目里常见的是在 Servlet 里调用了多个 DAO 方法每个方法各自获取连接事务无法统一控制。解决把下单相关的所有数据库操作放在同一个 Connection 里手动setAutoCommit(false)和commit()。DAO 方法接收外部传入的 Connection 参数而不是自己创建。上面 4.2 节的代码就是正确写法。5.5 Tomcat 启动报错web.xml 版本与 Servlet 版本不匹配现象Tomcat 启动时控制台报The servlet mapping is invalid或者web.xml 解析错误。原因web.xml的web-app标签版本号与 Tomcat 的 Servlet 版本不匹配。比如 Tomcat 9 支持 Servlet 4.0web.xml应该用version4.0但老项目可能写的是 2.5 或 3.0。解决查看 Tomcat 版本对应的 Servlet 规范版本修改web.xml头部的xsi:schemaLocation和version属性。如果不确定直接删掉web.xml里的 schema 声明只保留根标签Tomcat 会用默认配置解析。但这样会失去一些校验功能建议还是按规范写。6. 从能跑到能改二次开发与代码优化的几个实操方向把系统跑起来只是第一步毕设答辩时老师大概率会问「你这个系统有什么改进空间」。与其临场编不如提前动手改几处。第一个方向是把 JDBC 直连改成连接池。在WEB-INF/lib下加入commons-dbcp2和commons-pool2的 jar 包然后写一个DataSourceUtilpublic class DataSourceUtil { private static BasicDataSource dataSource new BasicDataSource(); static { dataSource.setDriverClassName(com.mysql.jdbc.Driver); dataSource.setUrl(jdbc:mysql://localhost:3306/agri_sales?useUnicodetruecharacterEncodingutf8mb4useSSLfalse); dataSource.setUsername(root); dataSource.setPassword(your_password); dataSource.setInitialSize(5); dataSource.setMaxTotal(20); dataSource.setMaxWaitMillis(3000); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }initialSize是启动时创建的连接数maxTotal是最大连接数maxWaitMillis是获取连接的超时时间。改完之后DBUtil.getConnection()直接调DataSourceUtil.getConnection()其他代码不用动。第二个方向是给商品列表加分页。老项目通常一次性SELECT * FROM product全查出来数据量大了页面直接卡死。分页的核心是两条 SQL-- 查询总数 SELECT COUNT(*) FROM product WHERE category_id ?; -- 查询当前页数据 SELECT * FROM product WHERE category_id ? LIMIT ?, ?;LIMIT的第一个参数是偏移量(pageNum - 1) * pageSize第二个是每页条数。Servlet 里接收pageNum参数算出偏移量传给 DAOJSP 页面渲染分页导航条。第三个方向是给订单状态加自动流转。目前订单状态是管理员手动改的可以加一个定时任务或者触发器比如下单后 30 分钟未支付自动取消。MySQL 的EVENT可以实现CREATE EVENT auto_cancel_order ON SCHEDULE EVERY 1 MINUTE DO UPDATE orders SET status -1 WHERE status 0 AND create_time DATE_SUB(NOW(), INTERVAL 30 MINUTE);status -1表示已取消。这个 Event 需要 MySQL 开启event_scheduler在my.ini里加event_schedulerON或者运行时执行SET GLOBAL event_scheduler ON;。最后一个实操建议改代码之前先备份数据库和项目。我一般会在项目根目录建一个backup文件夹每次大改之前把WebRoot和src各复制一份数据库用mysqldump导出mysqldump -u root -p agri_sales backup/agri_sales_$(date %Y%m%d).sql从那以后我每次动数据库结构或者改核心 Servlet 之前都强制走一遍备份流程。这套 JSP 毕设系统虽然老但把环境对齐、数据库建好、事务逻辑理清之后跑起来并不难。真正花时间的是排查那些编码、路径、版本不匹配的细节问题。希望帮到你。本文还有配套的精品资源点击获取
返回列表