每年课程设计季都会看到一批同学抱着“JSP农贸产品交易系统”这种题目发愁。说白了它就是电商系统的简化版只不过商品换成了蔬菜水果、粮油米面。今天拿这个g3608项目当例子把需求拆解、数据库设计、核心功能实现、调试部署这整条链路彻底捋一遍。无论你是第一次做Java Web课设还是已经在跑项目但被各种报错卡住这篇文章都能帮你把从源码到可运行系统的路走通。这套思路换到图书商城、二手交易平台、校园论坛这类题目上也完全通用底层逻辑是一样的。1. 拿到“农贸产品交易系统”题目后先拆清楚功能边界1.1 交易系统的主链路与最小功能集很多同学一上来就写代码结果写了三天发现页面缺东少西后面返工成本极高。正确做法是先花半天时间把“农贸产品交易系统”这句话翻译成功能清单。所谓交易系统核心链路只有一条浏览商品 → 加入购物车 → 生成订单。所有页面和逻辑都是围绕这条链路展开的。围绕主链路前台需要哪些东西我列一下最小功能集首页展示推荐商品、公告信息商品模块分类导航、商品列表分页、商品详情用户模块注册、登录、个人信息购物车模块加入购物车、修改数量、删除商品、购物车结算订单模块确认订单、生成订单、我的订单列表后台管理端是另一组CRUD功能不需要太复杂商品管理新增、编辑、下架、删除商品分类管理维护商品分类订单管理修改订单发货状态、查看订单详情用户管理查看注册用户列表这套功能全做出来项目主体就已经完整了。答辩时老师问“这个系统有哪些角色、哪些功能”你按这条链路讲逻辑非常清晰。1.2 农产品的特殊性对功能设计的影响农贸产品和普通图书、数码产品不同有几个特点会直接影响字段和页面设计。第一是季节性。很多农产品只有特定月份才上市比如大闸蟹、荔枝所以商品表里应该有一个上架时间或者上架状态字段。后台可以设置商品“上架/下架”过期商品下架后前台就搜不到这个设计很符合农贸场景。第二是产地属性。农产品买家很在意“哪里产的”比如东北大米、胶州白菜、烟台苹果。我建议商品表加一个origin字段详情页里展示“产地某某地区”前后台都能维护功能不大但显得很懂业务。第三是价格波动。蔬菜价格一天一个价后台改价功能必须要流畅。别把价格字段设计成不可修改的旧商品快照后台编辑页面直接一个价格输入框提交就生效。把握住这三个点项目就比纯模板电商系统多了农贸特色。课程设计里这种贴合业务场景的小细节往往是老师给高分的点。1.3 角色权限与Session控制的基本思路系统需要两种角色普通用户和管理员。用户注册进去默认是普通角色管理员账号不用单独开发注册页面直接在数据库预置一条记录就行比如admin / 123456密码存MD5值。权限控制不需要引入Spring Security这类重型框架用Session就够。登录成功后把用户对象存进SessionHttpSession session request.getSession(); session.setAttribute(user, user);JSP页面的导航栏用c:if标签判断Session中是否有user对象为空显示“登录 / 注册”链接非空显示“欢迎xxx | 退出登录”这里有个新手最容易犯的认知错误以为在页面上把操作按钮藏起来就等于权限控制了。错。用户完全可以绕过页面直接向后台Servlet发请求。所以真正的权限校验必须写在Servlet的doGet/doPost开头——判断Session里的用户是否为空没登录就重定向到登录页。页面隐藏按钮只是体验优化Servlet校验才是安全边界。1.4 功能边界哪些模块坚决不碰课程设计的大忌是功能堆砌。我见过有人往课设里加微信支付、加短信验证码、加多商家入驻最后项目烂尾。这里说几个“别碰”的方向真实支付支付接口需要商户号、签名证书、审核资质个人课设根本申请不下来。就算接了个沙箱环境也会消耗大量时间在对接上。订单状态里加一个“已模拟支付”就够用了。多商家入驻这意味着要再拆一套商家端、审核流程、店铺表工作量直接翻倍。消息推送/即时通信和交易系统主链路关系不大属于给自己加戏。一句话定位这是一个练习MVC分层和数据库操作的交易系统功能走到订单生成就算完整闭环。把主链路做扎实远比铺开一堆半成品功能强。2. 技术选型JSPServletJavaBean课程设计的稳妥组合2.1 Model 1还是Model 2JSP开发有两条路线选之前一定要搞清楚区别。Model 1是JSP页面直接调用JavaBean完成业务请求先到JSPJSP里既写页面标签又写Java代码职责混在一起。优点是小项目开发快缺点是页面里全是% %维护性极差。Model 2是JSP Servlet JavaBean也就是轻量级MVC。Servlet负责接收请求、调用业务、控制跳转JavaBean负责业务逻辑和数据库操作JSP只负责展示。农贸产品交易系统涉及商品、用户、订单三类核心模块功能不算少我强烈建议用Model 2。不然商品列表和购物车页面的Java代码会混成一锅粥改一个查询条件可能要翻好几个页面。2.2 不直接上手Spring MVC的理由肯定有人问现在公司里都Spring Boot了课程设计还折腾JSPServlet是不是有点老往前推几年的高校课设主流就是这套技术栈。写教材、实验大纲、答辩评分标准的老师最熟悉的就是JSPServlet答辩时老师常问的问题——Servlet生命周期是什么、请求转发和重定向有什么区别、Session和Cookie区别——全部基于这套技术栈。你用了Spring MVC代码能少写但底层机制反而答不上来。另外很多学校课设报告模板里的“系统架构图”“技术选型依据”也是按JSPServlet这套体系设计的。除非任课老师明确允许用框架否则JSPServlet就是最稳妥的选择。更重要的是先把这套技术栈跑通理解Servlet如何接收请求、如何转发页面、如何管理Session之后再去学Spring MVC你会瞬间理解框架帮你封装了什么——是带你成长不是拖你后腿。2.3 兼容性最好的版本组合技术栈选好了版本也要选稳。我重复调试过很多次最省心的组合是这个组件推荐版本说明JDK1.8.0_271兼容所有老Tomcat和JDBC驱动Tomcat8.5.xServlet规范3.1支持JSP 2.3MySQL5.7.x稳定、教程多、驱动兼容IDEA2020.3及以上社区版足矣Navicat15数据库可视化管理千万别用JDK 11以上跑老项目Tomcat版本和JDK版本之间兼容性很容易出问题。MySQL 8.x也能跑但驱动类名、连接串都变了新手在这里消耗一天很常见而且毫无必要。课设求稳这套组合闭眼用。2.4 JDBC封装与DBCP连接池连接数据库的方式一般两条路自己封装JDBC工具类或者用DBCP连接池。我推荐DBCP。它的核心价值是连接复用——不用每次请求都重新建立数据库连接同时还能控制并发连接数。DBCP在项目里的使用方式很简单核心配置就三个initialSize初始连接数比如5maxActive最大活跃连接数比如20maxWait获取连接最大等待毫秒数超过就报错在课设答辩时你能讲清楚“为什么使用连接池”——数据库连接的建立是重操作频繁创建销毁影响性能DBCP提前准备好一批连接循环利用——这已经是加分项了。准备好一个db.properties把连接池参数和数据库账号密码放一起维护起来也方便。jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/db_farm?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 jdbc.usernameroot jdbc.password123456 jdbc.initialSize5 jdbc.maxActive20 jdbc.maxWait100003. 数据库设计七张表把农贸交易系统串起来3.1 核心表结构与字段说明数据库是整个系统的地基表设计错了后面全跟着错。这个项目我建议建七张表覆盖交易全链路用户表 t_user字段类型说明idINT 自增主键用户IDusernameVARCHAR(50) 唯一用户名passwordVARCHAR(32)MD5加密后的密码phoneVARCHAR(20)手机号addressVARCHAR(255)收货地址create_timeDATETIME注册时间商品分类表 t_category字段类型说明idINT 自增主键分类IDnameVARCHAR(50)分类名称如蔬菜、水果descriptionVARCHAR(255)分类描述商品表 t_product字段类型说明idINT 自增主键商品IDcategory_idINT外键关联分类表nameVARCHAR(100)商品名称priceDECIMAL(10,2)商品单价stockINT库存数量imageVARCHAR(255)图片相对路径originVARCHAR(100)产地体现农贸特色descriptionTEXT商品详情create_timeDATETIME上架时间购物车表 t_cart字段类型说明idINT 自增主键购物车项IDuser_idINT用户IDproduct_idINT商品IDquantityINT购买数量create_timeDATETIME加入时间订单表 t_order字段类型说明idINT 自增主键订单IDorder_noVARCHAR(32) 唯一订单编号user_idINT下单用户IDtotal_priceDECIMAL(10,2)订单总金额statusTINYINT状态0待发货 1已发货 2已完成create_timeDATETIME下单时间订单明细表 t_order_item字段类型说明idINT 自增主键明细IDorder_idINT关联订单主表product_idINT商品IDproduct_nameVARCHAR(100)下单时商品名快照priceDECIMAL(10,2)下单时单价快照quantityINT购买数量公告表 t_notice字段类型说明idINT 自增主键公告IDtitleVARCHAR(100)公告标题contentTEXT公告内容create_timeDATETIME发布时间这七张表之间的关系很清晰商品的category_id关联分类表购物车和订单的user_id关联用户表订单明细的order_id关联订单主表。每次写SQL时都会用到这些关系Java代码里用JOIN或者子查询都能体现。3.2 金额字段必须用DECIMAL而不是浮点类型这是交易系统里最不能妥协的原则所有金额字段用DECIMAL(10,2)严禁用FLOAT或DOUBLE。原因在于浮点数在计算机里是二进制存储的很多十进制小数无法精确表示。两个浮点金额相加可能得到0.1 0.2 0.30000000000000004这种结果。电商交易系统哪怕差一分钱都不能接受科目里出现这种精度问题就是事故。DECIMAL以字符串方式精确存储小数MySQL内部对DECIMAL的计算也是精确的。Java端用BigDecimal接收传参时用setBigDecimal全程无精度丢失。这个习惯带进以后的工作项目非常有用。3.3 订单明细为什么要把商品名和价格存进来这个点我在写课设时也曾经想不明白。刚开始把订单明细设计成只有order_id和product_id需要时再去商品表联查名字和价格。后来发现业务逻辑有问题用户下单之后管理员完全可能改价、下架甚至删除商品。等用户查看“我的订单”时再去商品表查询要么查不到记录要么价格已经变了订单上展示的就不是用户购买时的实际价格了。解决方案就是快照设计在订单明细表里冗余product_name和price字段下单那一刻把名称和单价“冻结”下来。虽然看起来违反范式但这是订单系统的标准做法电商大厂也都是这么干的。数据库设计不是教科书里背出范式就能打天下业务合理才是第一位的。3.4 索引、外键与初始化数据索引不用建太多两个最关键t_order(order_no)唯一索引订单号按号查询很频繁并且不能重复t_product(category_id)普通索引分类页查询商品经常按这个字段过滤外键做不做我的建议是可以做但删除的时候要注意约束。比如要删除某个分类如果这个分类下还有商品外键约束会直接报错。所以在Java代码里删除分类前要先处理该分类下的商品——要么删除商品要么把商品的category_id改成其他分类。初始化数据这块经常看人忽略。一个好的SQL脚本应该自带测试数据管理员账号、五六个商品分类、十来个带图片的商品、一篇公告。项目导入后直接能看到效果首页不会空荡荡。写SQL脚本时用INSERT语句预置这些数据调试和答辩演示都方便很多。4. 核心功能实现从JSP页面到数据库操作的完整链路4.1 注册登录防重复与MD5加密注册功能虽然简单但有两个点必须处理好。第一是用户名防重复。RegisterServlet接收注册参数后先执行一条查询SELECT COUNT(*) FROM t_user WHERE username ?。大于0就回注册页并提示“用户名已被占用”否则才执行INSERT。这里必须用PreparedStatement不能用拼字符串的Statement——既防SQL注入预编译SQL效率也更高。第二是密码加密存储。数据库表里不能存明文密码。用MD5工具类把密码加密成32位十六进制字符串再入库。登录校验时把用户输入的密码也做一次MD5然后和库里的密文比对。String md5Password MemberUtil.md5(password); String sql SELECT * FROM t_user WHERE username ? AND password ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, md5Password);登录成功后在Session里存用户对象首页导航根据Session状态动态变化。退出登录就是session.invalidate()简单直接。4.2 商品列表分页JSTL与EL表达式的配合商品列表分页是整个JSP阶段最值得吃透的功能你把这一套学会后面所有列表页都能套用。实现思路是ProductServlet接收page参数默认1先去数据库COUNT(*)算出总记录数除以每页条数我习惯每页9个3行3列向上取整得到总页数。然后查当前页数据SELECT * FROM t_product LIMIT ?, 9其中?是(page - 1) * 9也就是偏移量。把list、currentPage、totalPages三个属性塞进request转发到product_list.jsp。JSP页面里的核心就是JSTL和EL表达式c:forEach items${list} varp div classproduct-card img src${p.image} / h3${p.name}/h3 span${p.price}/span a hrefproductDetail?id${p.id}查看详情/a /div /c:forEach分页条用c:if判断上一页/下一页是否可用用c:forEach配合begin和end生成页码数。这套分页逻辑理解了以后后台商品管理、订单列表都能复用同一套写法。4.3 购物车放Session还是数据库购物车是放Session还是数据库是我当年纠结过的问题两个方案各有适用场景。放Session数据结构是MapInteger, Integerkey是商品IDvalue是购买数量。代码实现简单直观刷新页面数据不丢用户浏览器关闭数据自动清理。缺点是换设备或换浏览器后购物车无法恢复。放数据库每次加购、删购都要写SQL还要考虑并发、多表联查代码明显复杂。好处是跨设备同步用户换电脑登录还能看到购物车。课程设计阶段我推荐用Session。购物车本质是下单前的临时状态在没生成订单之前不需要持久化到数据库。把Session方案写流畅后再想想“为什么大厂电商要把购物车放服务端”你对状态存储的理解会上一个台阶。4.4 生成订单事务处理的制高点生成订单是整个项目技术含量最高的功能也是答辩时最值得展开讲的地方。用户点击“提交订单”后OrderServlet要做的事不少查购物车循环算出总价向t_order插入一条订单记录拿到自增ID循环购物车每一项插入t_order_item订单明细清空该用户的购物车同步扣减t_product中的库存这五步是要么全成功要么全失败的关系。比如订单插入成功但购物车没清空用户重复提交就会产生重复订单再比如库存扣了但订单没建出来商品就莫名其妙少了一件。解决办法是数据库事务conn.setAutoCommit(false); try { // 插入订单主表 // 插入订单明细 // 清空购物车 // 扣减库存 conn.commit(); } catch (SQLException e) { conn.rollback(); throw new RuntimeException(下单失败事务已回滚); } finally { conn.setAutoCommit(true); }答辩时把这个逻辑讲清楚比多做五个功能页面都管用。因为你用实际例子说明了数据库事务的原子性老师知道你真正理解了。4.5 后台商品图片上传的正确姿势后台新增商品要支持图片上传这里有一个必然遇到的坑。JSP表单必须加enctypemultipart/form-data这个属性一加Servlet里再调用request.getParameter(name)就会返回null很多同学死磕在这里。解决办法是引入commons-fileupload组件。代码里先判断请求是不是MultipartRequest再用工厂类和解析器把文件流提取出来保存到项目部署目录下的upload文件夹。数据库image字段只存相对路径比如upload/potato.jpg。JSP页面上img src${pageContext.request.contextPath}/upload/potato.jpg就能正常显示。图片保存路径有一个部署细节要记住不要保存到IDEA项目源码目录下要保存到Tomcat实际部署目录下的upload文件夹不然重启Tomcat后图片容易丢失。5. 调试部署全流程从IDEA配置到Tomcat跑起来5.1 拿到源码包后的目录结构辨识g3608这种源码包解压后通常是一个标准Web项目结构第一件事是认清目录src所有Java源码包括Servlet、JavaBean、工具类、数据库配置文件webJSP页面、CSS、JS、images静态资源web/WEB-INFweb.xml部署描述文件和lib目录第三方jar包如果发现lib目录是空的别慌很可能是上传压缩时忽略了空目录。去Maven仓库把JSTL、mysql-connector-java、commons-fileupload这几个必要jar包下载补齐即可。先把这个结构理清再在IDEA里配置就不会像无头苍蝇一样乱点。5.2 IDEA导入与Project Structure配置用IDEA打开项目后重点检查三个地方第一个是Project SDK。File Project Structure Project选择1.8版本没有就Add SDK Download JDK下载。第二个是Language Level。保持8。第三个是Modules Dependencies。把web/WEB-INF/lib下面所有jar包都加入依赖很多人漏看这一步运行时动不动就ClassNotFoundException。如果源码包是Eclipse格式的IDEA导入后可能无法识别为Web项目。遇到这种情况用File New Module from Existing Sources重新指向源码根目录IDEA会重新解析结构。5.3 Tomcat运行配置与Artifact运行配置是新手最容易卡的环节。步骤是Run Edit Configurations左上角加号选Tomcat Server Local在Server标签页配置Tomcat安装目录切到Deployment标签页点加号选Artifact必须选war exploded包展开目录部署这样修改代码后热部署才灵敏Application context建议填/根路径访问时就不用带项目名前缀配置好后启动控制台出现Server startup in [xxx] milliseconds就说明Tomcat起来了。这里还要提一个Windows特有的问题控制台中文乱码。解决方法是修改Tomcat的conf/logging.properties把java.util.logging.ConsoleHandler.encoding从UTF-8改成GBK重启即可。5.4 数据库导入与连接配置修改数据库脚本一般放在项目的database或sql目录下名字类似farm.sql。用Navicat新建一个数据库名字保持db_farm字符集选utf8mb4。然后右键数据库选择“运行SQL文件”导入脚本。成功后能看到七张表和预置数据。接着改数据库连接配置在src目录下的db.properties文件里把jdbc.username和jdbc.password改成你自己MySQL的账号密码。改完后先别急着启动Tomcat。写一个main方法测一下DBUtil能不能连通数据库能打印出连接成功再启动。把问题链路拆开排查起来省力得多。5.5 404排查与日志使用思路项目启动后浏览器访问http://localhost:8080/应该能看到首页。如果点击功能出现404按这个思路排查打开浏览器F12切换到Network面板看请求发到了哪个URL、返回什么状态码。404通常有三种原因web.xml里Servlet的url-pattern没匹配上请求路径JSP链接写成了相对路径部署后上下文路径变了JSP被错放进了WEB-INF目录不能直接通过URL访问WEB-INF下的页面只能通过Servlet转发最怕遇到的情况是出问题后不看日志、不看Network四处乱改代码。Tomcat控制台的异常堆栈第一行就是你该看的重点信息比如ClassNotFoundException哪个类、SQLException哪条SQL按图索骥问题很快能定位。6. 环境准备与常见报错速查6.1 可以直接抄的版本清单我调试这套项目时用的环境直接列出来写课设报告软件环境部分也可以直接参考组件版本JDK1.8.0_271Tomcat8.5.71MySQL5.7.36IDEA2020.3mysql-connector-java5.1.49commons-fileupload1.4JSTL1.2这些版本都不是最新的但绝对是最稳的。老版本意味着资料多、踩坑少、教程查得到。课设阶段求“跑通、讲清、答辩过”不追新版本。6.2 高频报错的症状、原因与对策把常见的报错症状、原因和对策整理成对照表遇到问题直接查报错信息常见原因解决方法Access denied for user rootlocalhost数据库密码错误或账号权限不足检查db.properties用户名密码是否与MySQL一致ClassNotFoundException: com.mysql.jdbc.Driver缺数据库驱动jar包lib目录加入mysql-connector-java并在IDEA中添加到依赖Communications link failure连接串缺少时区或SSL参数URL加?useSSLfalseserverTimezoneAsia/Shanghai页面中文全部乱码编码不一致统一JSP pageEncoding、Servlet response编码、连接串characterEncodingutf8404且Network显示请求未到Servletweb.xml映射或上下文路径问题检查url-pattern、在JSP用${pageContext.request.contextPath}拼接路径Address already in use: JVM_BindTomcat端口8080被占用Windows执行netstat -ano | findstr 8080查PIDtaskkill /PID xxx /FServer startup失败报Servlet初始化异常web.xml配置或类路径错误对照源码包内包名检查web.xml的servlet-class和url-pattern6.3 三个容易忽略的部署细节最后补三个我自己实战踩过的小坑都是网上教程很少提的。第一个是图片上传后访问404。原因是IDEA热部署目录和Tomcat部署目录不一致图片存到了临时目录。解决办法是把图片统一保存到项目部署目录下的upload文件夹并且用绝对路径方式访问。不要在IDEA源码目录里折腾图片等你清理缓存、重新编译的时候图片就没了。第二个是不要直接替换网上下载的class文件。网上有人说“给你编译好的class拷贝到Tomcat下就能跑”听起来很方便但别人的编译环境、JDK版本、编码方式和你完全不同替换后轻则乱码重则直接报NoClassDefFoundError。老老实实从源码构建虽然多花几十秒但结果可控。第三个是数据库连接串里一定要带characterEncodingutf8。很多同学只加serverTimezone乱码问题就在JSP和数据库之间反复横跳。连接串完整写法是jdbc:mysql://localhost:3306/db_farm?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8这个参数组合能一次性解决时区、SSL握手、中文乱码三个问题强烈建议直接复制。如果你正卡在这个项目的某个环节我的建议是先按顺序把环境搭好导入数据库再启动Tomcat出现一个能展示商品的首页就算迈过最大的坎。剩下的功能逐个补每写完一个模块就重启验证一次而不是一口气把代码全部写完再统一调——那样出问题真的很难定位。课设本身不难难的是沉下心把一条链路彻底走通。走通之后你会发现后面再学Spring Boot很多概念都是原来就有的只是框架帮你省了写模板代码的时间。