拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

JSP+MySQL校园二手交易系统设计与部署实战:从表结构到Servlet避坑

JSP+MySQL校园二手交易系统设计与部署实战:从表结构到Servlet避坑

简介:基于JSP与MySQL实现的校园二手物品交易系统,采用B/S架构与SSM框架进行开发,面向毕业设计、课程设计以及正在学习JavaWeb整合开发的读者,解决校园内二手物品信息流转与交易管理问题。系统划分为前台和后台:前台支持用户注册登录、发布物品、分类搜索、下单购买以及个人中心维护,后台提供用户、物品、订单和分类等维度的管理功能。压缩包为7z格式,共202个文件,约12.63MB,包含Java源码及class编译文件、JSP页面、CSS与JS等前端资源、XML与Properties配置、SQL数据库脚本、Word说明文档以及PNG/JPG等图片素材。当前已有21人浏览学习。从内容预览可以看出,代码按照Controller、Service、Mapper三层组织,结构清晰,适合导入开发工具后结合SQL脚本快速搭建环境,进而理解SSM整合流程,并可直接作为毕业设计、课程设计或二次开发的基础。

1. 基于JSP和MySQL的校园二手物品交易系统到底能拿来做什么?

先说结论:用一个JSP+MySQL搭起来的校园二手物品交易系统,最典型的使用场景是毕业设计和课程设计交差,其次是让你在三个月后还能回头读懂自己的JavaWeb代码。它解决的是一所学校里学生之间买卖二手书、闲置数码、考研资料这类低频、实名、小范围交易的完整链路——不是做成闲鱼,而是做成一个能演示“注册、登录、发布闲置、浏览列表、下单或留言、后台管理”全流程的教学范本。

为什么这个题目在JSP课设里长盛不衰?因为它覆盖面刚好:JSP页面负责渲染,Servlet负责请求分发,MySQL负责三张以上业务表的增删改查,恰好把JavaWeb课程最重要的几个知识点全串起来。写系统之前,我习惯先把“提交一个表单后发生了什么”这个问题想清楚,剩下就是照着表的字段去堆代码。这篇笔记从数据库表设计开始,到部署、参数调整、避坑,最后给你几个不加代码量的加分项,全程按我实际做课设和帮人改稿子的顺序来。

2. 先把数据库立住:校园二手物品交易系统的表设计与SQL脚本

2.1 五张核心表,一张都不能少

做课程设计最怕的是表设计理不清,写代码时反复改结构。校园二手物品交易系统的业务其实只有四个动作:用户登录、发布商品、浏览商品、买卖双方达成交易。围绕这四个动作,我一般会建五张表:

  • tb_user:用户表,存学号、姓名、密码、联系方式、头像路径。
  • tb_category:分类表,存书籍、数码、生活用品、其他这几个固定分类。
  • tb_goods:商品表,关联用户和分类,存标题、描述、原价、售价、图片路径、发布时间、状态。
  • tb_order:订单表,关联买家、卖家、商品,存成交价格、下单时间、订单状态。
  • tb_admin:管理员表,后台登录用,独立于前台用户。

这里的核心是tb_goods的状态字段。很多新手会把“已售出”做成删除商品,导致后台查不到历史交易记录,这是最常见的翻车设计。正确做法是保留status字段,用0表示上架中、1表示已售出、2表示已下架,列表查询永远过滤status = 0。

SQL脚本如下,字符集统一用utf8mb4,排序规则用utf8mb4_general_ci:

