☰
JavaWeb校园二手图书交易系统课设:从登录到下单全流程实现
2026/10/7 18:58:53 网站建设 项目流程

简介:面向需要完成Java课程设计或期末大作业的学生,提供基于JavaWeb的校园二手图书交易系统完整源码与数据库。项目由助教审定,本地编译可运行,评审分达95分以上,难度适中,适合学习JavaWeb项目开发流程,也适合作为课程设计蓝本。资源包共485个文件,压缩后仅3.18MB,其中92个Java文件为核心业务源码,230个XML配置涵盖框架与项目配置,另有Vue前端组件、JavaScript脚本、SQL数据库脚本及编译后的class文件,内容完整,目录结构清晰,便于按模块查阅。目前已有192人学习下载。项目包含管理员控制、订单处理、闲置商品管理等模块,覆盖用户登录、图书发布、浏览、下单与订单管理等典型校园交易流程。通过源码可掌握JavaWeb分层架构、数据库交互及前后端协作方式,还可将数据库脚本直接导入使用,方便在现有基础上扩展预约、收藏等功能。源码注释和类命名规范清晰,便于快速定位关键逻辑,是JavaWeb综合训练的实用参考。

1. JavaWeb校园二手图书交易系统:一门课设为什么能拉开15分差距

课程设计打分最狠的差距,往往不在功能多少,而在一套系统有没有把“从登录到下单”的交易闭环跑完整。校园二手图书交易系统是JavaWeb课设里的常客,需求清楚、表结构不复杂,但真正要拿到95分以上高分大作业,你需要把登录校验、图书发布、图片上传、购物车、订单库存扣减这几段全部打通,还能在答辩现场把事务逻辑讲明白。这套方案覆盖Java基础、Servlet、JSP、MySQL增删改查全链路,适合大三正在做Java课设或者期末大作业的同学照着落地、改着验收。

2. 技术选型和模块边界:动手之前先定三层结构

2.1 Servlet+JSP为什么还是课设第一选择

每次带学弟做课设,十个里有八个会问“能不能直接用Spring Boot”。能用,但打分老师看到你用框架,重点就不会放在功能上了,而是追着你问“原理你能讲清吗”。Spring Boot把Tomcat、参数绑定、JSON转换都封装掉了,答辩时问一个Servlet生命周期,你很容易卡壳。JavaWeb课设老老实实用Servlet+JSP写,反而是在用最接近Java基础的方式证明你真懂Web开发。

Servlet负责接收请求和跳转,JSP负责渲染页面,POJO装数据,这套模型课堂上都讲过;过滤器还能顺手做登录拦截,这些天然就是加分材料。选型结论:Servlet 3.0 + JSP 2.3 + Tomcat 9 + MySQL 5.7/8.x + JDBC,最稳。Tomcat 10别碰,包名从javax改成了jakarta,照着老教程抄代码会大面积报ClassNotFound,纯属给自己挖坑。

数据库连接也别一上来就上连接池,课设这个并发量,DriverManager数据库连接完全够用。先把JDBC的Connection、PreparedStatement、ResultSet三件套写顺,答辩时老师问“数据库是怎么连的”,你能把手写DBUtil的每个参数讲清楚,这比报一个黑匣子式的框架依赖要实在得多。

2.2 六个核心模块与页面流转

校园二手图书交易系统看着界面多,拆开其实就是六个模块,每个模块对应一个Servlet、一到两个JSP、一张主表。把模块边界划清楚,后面写代码才不会乱。

模块入口Servlet核心JSP页面数据库表
用户注册登录UserServletlogin.jsp / register.jsptb_user
图书发布与展示BookServletpublish.jsp / detail.jsptb_book
分类与搜索BookServletindex.jsp / search.jsptb_book
购物车管理CartServletcart.jsptb_cart
订单交易OrderServletconfirm.jsp / orders.jsptb_order
评论与后台管理AdminServletadmin/index.jsptb_comment

模块之间唯一的流转规则就是页面跳转:首页点图书进详情页,详情页加购物车,购物车勾选去结算,结算成功后跳到订单列表。这条路径就是答辩时的DEMO主线,只要主线不断,功能分就差不了。后台管理我一般会放一个管理员角色,负责下架违规图书,这能体现你有权限分级的意识。

