ARTICLE DETAIL

资讯详情

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

JavaWeb网上购物书城课设:数据库设计到答辩避坑指南

JavaWeb网上购物书城课设:数据库设计到答辩避坑指南 简介一份JavaWeb网上购物书城课程设计/期末大作业完整资料包面向计算机相关专业在校学生、教师及企业员工尤其适合需要完成课设、毕设或项目初期演示的入门与进阶学习者。压缩包共140个文件以44个JSP页面与12个Java类构成核心业务逻辑涵盖29个class编译文件、14个JS脚本、12个HTML页面、8个CSS样式表及8个XML配置另含数据库SQL脚本、jar依赖与说明文档整体仅2.28MB结构清晰便于导入运行。资料包含商城前台购物、后台商品管理、订单处理等模块代码均测试通过功能稳定可在其基础上进行二次修改实现更多功能并附有运行相关说明适合边学边练。目前已有391人学习下载是快速完成课程设计或期末大作业的高质量参考。1. 网上购物书城的 JavaWeb 数据库课设先想清楚「能跑」和「能答辩」的差距网上购物书城是 JavaWeb 数据库课程设计里出现频率最高的选题几乎每个班都有几个人做它。这个选题的典型构成是MySQL 数据库配合 SQL 脚本后端用 Servlet JSP JDBC 实现登录注册、图书分页、购物车、生成订单这些增删改查最后配一份说明文档和一个能在 IDEA 里直接跑起来的项目。很多人以为这份课设的重点是「让代码跑起来」但答辩现场真正拉开分数差距的是数据库表结构设计、订单事务处理和 SQL 注入这几个点——这些恰好是课程考核大纲里「数据库设计能力」的落点。本篇按我本人做课设代跑、帮同学救火的经验把从建库到能答辩的完整路径拆开讲。2. 数据库设计先行书城项目的表结构、关系与 SQL 脚本落地在写第一行 Java 代码之前先把数据库的五张表设计清楚用户表 user、图书表 book、订单表 orders、订单明细表 order_item、购物车表 cart。三张主表承载业务实体两张关联表解决「用户 - 订单 - 商品」之间的多对多关系。ER 关系上一个 user 对应多个 orders一个 orders 对应多个 order_itemorder_item 关联 book 记录下单时的商品快照。这里关键词是「快照」课设里最容易翻车的地方就是订单明细直接关联图书表图书价格一变历史订单金额跟着变。正确做法是把下单时的书名、单价、数量冗余存进 order_item哪怕 book 表后续改价历史订单依然保持不可变。购物车表的设计则要看粒度。选择持久化购物车每次加购写库的优势是用户换浏览器购物车不丢缺点是代码量多一套增删改查选择 Session 购物车则少两张 DAO但浏览器一关购物车就没了。课程设计按「好答辩」的标准持久化购物车更有讨论价值——老师追问「购物车数据要不要入库」时你能说出取舍逻辑这就不是白做的功能。2.1 用户、图书、订单三张核心表字段、类型、默认值我建表时习惯把用户表、图书表、订单表的字段一次定到位。因为项目中期补字段要改 DAO 和 JSP还要处理已有数据的默认值成本远高于建表时多犹豫五分钟。MySQL 里ALTER TABLE加字段看着简单实际涉及存量数据的填充策略课设阶段改起来非常痛苦。用户表 user 字段不宜多但也不该只留 id 和 username。一份能上台面的课设至少要包含 id、username、password、nickname、phone、create_time。password 不要存明文至少用 MD5 摘要存储登录校验也走 MD5 后比对。字段长度上username 给 VARCHAR(32)password 给 VARCHAR(64)——MD5 摘要固定 32 位但给到 64 位是给未来切 BCrypt 留余地这个细节在答辩讲「安全性考虑」时能提一句是加分项。图书表 book 的金额字段必须用DECIMAL(10,2)Java 侧用 BigDecimal 接收。float 和 double 存在二进制浮点误差金额做累计求和时容易出现 0.1 0.2 不等于 0.3 的诡异结果。这个知识点可以直接决定答辩时「价格为什么不用 double」这个问题能不能答上来。订单表 orders 和明细表 order_item 是整套业务的核心orders 要记清楚 whouser_id、whencreate_time、how muchtotal_amount、what statestatus。status 用 TINYINT 存状态码1 待支付、2 已支付、3 已发货、4 已完成、5 已取消。只存码值显示层做映射不要在数据库字段里存「待支付」这种中文。下面是可以直接执行的建表 SQL按依赖顺序从上到下执行即可。-- 创建书城数据库指定 utf8mb4 字符集 CREATE DATABASE IF NOT EXISTS bookstore DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE bookstore; -- 用户表 CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT COMMENT 主键, username VARCHAR(32) NOT NULL COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT 密码(MD5摘要), nickname VARCHAR(50) DEFAULT NULL COMMENT 昵称, phone VARCHAR(20) DEFAULT NULL COMMENT 手机号, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;这段 SQL 有两个容易忽略的点。一是DEFAULT CHARSET必须指定 utf8mb4 而不是 utf8——utf8mb4 才是 MySQL 8 下完整支持中文和特殊字符的字符集很多课设项目中文乱码的根子就是建库时用了默认 Latin1 或者旧版 utf8。二是 username 加 UNIQUE KEY注册功能里要捕获这个唯一索引抛出的重复键异常并提示用户而不是等到查询时才发现数据不对。继续建订单、明细和图书表-- 图书表 CREATE TABLE book ( id INT NOT NULL AUTO_INCREMENT COMMENT 主键, title VARCHAR(100) NOT NULL COMMENT 书名, author VARCHAR(50) DEFAULT NULL COMMENT 作者, publisher VARCHAR(50) DEFAULT NULL COMMENT 出版社, price DECIMAL(10,2) NOT NULL COMMENT 定价, stock INT NOT NULL DEFAULT 0 COMMENT 库存, cover VARCHAR(255) DEFAULT NULL COMMENT 封面图片路径, description VARCHAR(500) DEFAULT NULL COMMENT 简介, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 上架时间, PRIMARY KEY (id), KEY idx_title (title) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图书表; -- 订单表 CREATE TABLE orders ( id INT NOT NULL AUTO_INCREMENT COMMENT 订单主键, order_no VARCHAR(32) NOT NULL COMMENT 订单号, user_id INT NOT NULL COMMENT 下单用户ID, total_amount DECIMAL(10,2) NOT NULL COMMENT 订单总金额, status TINYINT NOT NULL DEFAULT 1 COMMENT 1待支付 2已支付 3已发货 4已完成 5已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 下单时间, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES user (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; -- 订单明细表 CREATE TABLE order_item ( id INT NOT NULL AUTO_INCREMENT COMMENT 主键, order_id INT NOT NULL COMMENT 所属订单ID, book_id INT NOT NULL COMMENT 图书ID, book_title VARCHAR(100) NOT NULL COMMENT 下单时书名快照, price DECIMAL(10,2) NOT NULL COMMENT 下单时单价快照, quantity INT NOT NULL DEFAULT 1 COMMENT 购买数量, subtotal DECIMAL(10,2) NOT NULL COMMENT 小计金额, PRIMARY KEY (id), KEY idx_order_id (order_id), CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES orders (id), CONSTRAINT fk_item_book FOREIGN KEY (book_id) REFERENCES book (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;order_item 里冗余 book_title 和 price 这一处答辩时是实打实的加分项。老师追问「明细表为什么不通过 book_id 去 join 查询书名和价格」回答思路是下单之后图书可能改名、下架、调价如果只存 book_id历史订单渲染会失真冗余快照字段是为了保证订单的不可变性。orders 表的 order_no 用唯一索引而非主键主键保留自增 id。订单号生成规则建议用时间戳 用户 ID 随机数形如20250108120000_3_482不直接用主键当订单号的原因是防止枚举遍历——订单号被猜出来以后可以顺着接口爬数据。字段类型的选择可以整理成一张参数表放进文档说明里很加分字段类型适用字段选型理由DECIMAL(10,2)price、total_amount、subtotal避免浮点误差精度可控TINYINTorders.status状态码值化扩展新状态不用改结构VARCHAR(64)password兼容 MD5(32) 与 BCrypt(60) 两种摘要长度DATETIMEcreate_time配合 DEFAULT CURRENT_TIMESTAMP时间交给数据库INT UNIQUEorder_no、username业务唯一键与物理主键分离防止遍历2.2 购物车表、模拟数据与 SQL 脚本的组织方式购物车表 cart 我一般按三个字段加一个时间戳设计id、user_id、book_id、quantity、add_time。user_id 和 book_id 加联合唯一索引保证同一个用户对同一本图书只有一条记录。加购时先尝试INSERT ... ON DUPLICATE KEY UPDATE quantity quantity 1逻辑上比「先查再决定是 Insert 还是 Update」少一次 SQL 往返。-- 购物车表 CREATE TABLE cart ( id INT NOT NULL AUTO_INCREMENT COMMENT 主键, user_id INT NOT NULL COMMENT 用户ID, book_id INT NOT NULL COMMENT 图书ID, quantity INT NOT NULL DEFAULT 1 COMMENT 数量, add_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 加入时间, PRIMARY KEY (id), UNIQUE KEY uk_user_book (user_id, book_id), CONSTRAINT fk_cart_user FOREIGN KEY (user_id) REFERENCES user (id), CONSTRAINT fk_cart_book FOREIGN KEY (book_id) REFERENCES book (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT购物车表;执行顺序要先建 user 和 book再建 orders 和 cart最后建 order_item。外键约束要求被引用的表先存在顺序错了会报「无法添加外键约束」。解决方式是建表脚本从上到下按依赖顺序排好全选执行一次性过。模拟数据是课设里容易被低估的一环。数据库只有 3 条图书记录分页功能根本展示不出来。我一般一次插 20 到 30 条价格拉开梯度、书名覆盖不同分类前端列表页和分页导航才有效果。插入脚本节选如下-- 插入模拟数据节选 INSERT INTO book (title, author, publisher, price, stock, description) VALUES (Java编程思想, Bruce Eckel, 机械工业出版社, 108.00, 50, Java入门经典适合做课程设计参考), (MySQL高性能优化, Baron Schwartz, 电子工业出版社, 99.00, 30, 数据库性能优化细节), (深入理解计算机系统, Randal E.Bryant, 机械工业出版社, 139.00, 20, CSAPP中文版), (数据结构与算法分析, Mark Allen Weiss, 人民邮电出版社, 89.50, 40, 数据结构和算法教材), (计算机网络自顶向下方法, James F. Kurose, 机械工业出版社, 79.00, 35, 网络原理教材);插入后执行一条SELECT COUNT(*) FROM book;确认行数。答辩现场最尴尬的瞬间是演示分页时第二页是空的——不是代码 bug是数据不够这种问题最冤。提交时 SQL 脚本要包含三样建库语句、建表语句、模拟数据。我一般拆成bookstore_schema.sql和bookstore_data.sql两个文件schema 放 DDLdata 放 INSERT文档说明里写清楚「先执行 schema 再执行 data」老师拿着脚本能在自己机器上完整还原数据库环境。3. 后端落地Servlet JSP JDBC 分层不碰框架的骨架方案JavaWeb 数据库课设的主流技术路线是 Servlet JSP JDBC MySQL。不选 SSM 的原因很现实课设周期短Spring 和 MyBatis 的配置对新手来说是一个黑匣子报错以后排查链路太长而且课程大纲这一阶段要考核的是 Servlet 规范和数据库访问能力不是框架熟练度。Servlet 写起来虽然原始但每一行代码都能对应到 HTTP 请求的处理流程上答辩被追问「请求怎么从浏览器走到数据库」时可以直接照着代码指路径。项目结构按包分层com.bookstore.util放 JDBC 工具类com.bookstore.dao放数据访问层com.bookstore.service放业务层com.bookstore.servlet放控制器com.bookstore.entity放实体类。实体类对应第二章的五张表用java.math.BigDecimal接收 DECIMAL 字段用java.time.LocalDateTime接收 DATETIME 字段时间类型在 JDBC 里取出来是 Timestamp要做一次转换。3.1 JDBC 连接工具类从 DriverManager 到连接池数据库连接不能每次请求都 new 一个 Connection。连接建立的开销大无限制创建会拖垮 MySQL但课设阶段上 HikariCP 之类的连接池又显得重。折中做法是一个静态工具类统一管理连接获取和释放代码量小导师也能看懂。下面是完整的 DBUtilpackage com.bookstore.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/bookstore?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; static { try { // MySQL 8.x 必须用 com.mysql.cj.jdbc.Driver老版本才是 com.mysql.jdbc.Driver Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(MySQL驱动加载失败请检查jar包是否已导入); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(AutoCloseable... resources) { for (AutoCloseable r : resources) { if (r ! null) { try { r.close(); } catch (Exception ignored) { } } } } }URL 上的几个参数是 MySQL 8 的标配useSSLfalse去掉 SSL 握手告警serverTimezoneAsia/Shanghai解决时区报错allowPublicKeyRetrievaltrue解决 MySQL 8caching_sha2_password认证模式下客户端首次连接报 public key retrieval 错误。三个参数少一个项目第一次跑起来很可能在连接阶段报错而且错得莫名其妙。驱动类名也要注意MySQL 8 之前是com.mysql.jdbc.Driver8 之后是com.mysql.cj.jdbc.Driver用错直接 ClassNotFoundException。密码明文写在工具类里安全上是硬伤但课程设计场景下这是最常见做法。答辩被问「生产环境账号密码怎么办」准备一套回答生产环境会把配置外置到 properties 文件并对密码加密DBUtil 只负责读取课设里写常量是为了保证解压即跑的代码内聚性。把这个问题答圆了反而是加分项。3.2 用户登录与会话管理PreparedStatement 和 Filter 的统一校验登录模块是数据库「查」操作的典型应用。登录校验核心流程三步接收参数、按用户名查库、比对密码摘要。注意不要用「先查出所有用户再在 Java 里遍历比对」这种写法数据库索引存在的意义就是让WHERE username ?快速定位全表扫描进内存再过滤是性能反面教材。// UserDAO.java 核心方法 public User findByUsername(String username) throws SQLException { String sql SELECT id, username, password, nickname, phone, create_time FROM user WHERE username ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User u new User(); u.setId(rs.getInt(id)); u.setUsername(rs.getString(username)); u.setPassword(rs.getString(password)); u.setNickname(rs.getString(nickname)); u.setPhone(rs.getString(phone)); u.setCreateTime(rs.getTimestamp(create_time).toLocalDateTime()); return u; } } } return null; }这段代码的三个关键点PreparedStatement 占位符防 SQL 注入、try-with-resources 自动关闭连接、ResultSet 用完即走。答辩时可以展开讲「为什么不用 Statement 拼接 SQL」——用户输入admin OR 11会绕过密码校验PreparedStatement 让参数与 SQL 模板分离这是 SQL 注入防线的最基础形态。登录成功后把用户对象放进 Session。所有需要登录才能访问的页面统一在 Filter 里拦截不在每个 Servlet 里重复写判断。Filter 的核心逻辑极短// LoginFilter.java public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; Object user req.getSession().getAttribute(loginUser); if (user null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); }Filter 在 web.xml 里配置时拦截/cart/*、/order/*这样的路径登录页和图书列表页要放行。初学最容易犯的配置错误是把拦截路径写成/*导致登录页自己也被拦进入「无法登录」的死循环。另一个经验映射顺序决定执行顺序公开接口要么不匹配拦截规则要么在 Filter 内部放行。登录密码比对一个注意点数据库存的是 MD5 摘要登录时要把用户输入转成 MD5 再比对不要在 SQL 里直接setString(2, rawPassword)。MD5 工具类就是 JDK 自带 MessageDigest 包一层几十行代码但加上这个环节文档说明里「安全设计」一节就有内容写了。3.3 购物车与订单一个事务里完成库存扣减和订单生成购物车加购是简单的 Insert 或 Update但「下单」这个动作必须保证原子性扣减库存、生成订单、生成订单明细、清空购物车任何一个失败都不能让数据处于半成品状态。事务是这张表代码的核心。// OrderService.java 核心方法 public void checkout(int userId) throws SQLException { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交开启事务 // 1. 查询购物车 String cartSql SELECT c.book_id, c.quantity, b.price FROM cart c JOIN book b ON c.book_id b.id WHERE c.user_id ?; // 执行查询得到 cartItems // 2. 生成订单拿到自增ID String orderSql INSERT INTO orders (order_no, user_id, total_amount, status) VALUES (?, ?, ?, 1); // 生成订单号并执行通过 getGeneratedKeys() 取自增 id // 3. 扣减库存带库存校验条件的 UPDATE String deductSql UPDATE book SET stock stock - ? WHERE id ? AND stock ?; // 影响行数为 0 说明库存不足抛异常触发回滚 // 4. 批量插入订单明细 // 5. 清空购物车 DELETE FROM cart WHERE user_id ? conn.commit(); } catch (SQLException e) { if (conn ! null) conn.rollback(); throw e; } finally { if (conn ! null) { conn.setAutoCommit(true); // 恢复自动提交避免连接池污染 conn.close(); } } }扣库存的 SQL 值得单独展开UPDATE book SET stock stock - ? WHERE id ? AND stock ?。加AND stock ?保证库存不为负更新影响行数为 0 就抛异常回滚。这种写法本质是乐观锁的变体比「先 SELECT stock 再判断再 UPDATE」安全得多能防两个请求同时读到相同库存导致超卖。事务边界要清楚Connection 从一个地方拿事务在 Service 方法开头打开finally 块里必须把setAutoCommit(true)恢复。不恢复的后果很神秘——连接回到池里后自动提交仍是关闭的下一个请求拿到这个连接后续所有 SQL 都堆在内存里不落库表现是「第一句 SQL 没生效全在排队」这是 JDBC 事务里最容易翻车的地方之一。4. JSP 页面与前后端联调列表、分页、购物车的页面链路JSP 页面不求美观但要完整覆盖增删改查的展示入口。典型页面组合是登录/注册页、图书列表页、图书详情页、购物车页、结算页、订单列表页加分项是后台图书管理页。页面跳转逻辑要一眼能看懂列表页点详情进详情页详情页点加入购物车回列表或进购物车购物车点结算生成订单跳订单列表。JSP 里用 JSTL EL 表达式取 Session 和请求域数据禁止写% %Java 代码片段——内联代码既难读又难维护IDEA 对内联 Java 的补全也弱答辩现场临时改需求时排查成本高得离谱。编码过滤器是前后端联调的基石JSP 页面和 Servlet 之间所有中文传递都依赖它。我之前见过太多课设项目把request.setCharacterEncoding写在每个 Servlet 里漏一个就乱码一处而用 Filter 统一处理是标准姿势// EncodingFilter.java public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding(UTF-8); response.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); chain.doFilter(request, response); }注意要在chain.doFilter之前设置编码否则请求体已被读取再设置就来不及了。这个 Filter 拦截所有路径放在 web.xml 过滤器列表的第一位。4.1 分页查询的 SQL 写法与页码联动图书列表页的分页是数据库课程设计必考项。SQL 写SELECT * FROM book ORDER BY id LIMIT offset, size页面显示「第 x / y 页共 n 条」前后翻页。分页 SQL 必须带 ORDER BY不带排序的 LIMIT 返回顺序不稳定翻页时会出现「上一页看过的书在下一页又出现」的诡异现象。-- 第 2 页每页 6 条offset (2-1) * 6 6 SELECT id, title, author, publisher, price, stock FROM book ORDER BY id LIMIT 6, 6;配套的查询总数 SQL 是SELECT COUNT(*) FROM book两个查询结果组合成一个 PageBean 对象放进请求域。页码参数从 JSP 的request.getParameter(page)获取必须做默认值处理参数为空或非法时默认第 1 页用户手动输入?page99999时要能兜底为空列表而不是报错。页码范围在后端做边界控制当前页小于 1 就显示第 1 页大于总页数就显示最后一页。放在后端比前端 JS 更可靠因为用户可以直接输入 URL 绕过前端。分页导航栏的 JSP 写法用一个循环生成页码链接当前页高亮相邻页可点。要传多条件查询参数时比如带关键词搜索的列表页直接在 JSP 端用${param.keyword}把查询词拼回链接不要用 JS 去读表单值再拼接减少一个在答辩现场出 bug 的环节。4.2 购物车页面的数量修改与总价计算购物车页面展示用户加购的图书、数量、单价和合计金额。数量修改按钮对应一个CartUpdateServlet参数为 bookId 和新的 quantity。修改后由浏览器重新请求购物车列表页所有价格计算都在服务端完成不要在 JSP 里用 JS 计算后直接提交单价——总价必须以数据库里的价格为准前端算出来的只做展示。!-- cart.jsp 购物车列表核心片段 -- c:forEach varitem items${cartList} tr td${item.bookTitle}/td td${item.price}/td td a hrefcart/update?bookId${item.bookId}quantity${item.quantity - 1}-/a span${item.quantity}/span a hrefcart/update?bookId${item.bookId}quantity${item.quantity 1}/a /td td${item.price * item.quantity}/td /tr /c:forEach这段 JSP 里有两个可以拿出来讲的设计决策。一是price * quantity的精度JSTL 里的乘法走 EL 表达式引擎精度表现与 Java 里 BigDecimal 乘法并不完全一致稳妥做法是把总价在 Service 层用 BigDecimal 算好放进一个名为CartVO的视图对象里再渲染而不是依赖页面表达式计算。二是加减按钮用链接发起 GET 请求这在课设中够用但生产环境应该用 POST — 因为 GET 会被爬虫或浏览器预读触发导致购物车数量被意外修改。答辩时主动讲「我知道这里用 POST 更规范但课设为了演示直观先用 GET」远比被老师追问时卡壳好得多。下单成功后页面跳转到订单列表订单列表按create_time DESC排序倒序展示最新订单在最前面。订单详情和取消功能在课设里属于锦上添花但一旦做了取消订单就必须保证只做状态更新而不是物理删除——这个衔接点在第五章会展开讲。5. 避坑/常见问题排查环境配置、中文乱码、SQL 注入与订单数据不一致这个章节收集做 JavaWeb 书城课设时最高频的踩坑记录按「现象 → 原因 → 解决」顺序写。每一条都是帮同学调试课设时反复出现的真实问题不是从文档里抄来的理论。5.1 现象一IDEA 运行 JavaWeb 项目配置导致 Tomcat 404 或端口占用现象Tomcat 能启动但打开http://localhost:8080/bookstore/login.jsp是 404或启动直接报Port 8080 required by Tomcat v9.0 Server is already in use。原因分两种。404 的第一嫌疑是部署名不对IDEA 的 Artifact 配置里 Application context 设置成了/或与项目名不一致URL 少敲或多敲了上下文路径。端口占用的原因更简单——本机已经有一个 Tomcat 在跑或之前 IDEA 里启动的 Tomcat 进程没有正常终止残留进程锁住了 8080。解决端口占用在 Windows 上先跑netstat -ano | findstr :8080找到 PID然后taskkill /PID pid /F结束后重新启动。404 则打开 Run Configuration 的 Deployment 页检查 Application context确保浏览器访问路径和部署上下文完全匹配。还有一个经常被忽略的点JDK 版本和 Tomcat 版本不兼容也会在启动时报莫名其妙错误Tomcat 9 配 JDK 8/11 是稳妥组合Tomcat 10 的包名从javax.servlet改成jakarta.servlet旧代码直接搬过去会报 ClassNotFoundException。5.2 现象二数据库中文变问号或乱码现象从 JSP 页面提交的中文书名或用户昵称存入 MySQL 后变成???或读出到页面显示乱码。原因链有三层JDBC 连接 URL 缺characterEncodingutf8mb4、数据库或表默认字符集不是 utf8mb4、JSP 页面编码不是 UTF-8。任何一层断层中文就会在写入或读出时失真。最常见的是建库时没指定字符集MySQL 默认 Latin1 无法存储中文。解决三层同时检查。URL 按第二章代码补上useUnicodetruecharacterEncodingutf8mb4建库语句里写DEFAULT CHARACTER SET utf8mb4IDEA 和 Tomcat 的 VM 参数追加-Dfile.encodingUTF-8。如果数据已经乱码不要试图在乱码数据上补救直接重建库跑一遍 schema 脚本最快。血泪经验在已经乱码的数据库上调字符集改完原来乱码的数据依然乱码因为写入时已经丢失了信息。5.3 现象三登录 SQL 语法错误或拼接用户名绕过密码登录现象登录时输入用户名admin OR 11和任意密码直接登录成功或者输入含单引号的用户名登录报You have an error in your SQL syntax。原因DAO 层用了字符串拼接 SQL形如SELECT * FROM user WHERE username username AND password password 。输入的admin OR 11把原 SQL 拼成了恒真条件直接绕过密码校验。这是最典型的 SQL 注入漏洞也是数据库课程设计必须演示出来的知识点之一。解决把拼接改成占位符WHERE username ? AND password ?所有参数一律用 PreparedStatement 的 setString/setInt 绑定。密码比对前先转 MD5 摘要保证数据库里不是明文。课程设计里这一步做好答辩被问「安全方面做了什么」就有拿得出手的实锤回答还能顺手补一句「分页查询里的 offset 参数也走了类型转换没有直接拼进 SQL」。5.4 现象四删除订单报外键约束失败现象执行DELETE FROM orders WHERE id 10时报Cannot delete or update a parent row: a foreign key constraint fails或删除图书时提示被订单明细引用。原因order_item 表对 orders 和 book 都有外键约束删除父表记录时子表仍有引用记录。按业务规则历史订单不能物理删除只能逻辑取消状态置为 5所以这个问题的本质是业务允许删除但表结构不允许。解决把「删除订单」功能改成「取消订单」SQL 写UPDATE orders SET status 5 WHERE id ?而不是 DELETE。若实在需要物理删除程序里必须先删订单明细再删订单两条 DAO 操作放在同一个事务里。外键的作用是保护数据完整性这个坑不靠代码绕过靠业务规则绕开。被问到「为什么订单不能删」时回答「订单涉及库存和财务业务上只能作废不能删除」就是标准答案。5.5 现象五下单成功但库存没减或库存变成负数现象演示下单流程时订单生成了但图书列表里的库存数字不变或者快速两次下单同一本书库存变成负数。原因下单逻辑没有用事务插入订单成功但扣库存失败后未回滚扣库存的 SQL 没有带stock ?条件并发时两个事务都读到旧库存都把剩余库存减成负数。库存为负是数据库层面最明显的脏数据标志。解决把下单动作包在单连接事务里参考第三章 3.3 的代码setAutoCommit(false)后依次执行扣库存、插入订单、插入订单明细、清空购物车全部成功才commit()任一步失败则rollback()。扣库存 SQL 加AND stock ?条件影响行数为 0 就抛异常回滚。这个点能解释清楚并发场景下的数据一致性期末加分效果非常明显。6. 演示与验证把课设从「能跑」优化到「能答辩」的三个动作课设代码写完能从登录跑到下单发货和答辩现场老师愿意打高分中间还差三个动作预置演示数据、规划演示路径、测试边界输入。第一个动作是预置演示账号。在 data 脚本末尾插入一个明确的演示账号用户名demo密码123456提前算好 MD5 摘要写进 INSERT。答辩时不要当场上机注册新账号——注册流程容易在唯一索引、密码强度校验、网络延迟这几个环节出问题现场注册浪费时间。用预置账号登录把时间留给图书浏览和下单链路。演示账号用过要在文档说明里显著标注老师拿到项目后直接按文档操作进入系统体验会好很多。第二个动作是规划演示路径。一条完整的路径是登录 → 浏览图书列表 → 翻页一次 → 进入详情页 → 加入购物车 → 购物车改数量 → 结算下单 → 查看订单列表 → 取消订单。每演示一步心里都要过一遍数据库里对应执行了哪条 SQL。老师问「这个操作改了哪些表」你能立刻回答这是拿分的核心节点。演示时先讲页面再讲 SQL不要把时间耗在代码逐行解释上代码留给老师抽查。第三个动作是测试边界输入。图书搜索框输入不存在的书名、购物车数量改为 0、分页点击最后一页、重复提交下单按钮。这些边界场景在答辩前自己跑一遍老师在现场随手操作时你就不会翻车。下单按钮的重复提交问题尤其要注意——最快的兜底方案是点击结算后前端禁用按钮并对order_no做唯一索引约束重复提交报错能明确提示而不是生成两笔相同订单。我自己的习惯是答辩前一天把整个流程录一遍视频按演示顺序走完把每个 SQL 操作对应的页面截图放进文档说明。这个习惯帮我躲过很多现场意外比如临时断网连不上数据库或者答辩教室的浏览器缓存了旧页面。一份课设的文档说明不只是写给别人看的它就是你的演示脚本和后悔药。希望帮到你。本文还有配套的精品资源点击获取
返回列表