-- 校园二手物品交易系统建库脚本 V1.0 CREATE DATABASE IF NOT EXISTS campus_trade DEFAULT CHARACTER SET utf8mb4; USE campus_trade; -- 用户表 CREATE TABLE tb_user ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) UNIQUE NOT NULL COMMENT '学号,登录账号', username VARCHAR(50) NOT NULL COMMENT '昵称', password VARCHAR(64) NOT NULL COMMENT '密码,用MD5加密之后存入', phone VARCHAR(20) DEFAULT NULL, avatar VARCHAR(255) DEFAULT '/upload/avatar/default.jpg', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB; -- 分类表 CREATE TABLE tb_category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(30) NOT NULL UNIQUE COMMENT '分类名称', sort INT DEFAULT 0 COMMENT '排序权重' ) ENGINE=InnoDB; -- 商品表 CREATE TABLE tb_goods ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL COMMENT '发布者ID', category_id INT NOT NULL COMMENT '分类ID', title VARCHAR(100) NOT NULL, description TEXT, original_price DECIMAL(10,2) DEFAULT 0, sell_price DECIMAL(10,2) NOT NULL, image VARCHAR(255) DEFAULT NULL COMMENT '商品图片存放路径', status TINYINT DEFAULT 0 COMMENT '0上架 1已售出 2下架', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_user (user_id), KEY idx_category (category_id), KEY idx_status (status), CONSTRAINT fk_goods_user FOREIGN KEY (user_id) REFERENCES tb_user(id), CONSTRAINT fk_goods_cate FOREIGN KEY (category_id) REFERENCES tb_category(id) ) ENGINE=InnoDB; -- 订单表 CREATE TABLE tb_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) UNIQUE NOT NULL COMMENT '订单编号,用时间戳+随机数生成', goods_id INT NOT NULL, buyer_id INT NOT NULL, seller_id INT NOT NULL COMMENT '冗余卖家ID,避免连表查询', price DECIMAL(10,2) NOT NULL COMMENT '成交价', status TINYINT DEFAULT 0 COMMENT '0待确认 1已完成 2已取消', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_goods (goods_id), KEY idx_buyer (buyer_id), KEY idx_seller (seller_id) ) ENGINE=InnoDB; -- 管理员表 CREATE TABLE tb_admin ( id INT PRIMARY KEY AUTO_INCREMENT, admin_name VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(64) NOT NULL ) ENGINE=InnoDB;

2.2 这套表结构里藏着三个必调参数

第一,password字段我用VARCHAR(64),是为了存MD5或SHA256的十六进制结果。如果你的代码里用MD5工具类,32位就够了;如果用SHA-256,就是64位。课程设计一般两种都行,但一定不要明文存密码,答辩时老师几乎必问。

第二,DECIMAL(10,2)不要写成FLOAT。二手物品价格虽然简单,但答辩时老师会追一句“浮点数为什么会出现0.30000000000000004这种误差”,有钱的字段一律用定点数,这是MySQL的基本常识。

第三,create_time我全部使用DEFAULT CURRENT_TIMESTAMP,让数据库自己写时间,而不是在JSP里靠new Date()手动拼。这样数据插入时少一次代码层的时间格式化,也避免服务器时区和数据库时区不一致导致差了8小时。

2.3 数据初始化:管理员账号和演示数据

课程设计交上去,老师第一件事肯定是打开系统看登录页面。所以预置一条管理员账号和几条商品演示数据非常必要:

-- 预设管理员,初始密码 admin123(MD5后) INSERT INTO tb_admin (admin_name, password) VALUES ('admin', '21232f297a57a5a743894a0e4a801fc3'); -- 两个学生用户 INSERT INTO tb_user (student_no, username, password, phone) VALUES ('20210001', '张三', 'e10adc3949ba59abbe56e057f20f883e', '13800000001'), ('20210002', '李四', 'e10adc3949ba59abbe56e057f20f883e', '13800000002'); -- 分类数据 INSERT INTO tb_category (name, sort) VALUES ('教材书籍', 1), ('数码产品', 2), ('生活用品', 3), ('运动健身', 4); -- 演示商品 INSERT INTO tb_goods (user_id, category_id, title, description, original_price, sell_price, status) VALUES (1, 1, '高数第六版 课后习题答案', '九成新,有少量笔记,适合期末突击', 45.00, 15.00, 0), (2, 2, '小米手环5 NFC版', '戴了半年,充电线还在', 199.00, 80.00, 0);

存储引擎我统一用InnoDB,外键约束保留ON DELETE RESTRICT的默认行为,即用户有未删除的商品时不能直接删用户。这对课程设计来说是合理的——老师不会考你怎么级联删除,但外键能防止你写出删了用户导致商品悬空的逻辑。

3. JSP+Servlet代码结构:从一次登录请求看懂请求模型和MVC落法

3.1 项目目录结构:先按MVC把包建对