2.3 三层结构:Servlet、Service、DAO各管一段

源码组织建议按三层结构拆包,这是95分以上高分大作业的基本盘。包名用项目名全小写,例如com.example.bookmarket,下面按controller、service、dao、entity、util分层。

src/main/java/com/example/bookmarket/ ├── controller/ # Servlet,只做参数接收和页面跳转 ├── service/ # 业务逻辑与事务边界 ├── dao/ # JDBC数据访问,只写SQL ├── entity/ # 与表对应的POJO └── util/ # DBUtil、分页工具等

很多低分课设的通病,是把SQL全部写在Servlet里,一个方法几百行,老师看代码第一眼就劝退了。分层之后各管一段:Servlet从request里拿参数、调用Service、把结果塞进request域再forward;Service处理业务规则,比如下单时扣库存、判断图书是否已售;DAO只做增删改查。这样每个类都很短,答辩时老师随便点开一个文件你都能说清它的职责。

我自己的习惯是Service层接口+实现类分开写,虽然课设多几个文件,但看着专业。如果时间紧,接口可以省,直接用实现类,不影响验收,关键是DAO和Servlet绝对不能互相越界。

3. 数据库设计:五张表撑起“上架-下单-成交”全流程

3.1 表结构设计:从tb_user到tb_comment的取舍

这套系统说到底是交易系统,数据库设计的核心就一句话:谁、在什么时候、用什么价格、把哪本书卖给了谁。围绕这个主线,五张表就够:用户表、图书表、购物车表、订单表、评论表。别贪多,老师看重的是表间关系能不能讲圆。

用户表的关键字段是role和status,role区分普通用户和管理员,status用于冻结账号。图书表是核心表,seller_id绑定发布者,category做分类筛选,sell_price和original_price分开存,因为二手书原价和成交价差距很大。订单表必须冗余一份price和receiver、phone、address,这叫“订单快照”,用户买的时候什么价、送到哪,要原样留在订单里,不能等图书改价或用户改了资料再变,这是交易系统最基本的要求。

购物车表我加了联合唯一键(user_id, book_id),同一本书在同一个用户的购物车里只能出现一次。评论表独立出来,是为了在图书详情页展示历史评价,挂到图书表上会让图书表越来越臃肿。

3.2 建表SQL与初始数据:一份能直接跑通的脚本

数据库名建议用book_market,字符集统一utf8mb4,不然中文容易乱码。下面是完整建表脚本,存成init.sql直接执行。