拿到源码不是先看哪个文件,而是先看目录。一个标准的JSP+Servlet工程不需要引入Spring全家桶,但必须分清楚角色。我习惯用如下目录:

src/ com.campus.dao // 数据库操作,最简单的手写JDBC com.campus.entity // 实体类,对应五张表 com.campus.service // 业务逻辑,放一些简单的判断 com.campus.servlet // 控制器,接收请求、调service、转发JSP com.campus.util // DBUtil、MD5工具 WebContent/ index.jsp // 门户首页,商品列表 user/login.jsp // 登录页 user/register.jsp goods/publish.jsp // 发布商品页 goods/detail.jsp // 商品详情 admin/admin.jsp // 后台管理 WEB-INF/web.xml upload/ // 商品图片上传目录

如果你拿到的源码包没有service这一层,那多半是Servlet直接写JDBC,能跑但答辩时会被问“代码重复怎么办”。建议自己补一层。即使不补,也要在答辩里说清楚你这是瘦控制器风格,业务逻辑会抽取到独立类中。

3.2 从登录功能拆解Servlet请求处理流程

登录是每个用户的入口,也是老师第一个试的功能。登录请求的完整链路值得逐行写清楚:浏览器提交表单,Servlet接收参数,调Service查询tb_user,有记录则把用户对象放进session,重定向到首页;否则返回错误提示。