-- 用户表:role 0普通用户 1管理员 CREATE TABLE `tb_user` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL UNIQUE COMMENT '登录名', `password` VARCHAR(64) NOT NULL COMMENT 'MD5加密后的密码', `nickname` VARCHAR(50) DEFAULT '' COMMENT '昵称', `phone` VARCHAR(20) DEFAULT '', `email` VARCHAR(100) DEFAULT '', `avatar` VARCHAR(255) DEFAULT '' COMMENT '头像相对路径', `role` TINYINT NOT NULL DEFAULT 0 COMMENT '0普通 1管理员', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0正常 1冻结', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- 图书表:status 0在售 1已售 2下架 CREATE TABLE `tb_book` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `seller_id` INT NOT NULL COMMENT '卖家ID,关联tb_user', `title` VARCHAR(100) NOT NULL COMMENT '书名', `author` VARCHAR(50) DEFAULT '', `publisher` VARCHAR(100) DEFAULT '', `category` VARCHAR(30) NOT NULL DEFAULT '其他' COMMENT '分类', `original_price` DECIMAL(10,2) DEFAULT 0 COMMENT '原价', `sell_price` DECIMAL(10,2) NOT NULL COMMENT '售价', `book_condition` TINYINT DEFAULT 3 COMMENT '1全新 2九成 3八成 4有笔记划线', `description` TEXT COMMENT '书况描述', `cover` VARCHAR(255) DEFAULT '' COMMENT '封面路径', `status` TINYINT NOT NULL DEFAULT 0, `click_count` INT DEFAULT 0, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, KEY `idx_seller` (`seller_id`), KEY `idx_category` (`category`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='图书表'; -- 购物车表:同一用户同一本书只有一条记录 CREATE TABLE `tb_cart` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `user_id` INT NOT NULL, `book_id` INT NOT NULL, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY `uk_user_book` (`user_id`, `book_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='购物车表'; -- 订单表:price/receiver/phone/address为下单时快照 CREATE TABLE `tb_order` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `order_no` VARCHAR(32) NOT NULL UNIQUE COMMENT '订单号,业务唯一', `buyer_id` INT NOT NULL COMMENT '买家ID', `book_id` INT NOT NULL, `price` DECIMAL(10,2) NOT NULL COMMENT '成交价快照', `receiver` VARCHAR(50) NOT NULL, `phone` VARCHAR(20) NOT NULL, `address` VARCHAR(255) NOT NULL, `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0待付款 1待发货 2待收货 3已完成 4已取消', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `pay_time` DATETIME DEFAULT NULL, KEY `idx_buyer` (`buyer_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表'; -- 评论表:一个用户对一本书只能评一次 CREATE TABLE `tb_comment` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `user_id` INT NOT NULL, `book_id` INT NOT NULL, `content` VARCHAR(500) NOT NULL, `rating` TINYINT DEFAULT 5 COMMENT '1-5星', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY `uk_user_book` (`user_id`, `book_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评论表'; -- 初始管理员,密码为 admin123 的MD5 INSERT INTO `tb_user` (`username`, `password`, `nickname`, `role`) VALUES ('admin', '0192023a7bbd73250516f069df18b500', '管理员', 1);

这段脚本里几个细节要说明:order_no单独设唯一键,是为了防重复下单,生成规则可以用时间戳加用户ID,例如String.valueOf(System.currentTimeMillis()) + userId。book_condition用数字而不是字符串,排序和筛选都方便。点击量click_count先留字段,后面做排行榜直接count就好。

3.3 外键、索引和修改表结构

注意建表SQL里我没有写外键约束,这是刻意的。课设阶段用外键,删除图书时会受订单限制,演示时容易当场翻车。表之间的关联关系在Service层用代码控制,比如删除图书前先查订单状态,这反而能在答辩时讲成“业务层先校验,再执行删除”。数据库层面只保留索引,idx_status、idx_category就是给列表页和筛选页用的。

老师验收前临时加字段,是课设最常用的场景。比如突然要你加“教材/非教材”标记,不用重建表,一条ALTER TABLE就够:

ALTER TABLE `tb_book` ADD COLUMN `course_name` VARCHAR(100) DEFAULT '' COMMENT '所属课程,非教材留空' AFTER `category`;

MySQL表结构修改是高频操作,做完ALTER之后,对应的entity类要同步加字段,JSP里也要加输入框,三层同步改才不出错。这也提醒你,数据库脚本一定要用git管起来,每次改动都提交,别等到答辩前一天发现库和代码对不上。

4. 核心功能实现:登录、发书、下单三块代码一次跑通

4.1 登录与会话控制:Session超时和密码加密

登录是系统的门面,也是最容易被挑刺的模块。课设级别的正确做法是密码MD5后存库,登录时把用户输入的密码MD5再比对。Session在登录成功后写入,setMaxInactiveInterval控制30分钟超时,让“未登录不能下单”这个规则生效。

@WebServlet("/user/login") public class LoginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); String username = req.getParameter("username"); String password = DigestUtils.md5Hex(req.getParameter("password")); UserDao userDao = new UserDao(); User user = userDao.findByUsernameAndPassword(username, password); if (user == null) { req.setAttribute("msg", "用户名或密码错误"); req.getRequestDispatcher("/login.jsp").forward(req, resp); return; } if (user.getStatus() == 1) { req.setAttribute("msg", "账号已被冻结,请联系管理员"); req.getRequestDispatcher("/login.jsp").forward(req, resp); return; } HttpSession session = req.getSession(); session.setAttribute("loginUser", user); session.setMaxInactiveInterval(60 * 30); resp.sendRedirect(req.getContextPath() + "/book/list"); } }

逻辑说明:先查用户再查状态,两个if分支的顺序不要反。密码比对发生在SQL层面,findByUsernameAndPassword同时带用户名和密文两个条件,查不到就是用户名或密码错误,不会把“用户不存在”和“密码错误”分开暴露。最后用sendRedirect而不是forward,是为了避免刷新页面时重复提交表单,这是表单提交后跳转的标准姿势。参数上60*30就是30分钟,课设演示时想省事可以调大,但别取消,Session永不过期会被老师扣分。

配套的登录拦截用Filter来做最干净,注册一个/*的过滤器,放行/login、/register、静态资源,其余路径检查session里有没有loginUser。

@WebFilter("/*") public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; String uri = req.getRequestURI(); if (uri.contains("/login.jsp") || uri.contains("/user/login") || uri.contains("/register.jsp") || uri.contains("/user/register") || uri.endsWith(".css") || uri.endsWith(".js") || uri.endsWith(".jpg")) { chain.doFilter(request, response); return; } if (req.getSession().getAttribute("loginUser") == null) { resp.sendRedirect(req.getContextPath() + "/login.jsp"); return; } chain.doFilter(request, response); } }

这里要强调:Filter放行的静态资源列表要完整,漏了.jpg会导致图书封面全部加载不出来。另一方面,管理员接口记得加白名单之外的权限判断,比如AdminServlet在处理请求前检查loginUser.getRole()==1,否则普通用户直接访问URL就能进后台,这是权限控制的常见漏洞。

4.2 发布图书:图片上传用Servlet 3.0 Part API

发布图书是交易系统的起点,能出书的模块一定要支持封面图上传。Servlet 3.0起的@MultipartConfig配合Part接口,比commons-fileupload少引一个依赖,代码更干净。

@WebServlet("/book/add") @MultipartConfig(maxFileSize = 2 * 1024 * 1024, maxRequestSize = 5 * 1024 * 1024) public class AddBookServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); HttpSession session = req.getSession(); User loginUser = (User) session.getAttribute("loginUser"); if (loginUser == null) { resp.sendRedirect(req.getContextPath() + "/login.jsp"); return; } Book book = new Book(); book.setSellerId(loginUser.getId()); book.setTitle(req.getParameter("title")); book.setAuthor(req.getParameter("author")); book.setCategory(req.getParameter("category")); book.setSellPrice(new BigDecimal(req.getParameter("sellPrice"))); Part cover = req.getPart("cover"); if (cover != null && cover.getSize() > 0) { String fileName = UUID.randomUUID().toString().replace("-", "") + getSuffix(cover.getSubmittedFileName()); String savePath = req.getServletContext().getRealPath("/upload") + File.separator + fileName; cover.write(savePath); book.setCover("upload/" + fileName); } new BookService().addBook(book); resp.sendRedirect(req.getContextPath() + "/book/list?category=" + URLEncoder.encode(book.getCategory(), "UTF-8")); } private String getSuffix(String name) { return name.substring(name.lastIndexOf(".")); } }

逻辑说明:上传文件用UUID重命名,避免两个用户上传同名图片互相覆盖。保存路径用getRealPath拿到部署目录下的upload文件夹,数据库里只存相对路径upload/xxx.jpg,页面上直接用${contextPath}/upload/xxx.jpg拼完整地址。这里两个参数要记住:maxFileSize限制单张图片2MB,maxRequestSize限制整个请求5MB,图书页面上如果还要传多张图,5MB够用;超过限制Tomcat会抛IllegalStateException,前端记得用JS先校验文件类型和大小。

参数方面sellPrice用BigDecimal接收,不要用double,涉及金额的字段用BigDecimal是答辩时的高频考点。前端采集的category如果为空,服务端要兜底存“其他”,这个校验写在Service的addBook方法开头。

4.3 下单扣库存:事务边界必须放在Service层

下单是整个系统最核心的一段,也是最容易拿高分的一段。核心逻辑是:把图书状态从0在售改成1已售,成功后再插入订单记录。两步必须在一个数据库事务里,否则会出现“钱付了书还在卖”或“书卖了订单没生成”的情况。

public class OrderService { public boolean createOrder(Order order, int bookId, int buyerId) { Connection conn = null; PreparedStatement updateBook = null; PreparedStatement insertOrder = null; try { conn = DbUtil.getConnection(); conn.setAutoCommit(false); // 条件更新:只有图书还是“在售”才更新,防止并发超卖 updateBook = conn.prepareStatement( "UPDATE tb_book SET status = 1, click_count = click_count + 0 WHERE id = ? AND status = 0"); updateBook.setInt(1, bookId); int rows = updateBook.executeUpdate(); if (rows == 0) { conn.rollback(); return false; // 图书已被人买走,下单失败 } // 生成订单号并插入订单 order.setOrderNo(String.valueOf(System.currentTimeMillis()) + buyerId); insertOrder = conn.prepareStatement( "INSERT INTO tb_order(order_no, buyer_id, book_id, price, receiver, phone, address, status) " + "VALUES(?,?,?,?,?,?,?,0)"); insertOrder.setString(1, order.getOrderNo()); insertOrder.setInt(2, buyerId); insertOrder.setInt(3, bookId); insertOrder.setBigDecimal(4, order.getPrice()); insertOrder.setString(5, order.getReceiver()); insertOrder.setString(6, order.getPhone()); insertOrder.setString(7, order.getAddress()); insertOrder.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { DbUtil.closeQuietly(updateBook, insertOrder, conn); } } }

这一段讲清楚两层东西。第一层是事务:setAutoCommit(false)之后,update和insert要么都成功commit,要么都失败rollback,这就是数据库事务的ACID里原子性和一致性。第二层是防并发:UPDATE语句里带AND status = 0,在事务提交前会锁住这条记录,第二个用户同时下单时,更新影响行数是0,自然回滚“图书已售”。这两点是答辩时老师最愿意深挖的,能讲明白基本就是高分区间。

订单状态字段建议用数字状态机:0待付款、1待发货、2待收货、3已完成、4已取消,每次变更都检查前置状态,比如只有0状态的订单能改成4。状态转换的校验放在Service里,别放JSP里用超链接随便跳。

4.4 DBUtil和MySQL连接参数:一次配好不折腾

DBUtil是JDBC方案的底座,里面最容易出问题的是连接参数。MySQL 8.x必须用com.mysql.cj.jdbc.Driver,MySQL 5.7用com.mysql.jdbc.Driver,两者别混。

public class DbUtil { private static final String URL = "jdbc:mysql://localhost:3306/book_market" + "?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new RuntimeException("MySQL驱动未找到,请检查WEB-INF/lib", e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void closeQuietly(AutoCloseable... resources) { for (AutoCloseable r : resources) { if (r != null) { try { r.close(); } catch (Exception ignored) { } } } } }

参数的三个重点:characterEncoding=utf8管中文不乱码;serverTimezone=Asia/Shanghai管日期类型不出偏差,MySQL 8.x不加这个参数直接报错;useSSL=false关掉安全连接告警。驱动Jar包要放到WEB-INF/lib下,光在IDEA里Add as Library不够,部署到Tomcat时没有一起打进去,启动就ClassNotFound。

5. 常见问题排查:IDEA运行JavaWeb项目配置与编码的翻车现场

5.1 现象:IDEA配置Tomcat后一访问就是404

这是JavaWeb课设里出现频率最高的翻车现场。启动没有报错,Tomcat也起来了,地址栏一敲localhost:8080就404。

原因基本有三类:一是IDEA里Run Configuration的Deployment没添加Artifact,或者Artifact选成了war而不是war exploded;二是Application context设置成了项目名,访问路径得带前缀;三是WEB-INF下的web.xml版本配置和Servlet注解没扫到。

解决顺序:打开Run/Debug Configurations,Deployment选项卡点加号,选Artifact,类型选“带exploded的那个”,Application context建议直接设成/,这样访问根路径就是首页。然后重新Build Artifact。还不行就检查classes输出目录是否包含编译产物,IDEA里File > Project Structure > Modules,把项目源码目录标记为Sources,输出目录指向target/classes。这一串做完,九成404都能救回来。剩下那一种是玄学,关掉Tomcat进程重启,IDEA的Tomcat配置经常要重启才生效。

5.2 现象:注册用户时中文全部变成问号

数据库里看nickname是“???”而不是中文。这个坑十个人踩九个。

原因分两层:数据库或表的字符集不是utf8mb4;连接字符串没带characterEncoding=utf8。两层缺一不可。

解决:连接URL按4.4那样拼全参数。然后检查MySQL侧,flock的建表SQL已经带了DEFAULT CHARSET=utf8mb4,如果库是早先建的,用ALTER DATABASE和ALTER TABLE改。改完表结构还不行,把现有乱码数据删掉重新插,已写入的问号是恢复不回来的,这也是为什么我建议初始化脚本一次性到位。

5.3 现象:Tomcat启动时ClassNotFound或驱动类不存在

ClassNotFound基本分两种:com.mysql.jdbc.Driver找不到,和javax.servlet的类找不到。

第一种是MySQL驱动Jar没进部署包,IDEA里右键Jar选Add as Library只是编译期可用,运行期还要确保Artifact里包含它。打开Project Structure > Artifacts,输出目录里必须有WEB-INF/lib/mysql-connector-java-x.jar。用Tomcat 9配了Tomcat 10的依赖也会有javax.servlet相关错误,确认你用的还是javax版本,不是jakarta版本。

第二种是项目把servlet-api.jar塞进了WEB-INF/lib,而Tomcat自带这套类库,重复放会导致类加载冲突。解决就是把这个Jar从lib里删掉,Tomcat的lib目录已经有它了。凡是Tomcat自带的Jar,项目里不要重复引。

5.4 现象:图片上传成功,刷新一下就没了

图片上传后当时显示正常,重启Tomcat再访问就404。这是因为Part.write写到了IDEA部署的临时目录里,target目录下的upload在重启时被清掉了。

解决:别往getRealPath指向的部署目录里存文件。常见做法是配置一个项目外的绝对路径,比如D:/bookmarket/upload,数据库存upload/文件名,页面上再拼绝对路径映射。但课设里改映射麻烦,偷懒方案是把upload目录放到src/main/webapp下,IDEA的war exploded部署会同步到target,重启不清。另一个血泪经验:不同机器路径不同,老师验收换电脑时,要么用相对路径方案,要么把upload打包进war,不然图全丢。

5.5 现象:数据库连接超时或连接数耗尽

演示到中途报Connection is null或者Communications link failure。原因多半是DBUtil每次getConnection后没关连接,或者关的顺序错了。

解决:所有DAO方法统一用finally关闭,关闭顺序ResultSet、PreparedStatement、Connection倒着关。用我上面的closeQuietly工具方法,能少写一堆重复代码。连接超时一般是MySQL的wait_timeout默认8小时,长时间不操作旧连接失效,解决方案是每次getConnection直接用新的连接,别自己缓存Connection实例,课设阶段不需要连接池。

6. 让答辩现场从“会做”变成“懂做”:三个加分证据

演示的时候,先把“注册→登录→发布图书→另一账号下单→卖家发货→确认收货→留言评论”这条主线完整走一遍,每个页面停顿两秒,让老师看清页面跳转。这条线走完,功能分基本到手。

第一个加分证据是讲订单表的价格快照。现场指着tb_order里的price字段说:“这里没关联图书表原价,而是下单时单独存了一份快照,因为图书价格后续会改,订单需要用当时的成交价结算。”这句话一出口,老师就知道你理解“冗余”在交易系统里的作用。

第二个加分证据是讲登录拦截和防注入。演示时故意在地址栏输入/login.jsp,说“没登录直接访问会被Filter拦回”,再说“DAO里全程用PreparedStatement,参数化查询,不拼接SQL字符串”。这两点属于JavaWeb安全基础,很多做框架课设的同学被问住,你点出来就是优势。

第三个加分证据是给足“工程感”:数据库脚本放在sql目录、README里写清楚启动步骤、idea运行JavaWeb项目配置写明白Tomcat版本和JDK版本。这些看着不起眼,但老师翻文件时观感完全不同。

这套项目做完,顺手就把Java面试题里几个重点覆盖了:Session生命周期、事务隔离级别、PreparedStatement防SQL注入、BigDecimal精度问题。我去面试遇到答辩问过的问题,回答起来完全不虚。说到最后,我做JavaWeb课设吃过最大的亏就是只写代码不写文档,后来每次都先备份数据库脚本再动手改代码,这是后悔药。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询