@WebServlet("/login") public class LoginServlet extends HttpServlet { private UserService userService = new UserService(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1.设置请求编码,这一步不写中文必乱码 request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); String studentNo = request.getParameter("studentNo"); String password = request.getParameter("password"); String md5Pwd = MD5Util.md5(password); User user = userService.login(studentNo, md5Pwd); if (user == null) { // 2.登录失败:回登录页,用request域携带错误信息 request.setAttribute("errorMsg", "学号或密码错误"); request.getRequestDispatcher("/user/login.jsp").forward(request, response); } else { // 3.登录成功:session保存用户信息 HttpSession session = request.getSession(); session.setAttribute("loginUser", user); response.sendRedirect(request.getContextPath() + "/index.jsp"); } } }

这个短方法里有两个避坑点。第一,request.getParameter拿到的是表单里的name值,不设置编码时POST请求的中文大概率会乱。第二,登录失败用forward,成功用sendRedirect。前者保留请求域数据,刷新页面会重复提交;后者让浏览器URL变成首页,避免按F5把表单再提交一次。这两段视频里的老师可能不会展开讲,但面试或答辩时很加分。

3.3 发布商品:图片上传与状态字段的配合

发布闲置是二手交易系统的核心操作,这里的难点是文件上传。JSP端表单必须声明enctype="multipart/form-data",Servlet端如果你用原生的getParameter去取标题,拿到的永远是null。这是最常见的翻车点。

@WebServlet("/publish") public class PublishServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // 先取当前登录用户,没登录则回登录页 HttpSession session = request.getSession(); User loginUser = (User) session.getAttribute("loginUser"); if (loginUser == null) { response.sendRedirect(request.getContextPath() + "/user/login.jsp"); return; } // 用commons-fileupload解析multipart请求 DiskFileItemFactory factory = new DiskFileItemFactory(); factory.setSizeThreshold(1024 * 1024); ServletFileUpload upload = new ServletFileUpload(factory); upload.setFileSizeMax(5 * 1024 * 1024); // 限制单张图片5MB try { List<FileItem> items = upload.parseRequest(request); String title = null, desc = null; String originalPrice = null, sellPrice = null, categoryId = null; String savedFileName = null; for (FileItem item : items) { if (item.isFormField()) { switch (item.getFieldName()) { case "title": title = item.getString("UTF-8"); break; case "description": desc = item.getString("UTF-8"); break; case "originalPrice": originalPrice = item.getString("UTF-8"); break; case "sellPrice": sellPrice = item.getString("UTF-8"); break; case "categoryId": categoryId = item.getString("UTF-8"); break; } } else { String fileName = new File(item.getName()).getName(); String ext = fileName.contains(".") ? fileName.substring(fileName.lastIndexOf(".")) : ""; savedFileName = System.currentTimeMillis() + ext; String savePath = getServletContext().getRealPath("/upload"); File dir = new File(savePath); if (!dir.exists()) dir.mkdirs(); item.write(new File(savePath, savedFileName)); } } GoodsService goodsService = new GoodsService(); goodsService.publish(loginUser.getId(), Integer.parseInt(categoryId), title, desc, new BigDecimal(originalPrice), new BigDecimal(sellPrice), "/upload/" + savedFileName); response.sendRedirect(request.getContextPath() + "/index.jsp"); } catch (Exception e) { e.printStackTrace(); request.setAttribute("errorMsg", "发布失败,请检查图片大小或格式"); request.getRequestDispatcher("/goods/publish.jsp").forward(request, response); } } }

代码里的关键逻辑在于文件名重命名:用System.currentTimeMillis()拼原始扩展名,避免用户上传两个同名文件互相覆盖。item.write本质上把临时文件写到WebContent/upload/。还有一点容易被忽略——diskFileItemFactory.setSizeThreshold(1024*1024)只是内存阈值,超过1MB会写入临时文件;而setFileSizeMax(5*1024*1024)才是硬限制。

3.4 列表页用JSTL而不是脚本片段

拿到一套课程设计源码,很多老代码的index.jsp长这样:<% for (Goods g : goodsList) { %>,在页面里写Java代码。这个写法能用,但在现在IDEA里会画一堆黄色下划线,答辩时印象分也会打个折扣。我更建议改成JSTL+EL:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <c:forEach var="g" items="${goodsList}"> <div class="card"> <img src="${g.image}" alt="${g.title}" /> <h3>${g.title}</h3> <p class="price">¥<fmt:formatNumber value="${g.sellPrice}" pattern="0.00"/></p> <a href="${pageContext.request.contextPath}/goods/detail?id=${g.id}">查看详情</a> </div> </c:forEach>

这里注意${g.image}是相对路径/upload/xxx.jpg,浏览器会拿它拼上当前域名直接访问。如果你的项目部署上下文是campus-trader,那这张图片的完整URL就是http://localhost:8080/campus-trader/upload/xxx.jpg。Servlet在取图片时会去getServletContext().getRealPath("/upload")找文件,两者路径必须严格对应。

4. 把系统跑起来:JDK、Tomcat、MySQL的配置顺序与参数细节

4.1 版本匹配是第一道门槛

拿到源码后先别急着导入IDE,先确认三个版本:JDK版本、Tomcat版本、MySQL驱动版本。这个三元组只要有一个对不上,系统就会在启动或首次查询时报莫名其妙的老虎。课程设计源码多见于JDK 1.8 + Tomcat 8.5/9.0 + MySQL 5.7/8.0,这是目前最稳的组合。如果你用的是JDK 17,Tomcat 9也能跑,但很多老源码里的javax.servlet依赖和JDK模块化会产生额外配置成本。

排查顺序我一般是这样:先看web.xml头部的version,再看pom.xml或WEB-INF/lib下的mysql-connector-java文件。有些源码不带lib目录,驱动jar需要自己补,那就要记住一个原则:MySQL 5.7用mysql-connector-java-5.1.49.jar,MySQL 8.0用mysql-connector-java-8.0.33.jar。版本差一次,启动时出现的报错完全不同。

4.2 数据库连接URL里的必调参数

JDBC连接串里除了地址、账号、密码,还有三个参数直接影响中文和时区行为:

// DBUtil.java 核心代码 String url = "jdbc:mysql://localhost:3306/campus_trade?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true"; String user = "root"; String password = "你的密码"; Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = DriverManager.getConnection(url, user, password);

MySQL 8.0的驱动类名是com.mysql.cj.jdbc.Driver,5.x是com.mysql.jdbc.Driver。如果你的工程用的8.x驱动但写了旧类名,启动后第一次查库会报ClassNotFoundException。serverTimezone=Asia/Shanghai是8.0的必填项,否则还会插入时间字段时报The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。allowPublicKeyRetrieval=true也是8.0特有的,不设的话连接时可能遇到Public Key Retrieval is not allowed。这三行几乎是我排查JSP连MySQL问题的标准开头。

4.3 IDE里部署Web工程的三个参数

用IDEA的话,配置项集中在Run/Debug Configurations -> Tomcat Server下的Deployment选项卡。课程设计里最常见的坑是Web应用上下文路径对不上,导致图片和CSS全挂。我的习惯是统一设置:

  • Application server 指向本机Tomcat安装目录
  • URL 端口保持8080
  • Deployment 里 Deploy at the host root,上下文保持默认应用名,比如campus-trader
  • 然后所有页面里的相对路径都写成${pageContext.request.contextPath}/...

为什么强调这个?因为JSP里如果写死/upload/xxx.jpg,部署上下文不是ROOT时,浏览器会去http://localhost:8080/upload/xxx.jpg找文件,而Tomcat的webapps/upload是空的,系统里所有图片都会裂开。用${pageContext.request.contextPath}才是动态适配上下文的正解。

4.4 MySQL安装与初始化:5.7与8.0的选择

做这个系统时,我用过MySQL 5.7和8.0两种环境,结论是:如果你的源码驱动是5.x,直接装MySQL 5.7最省事。装8.0的话,务必把驱动也换成8.x,并设置好时区参数。Windows下安装MySQL时,安装向导里“Choose a Setup Type”我一般选Developer Default,但要注意端口3306有没有被占用。如果之前装过旧MySQL,先清干净C:\ProgramData\MySQL残留目录,否则新版服务会启动失败。

安装完成后两个必做动作:

# 检查MySQL服务状态(Windows) net start | findstr -i mysql # 如果服务没启动,以管理员身份执行 net start mysql # 命令行登录测试 mysql -u root -p

登录后执行SHOW DATABASES;,看是否存在campus_trade。如果没有,把第2章的建库脚本整个粘贴进去执行。这一步很多人会在IDE里做,结果连接失败时第一反应是代码问题,最后发现库根本没建。

5. 避坑与排查:校园二手物品交易系统从部署到运行的常见问题

5.1 登录时报500:驱动类找不到或数据库连不上

现象:输入学号密码点登录,页面直接跳出HTTP Status 500,堆栈第一行是java.sql.SQLException: No suitable driver found for jdbc:mysql://localhost:3306/campus_trade。

原因:MySQL驱动jar包没有放到Web应用类路径下。常见做法是把jar复制到WebContent/WEB-INF/lib,但有时在IDEA里没刷新依赖,或者用的是Maven但pom.xml没有重新导入。

解决:先确认WEB-INF/lib下存在mysql-connector-java-*.jar;如果存在,检查Build Path里是否把它添加为Library。对不熟悉Maven的,建议直接手动复制jar到lib目录,这样最不容易出错。

5.2 商品图片上传后访问404

现象:发布商品时图片上传成功,后台upload/目录下也有文件,但浏览器打开商品详情时图片裂开,控制台404。

原因:JSP页面里写死了/upload/xxx.jpg,未拼接应用的上下文路径。部署上下文不是根路径时,真实访问URL是/上下文名/upload/xxx.jpg。另外还有个坑是Tomcat的conf/web.xml里对/upload目录的映射规则,如果你的工程在根路径部署,直接访问/upload可能被DefaultServlet处理,但文件存在性检查失败。

解决:页面统一使用${pageContext.request.contextPath}/upload/xxx.jpg,后端存/upload/xxx.jpg,前端取时拼上下文。

5.3 POST提交中文乱码

现象:登录成功后,网页右上角用户名显示成一串问号;发布商品描述也乱,且数据库表里直接存的是乱码。

原因:JSP页面Content-Type没设置UTF-8,Servlet也忘了调request.setCharacterEncoding("UTF-8")。更隐蔽的原因是Tomcat 8以上GET请求默认UTF-8,但POST就看你过滤器。

解决:写一个编码过滤器,拦截所有请求,在doFilter里统一执行:

request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8");

把这个过滤器配置到web.xml的<filter-mapping>里,URL匹配/*。保证所有Servlet都先过这一层。

5.4 商品列表一片空白,控制台没有异常

现象:首页能打开,但<c:forEach>渲染的区域是空的,整个div高度为0,等会儿又能看到数据闪现,某些浏览器直接空白。

原因:最常见是Servlet在doGet里查了数据,但没有将其放入request或session作用域。或者是JSP页面没有声明JSTL的taglib,报错被isELIgnored吞掉。老的Tomcat版本默认isELIgnored="false",但某些IDE生成的web.xml会开启isELIgnored。

解决:把查询代码贴过来看清楚。参考做法是:

GoodsService goodsService = new GoodsService(); List<Goods> goodsList = goodsService.findAllOnSale(); request.setAttribute("goodsList", goodsList); request.getRequestDispatcher("/index.jsp").forward(request, response);

然后JSP头里加<%@ page isELIgnored="false" %>并引入两个taglib。

5.5 后台管理登录不进去,管理员表是空的

现象:前台用户注册登录都正常,但用admin/admin123后台登录时提示“用户名或密码错误”。

原因:两张表——前台是tb_user,后台是tb_admin。很多源码包自带的SQL脚本里没有插入管理员初始数据,只建了表结构。你查tb_admin会发现一条记录都没有。

解决:手动向tb_admin插入一条MD5后的密码记录。MD5值为21232f297a57a5a743894a0e4a801fc3。如果登录逻辑里用SHA-256,则改成SHA-256的哈希。注意看源码里AdminService的加密算法到底用的哪种。

6. 让毕业设计看起来像成品:三个不用加代码量的验证与加分点

6.1 过滤器统一编码与登录拦截

第一个加分项就是第5.3节提到的编码过滤器。它还能顺手做登录拦截——在doFilter里判断session.getAttribute("loginUser")是否为空,为空则跳回登录页。这个功能只有十几行代码,但比写一百行权限判断更容易让老师看到你有安全意识。

建议在web.xml里配置两个过滤映射:一个encodingFilter拦所有路径,一个loginFilter只拦/goods/publish和/user/center等需要登录的路径,比如:

<filter> <filter-name>loginFilter</filter-name> <filter-class>com.campus.filter.LoginFilter</filter-class> </filter> <filter-mapping> <filter-name>loginFilter</filter-name> <url-pattern>/goods/publish/*</url-pattern> <url-pattern>/user/center/*</url-pattern> </filter-mapping>

这样未登录用户直接访问发布页面会被强制拉回登录页,在演示时这个行为是实打实能看到的。

6.2 验证链路:用一条SQL确认系统闭环

系统做完,我习惯在MySQL命令行里做一次完整链路验证,而不是光靠UI点点点。发一条SQL:

SELECT g.title AS 商品, u.username AS 卖家, o.price AS 成交价, o.create_time AS 成交时间, o.status AS 订单状态 FROM tb_order o JOIN tb_goods g ON o.goods_id = g.id JOIN tb_user u ON o.seller_id = u.id ORDER BY o.create_time DESC;

这条SQL能查出订单表是否通过外键正确关联了商品和用户。如果这张表查出来为空,说明你的代码里下单逻辑没有真正插入订单数据——无论界面弹了什么成功提示,这条查询才是最诚实的。课程设计答辩时,老师最爱问“为什么订单表里看不到刚才那条操作”,所以这个验证步骤值得在演示前跑一遍。

6.3 用日志反查一个“说不清”的Bug

第三个技巧是遇到诡异Bug时,别只盯着Tomcat控制台,而是关注三层日志:JSP编译日志、Servlet异常堆栈、MySQL的general_log。MySQL开启general有代价,但在课设调试场景没关系,可以在my.ini里临时打开:

general_log=ON general_log_file="C:/ProgramData/MySQL/MySQL Server 8.0/Data/general.log"

然后重复一次出错的操作,打开日志文件,能看到MySQL实际收到和执行了哪条SQL。很多时候界面报错是Java层变量拼接了非法SQL,而这条明细能让问题暴露出来的速度翻倍。调完记得把general_log关掉,否则日志文件会快速增长。

我自己做这套系统时,被坑得最惨的一次就是图片404——当时查了半小时代码,最后发现是Tomcat部署名多了一层版本号,所有路径全差一个目录。从那之后,我每次部署Web工程都先看一遍URL栏里的上下文路径,再决定是不是要去查代码。做校园二手物品交易这种经典课设,代码本身不难,难的是把数据库、容器、驱动这三个黑匣子都调到同一个频道。只要你顺着这篇文章把表建好、请求模型理清、部署参数对一遍,这套源码在你手里就能从“能跑”变成“敢上台演示”的状态。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表