ARTICLE DETAIL

资讯详情

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

网上购物书城JavaWeb课程设计实战:从JSP到事务处理完整指南

网上购物书城JavaWeb课程设计实战:从JSP到事务处理完整指南 简介这是一份网上购物书城课程设计与期末大作业项目基于JavaWeb数据库技术面向计算机相关专业的在校学生、教师及企业学习者可用于课程设计、期末答辩、毕业设计演示或入门进阶练习。压缩包总计一百四十个文件核心包含四十四份JSP页面文件、十二份Java源代码文件、十四份JavaScript脚本、八份CSS样式表以及数据库SQL脚本另有编译后的class文件、XML配置与说明文档整体体量约两点二八兆字节目录安排清晰便于按模块查阅。项目已有三百九十一人浏览学习。所有代码均经过测试并成功运行作者注明答辩评审平均分达九十六分从前台页面交互到后台订单管理功能完整既能作为实战项目研读也可在此基础上修改扩展。下载后打开说明文档即可快速上手如遇运行问题还提供远程教学支持适合不同基础的学生使用。1. 网上购物书城课程设计为什么这套 JavaWeb 大作业值得认真做一遍期末倒数第二周拿到 JavaWeb 数据库课程设计/期末大作业的题目——网上购物书城要求交源代码、文档说明和数据库 sql 文件。这类题目每年都有网上改名就能买到但答辩现场最容易露馅老师问一句“购物车数据到底存哪儿了”答不上来的不在少数。实际上书城是数据库课程设计里性价比最高的题目它把 JDBC、Servlet、JSP、SQL 和事务全部串在一起做完这一套增删改查、多表关联、订单事务这些基本功基本就通了。这篇笔记按我带课程设计的路线写从建工程讲到答辩演示新手照着敲能跑通熟手也能拿去当复习提纲。书城项目有一个天然优势业务场景大家都熟悉不需要额外理解领域知识。用户注册登录、浏览图书、加购物车、下单支付每一步都能对应到一张表和几条 SQL评审老师一眼就能看出你的表设计合不合理、代码是不是自己写的。对刚学完 JavaWeb 和 MySQL 的同学来说这是把零散知识点收拢成完整项目的绝佳机会对想补基础的在职者它也是一套能复用的传统 JavaWeb 代码骨架。接下来直接从技术选型和建工程开始一步步把整个项目搭出来。2. 工程骨架与技术选型课程设计别一上来就追 Spring Boot2.1 为什么 JSP Servlet MySQL 才是课设的标准答案很多同学一看到“网上购物书城”就开始想 Spring Boot MyBatis Plus Vue 全家桶理由是现在公司都这么用。这个想法本身没错但放在数据库课程设计这个场景里大概率是给自己挖坑。课程设计的评审重点从来不是你用了多新的框架而是你能不能讲清楚“一条数据从浏览器到数据库再从数据库回到浏览器的完整路径”。用 Servlet 写请求处理、用 JDBC 操作数据库、用 JSP 渲染页面每一步都肉眼可见老师问到哪一层你都能接上话。这就像学开车先用手动挡虽然换挡麻烦但离合和变速箱原理全在手上。我一般建议的选型组合是IDEA Maven Tomcat 9.x JSP/Servlet MySQL 8.0 Druid 连接池 JSTL。这个组合和黑马 JavaWeb 笔记的路线基本一致资料多、报错好搜、网上踩坑案例一抓一大把。相比之下Spring Boot 把 Tomcat 内嵌、自动配置全做了代码是少写很多但答辩时如果被问到“你的数据库连接是怎么管理的”你会发现除了一个 application.yml 什么都说不出来。这不是说 Spring Boot 不好而是它不适合用来证明你掌握了 JavaWeb 和数据库的基本功。课程设计只有几周时间把精力花在 Servlet 和 JDBC 上比花在框架配置上划算得多。2.2 IDEA 新建 Maven 工程pom.xml 依赖和 Tomcat 部署配置先说工程怎么建。用 IDEA 新建一个 Maven 工程选 webapp 骨架GroupId 随便填个 com.bookstoreArtifactId 就叫 bookstore。建完之后第一件事是补 pom.xml里面要加 Servlet、JSP、MySQL 驱动、Druid 连接池和 JSTL 这几组依赖dependencies !-- Servlet APITomcat 9 对应 javax.servlet 4.x编译时需要 -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency !-- JSP API同样由 Tomcat 提供所以 scope 也是 provided -- dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency !-- MySQL 驱动8.0 系列对应 com.mysql.cj.jdbc.Driver -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency !-- Druid 数据库连接池阿里巴巴出品 -- dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.20/version /dependency !-- JSTLJSP 里做循环和判断标签库 -- dependency groupIdjstl/groupId artifactIdjstl/artifactId version1.2/version /dependency /dependencies这里的 scope 是理解重点。servlet-api 和 jsp-api 的 scope 一定要是 provided因为 Tomcat 自身已经带了一套实现如果打成 provided 之外的范围部署时可能出现类冲突导致启动直接报java.lang.LinkageError。mysql-connector-java 的版本必须和 MySQL 服务端版本兼容8.0 的驱动不能配 5.7 的旧连接参数反过来也一样这个坑后面单独讲。依赖加完还需要把工程打成 war 包而不是 jar 包。在 pom.xml 里加一行packagingwar/packaging然后配置 IDEA 的 Tomcat在 Run Configurations 里新增一个 Tomcat Server LocalDeployment 选项卡里把 bookstore:war exploded 添加进去Application context 填/bookstore。这个路径值很关键后面所有 Servlet 的 WebServlet 注解里的路径以及 JSP 里写链接时的 contextPath都要和它对应。配好之后启动 Tomcat浏览器访问http://localhost:8080/bookstore/能出 404 页面都算正常说明 Tomcat 已经跑起来了只是还没有可访问的资源。提示war exploded 和 war 的区别在于前者是解压后的目录改完 JSP 不需要重启 Tomcat刷新页面就能看到效果课程设计阶段强烈建议用 exploded 模式能省下大量重启时间。3. 数据库设计与连接池建表 SQL、外键策略和 Druid 参数一次说清3.1 四张核心表的建表脚本字段、外键与明细快照书城项目不管功能怎么加核心表就四张用户表、图书表、订单表、订单明细表。购物车表要不要建取决于你的购物车方案选 Session 还是数据库第四章会专门说这里先不建。设计表结构时我见过太多同学把所有字段塞进一张大表里美其名曰“方便查询”结果订单和商品耦合在一起改一个书名要连带改几十条订单记录。正确的做法是把数据按业务实体拆开用外键建立关联订单明细里冗余保存商品名称和价格快照。下面是四张表的完整建表脚本直接放到数据库里执行即可-- 用户表存登录账号和基础资料 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名唯一约束, password VARCHAR(64) NOT NULL COMMENT MD5加盐后的密码, nickname VARCHAR(50) COMMENT 昵称可为空, phone VARCHAR(20) COMMENT 手机号, address VARCHAR(255) COMMENT 收货地址, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 图书表书城的商品主表 CREATE TABLE t_book ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 图书主键, name VARCHAR(100) NOT NULL COMMENT 书名, author VARCHAR(50) COMMENT 作者, price DECIMAL(10,2) NOT NULL COMMENT 单价精确到分, stock INT NOT NULL DEFAULT 0 COMMENT 库存下单时扣减, cover_url VARCHAR(255) COMMENT 封面图片路径, category VARCHAR(50) COMMENT 分类如 文学/计算机/历史, sales INT DEFAULT 0 COMMENT 累计销量用于排行榜展示 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图书表; -- 订单表一个订单属于一个用户 CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 订单主键, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 订单号对外展示用, user_id INT NOT NULL COMMENT 下单用户ID, total_amount DECIMAL(10,2) NOT NULL COMMENT 订单总金额, status TINYINT DEFAULT 0 COMMENT 0待支付 1已支付 2已发货 3已完成 4已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 下单时间, CONSTRAINT fk_order_user FOREIGN KEY (user_id) REFERENCES t_user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; -- 订单明细表一个订单包含多本图书 CREATE TABLE t_order_item ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 明细主键, order_id INT NOT NULL COMMENT 所属订单ID, book_id INT NOT NULL COMMENT 图书ID, book_name VARCHAR(100) COMMENT 商品名称快照, price DECIMAL(10,2) COMMENT 下单时单价快照, quantity INT NOT NULL COMMENT 购买数量, CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES t_order(id), CONSTRAINT fk_item_book FOREIGN KEY (book_id) REFERENCES t_book(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;脚本里有几个设计决策值得在文档说明里重点写。第一订单明细表里的 book_name 和 price 是冗余字段这叫“快照”因为图书表里的书名和价格可能以后会改但历史订单必须保留下单那一刻的信息等你做到订单查询功能就明白这个设计有多重要了。第二所有表都用 InnoDB 引擎因为要支持外键约束和事务MyISAM 不支持事务下单扣库存时会出大问题。第三字符集用 utf8mb4 而不是 utf8因为 utf8 在 MySQL 里最多存 3 字节遇到 emoji 表情或生僻字会导致插入失败这个坑在用户填收货地址时很容易触发。图书表还需要提前插入一些测试数据方便后面调试和答辩演示。常见做法是写一个 insert 脚本插入十本左右不同分类的图书价格设成带小数的值比如 49.90库存不要全是 100故意留一两本库存只有 2 的这样演示超卖处理时有素材。3.2 Druid 连接池配置与 DBUtil 封装参数表和连接释放习惯JDBC 原生写法是每操作一次数据库就DriverManager.getConnection()一次课程设计里数据量小这么写也能跑但我强烈建议用 Druid 连接池。原因很简单连接池里的连接是复用的不用每次请求都经历 TCP 握手和 MySQL 认证性能差距在演示现场不明显但答辩时“为什么用连接池”这个问题几乎必问你得能说出个一二三来。在 resources 目录下新建 druid.properties 文件driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/bookstore?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai usernameroot password你的数据库密码 initialSize5 maxActive20 maxWait3000 minIdle5几个参数值得展开讲。initialSize 是启动时创建的初始连接数设 5 就够不要设太大课程设计这种并发量根本用不到。maxActive 是最大活跃连接数20 是一个稳妥值超过这个数再来的请求就会排队等 maxWait 毫秒maxWait 设 3000 表示等 3 秒拿不到连接就抛异常防止请求无限阻塞把 Tomcat 线程池拖垮。url 里的characterEncodingutf8和serverTimezoneAsia/Shanghai这两个参数必须写前者保证中文不乱码后者解决高版本 MySQL 驱动对时区的严格校验很多人连接报错都是因为少写了 serverTimezone。连接池配好后封装一个 DBUtil 工具类这是整个项目的基础设施public class DBUtil { private static DruidDataSource dataSource; static { try { // 从 classpath 加载 druid.properties InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(druid.properties); Properties props new Properties(); props.load(in); // 用工厂方法创建数据源 dataSource (DruidDataSource) DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(数据库连接池初始化失败 e.getMessage()); } } // 对外提供获取连接的方法 public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }这段代码的逻辑很直白类加载时读配置文件、创建数据源之后所有 DAO 层代码通过DBUtil.getConnection()拿连接。注意静态代码块里如果初始化失败直接抛ExceptionInInitializerError这样 Tomcat 启动时如果连不上数据库应用会立刻报错而不是等第一个请求进来才挂能帮你早发现问题。还有一个必须养成的习惯connection 用完一定要关。连接池里的 close 不是真关闭而是把连接归还给池子所以即使有连接池你依然要在 finally 块里关 connection。我见过太多课设代码只关 statement 不关 connection运行几小时后连接池耗尽Tomcat 直接无响应这种问题在答辩现场是毁灭性的第五章排查部分会细讲。另外如果项目中要写 SQL 分析类的查询记得给表加上合适的索引比如 t_order 表的 user_id 和 t_order_item 表的 order_id 都有外键索引不需要额外创建但如果要按图书分类查询建议给 t_book 表的 category 加一个普通索引用 EXPLAIN 看一下 SQL 执行计划就知道有没有走全表扫描。4. 核心功能代码走读登录、购物车与订单事务的落地写法4.1 注册登录MD5 加盐不是炫技是答辩安全问题的挡箭牌用户模块是几乎所有 JavaWeb 课设的第一步也是答辩时最容易暴露代码质量问题的地方。最简单的写法是把密码明文存数据库登录时查一遍比对但这在答辩时一旦被问“密码安全怎么考虑”基本就沉默了。所以我建议做两层注册时对密码做 MD5 加盐登录时把 Session 作为会话状态的载体。先看注册的密码处理代码// 生成 8 位随机盐值 String salt UUID.randomUUID().toString().replace(-, ).substring(0, 8); // 盐和密码拼接后做 MD5 String hashed md5(salt password); // 数据库中 password 字段存 盐:摘要 格式 String dbPassword salt : hashed;登录的校验逻辑就要把盐拆出来重新计算// 按用户名查出记录后从数据库取的 dbPassword 形如 a1b2c3d4:5f6e7... String[] parts dbPassword.split(:); String salt parts[0]; String storedHash parts[1]; String inputHash md5(salt inputPassword); if (storedHash.equals(inputHash)) { // 密码匹配把用户对象放进 Session session.setAttribute(user, user); response.sendRedirect(request.getContextPath() /book/list); } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); }为什么加盐而不是直接 MD5因为同样的密码不加盐会得到完全相同的摘要攻击者用彩虹表一比对就能反推出密码加盐之后即使两个人密码相同盐不同摘要就不同。Salt 不需要保密就明文存在数据库里它的作用是让每个用户的哈希结果都不一样。md5 方法可以自己用MessageDigest写一个工具方法网上实现很多就不贴了但要注意转十六进制字符串时用String.format(%02x, b)补零否则生成的摘要可能长度不齐。Session 这块有几个细节要注意。登录成功后设置session.setAttribute(user, user)后续的购物车、下单接口都从这里取当前用户。Session 默认超时时间是 30 分钟可以在 web.xml 里显式配置这个配置要写进文档说明里因为它是会话管理的一部分。退出登录就调用session.invalidate()不要只移除 user 属性。所有需要登录才能访问的 Servlet比如下单、查看订单都应该在 doGet 或 doPost 开头检查 Session 里有没有 user没有就重定向到登录页。4.2 购物车放 Session 还是数据库表先想清楚访客场景购物车是书城项目的核心交互也是很多课设代码最混乱的地方。常见做法有两种一是把购物车数据放 Session 里用 Map图书ID, 数量 存二是在数据库建一张购物车表。我一般建议课程设计用 Session 方案因为购物车是临时数据不需要永久保存用户关掉浏览器购物车清空完全合理而且不用登录就能往购物车加书这符合真实电商的体验。如果一上来就建购物车表反而要处理“游客购物车”和“登录购物车合并”的复杂逻辑完全超出了课程设计的范围。Session 购物车的核心代码非常简单SuppressWarnings(unchecked) MapInteger, Integer cart (MapInteger, Integer) session.getAttribute(cart); if (cart null) { // 第一次访问初始化购物车 cart new HashMap(); session.setAttribute(cart, cart); } // merge 方法如果图书已在购物车则数量1否则放入数量1 cart.merge(bookId, 1, Integer::sum);这段代码的逻辑是一行merge解决了“新增”和“数量累加”两种情况不需要先判断是否包含再分别处理。map 的 key 是图书 idvalue 是数量购物车页面展示时再拿这些 id 去查 t_book 表补全书名、价格等信息。这里要注意从 Session 里取出来的 Map 是共享引用直接修改它就等于修改了 Session 里的数据不需要再 setAttribute 一遍但为了可读性建议还是写清楚。购物车页 JSP 里展示时如果购物车为空要显示“购物车是空的”否则循环遍历 Map 逐行展示图片、书名、单价、数量和一个“删除”按钮。删除操作就是map.remove(bookId)然后重定向回购物车页。结算按钮链接到/order/confirm这个 Servlet。为什么不用数据库表答辩被问到就回答购物车的生命周期短、访问频繁、数据量大但是临时性的放数据库会增加每次操作的 IO 开销Session 内存操作更快也天然隔离不同用户的数据。这个回答不仅能挡住追问还显得你思考过性能问题比“课设简单所以没做”高明得多。4.3 下单事务扣库存、写订单、写明细的原子性与超卖处理下单是整个项目里技术含量最高的功能也是事务处理的最佳落点。一次下单要做三件事扣减图书库存、插入订单主表、插入订单明细表。这三步必须要么全成功、要么全失败否则会出现订单生成了但库存没扣或者库存扣了订单没生成的情况。这就是数据库事务的应用场景。代码里通过conn.setAutoCommit(false)关闭自动提交手动控制提交和回滚public void createOrder(int userId, MapInteger, Integer cart) throws Exception { Connection conn DBUtil.getConnection(); PreparedStatement ps null; try { conn.setAutoCommit(false); // 关闭自动提交开启事务 // 第一步扣减库存WHERE 条件里带上 stock ? 防止超卖 String updateStock UPDATE t_book SET stock stock - ? WHERE id ? AND stock ?; ps conn.prepareStatement(updateStock); for (Map.EntryInteger, Integer entry : cart.entrySet()) { ps.setInt(1, entry.getValue()); ps.setInt(2, entry.getKey()); ps.setInt(3, entry.getValue()); int rows ps.executeUpdate(); if (rows 0) { // 受影响行数为0说明库存不足回滚整个事务 throw new RuntimeException(图书ID entry.getKey() 库存不足); } } // 第二步插入订单主表生成订单号 String insertOrder INSERT INTO t_order (order_no, user_id, total_amount, status) VALUES (?, ?, ?, 0); ps conn.prepareStatement(insertOrder, Statement.RETURN_GENERATED_KEYS); String orderNo BK System.currentTimeMillis(); // 时间戳生成订单号 ps.setString(1, orderNo); ps.setInt(2, userId); ps.setBigDecimal(3, calculateTotal(cart)); ps.executeUpdate(); ResultSet rs ps.getGeneratedKeys(); int orderId 0; if (rs.next()) { orderId rs.getInt(1); // 拿到自增主键 } // 第三步写订单明细每条明细关联订单ID // 这里遍历 cart 同时要查出当前价格做快照省略了查询代码 // 每本图书执行一次 INSERT INTO t_order_item (order_id, book_id, book_name, price, quantity) VALUES (?, ?, ?, ?, ?) conn.commit(); // 三部分都成功提交事务 } catch (Exception e) { conn.rollback(); // 任何一步异常回滚全部修改 throw new RuntimeException(下单失败已回滚 e.getMessage(), e); } finally { // 恢复自动提交并归还连接这两步不能用 return 跳过 conn.setAutoCommit(true); conn.close(); } }这个方法的逻辑值得逐段理解。第一步扣库存时WHERE stock ?是防超卖的关键在高并发下两个请求同时读到库存为 1如果不用这个条件两个都能 UPDATE 成功库存会变成负数加了条件后第二个 UPDATE 受影响行为 0直接抛异常回滚。这就是乐观锁思路用一条带条件的 UPDATE 代替 SELECT UPDATE 两步操作避免并发问题。第二步插入订单主表时用RETURN_GENERATED_KEYS拿自增 id目的是把它作为第三步明细表的外键。第三步需要遍历购物车每本书查一次当前价格写入快照字段因为商品价格可能在支付前被管理员修改快照能保证订单金额和用户下单时看到的一致。事务里的所有 SQL 必须用同一个 Connection 对象这是最容易被忽视的点。如果在 DAO 层每个方法都自己getConnection()下单时三个方法各用各的连接事务就是无效的——每个连接独立自动提交根本不存在一起回滚的可能。所以下单这个业务要么把 DAO 方法设计成接受 Connection 参数要么直接在 Service 层写 SQL。课程设计阶段我推荐后者代码集中、逻辑清晰、答辩好解释不必为了设计模式硬拆层。注意finally 里的conn.setAutoCommit(true)是很多同学容易漏的。如果连接归还给连接池时还处于手动提交状态下一次从池子里取出这个连接的人会莫名其妙地遇到“事务不生效”或“修改没提交”的问题非常隐蔽。5. 常见问题排查书城项目最容易翻车的 5 个坑5.1 中文乱码JSP、Tomcat、连接串三层都要管现象页面上用户注册时填的昵称存进数据库变成问号“???”或者从数据库查出来的书名在页面上显示乱码。原因乱码本质是编码不一致。Tomcat 默认对 POST 请求体的解析编码是 ISO-8859-1而 MySQL 连接串里如果没写 characterEncodingutf8驱动会用服务端默认编码两边对不上就全乱了。这个坑有三个层面只改一处没用。解决第一JSP 页面顶部写上% page contentTypetext/html;charsetUTF-8 languagejava %并且页面本身的文件编码要存成 UTF-8。第二给所有 POST 请求加一个字符编码过滤器在 web.xml 里配置filter和filter-mapping把request.setCharacterEncoding(UTF-8)在过滤器中统一执行。第三JDBC 连接串必须带useUnicodetruecharacterEncodingutf8第四章节已经写过了。这三处都做到中文乱码基本绝迹。5.2 SQL 注入万能密码不是段子是真能绕过登录现象在登录页的用户名输入框里输入 OR 11密码随便输直接登录成功。或者更隐蔽的在搜索框输入 OR 11 --把整个图书列表带条件全查出来。原因登录 SQL 是拼接出来的比如SELECT * FROM t_user WHERE username username AND password password 输入的内容变成了 SQL 的一部分。 OR 11让整个 WHERE 条件恒为真等于把密码校验跳过了。这是 JavaWeb 项目里最经典的漏洞也是数据库课程设计文档里必须讨论的内容。解决全部改用 PreparedStatement 的占位符方式。PreparedStatement 会把参数当作数据而不是 SQL 代码处理输入的内容即使包含特殊字符也只会被当作字符串值。前面的下单代码已经演示了这种写法。如果项目里的查询还有用字符串拼接的一律改掉。这一条属于答辩时老师必查的安全项改完一定要在文档的测试部分记录验证过程。5.3 外键约束删不掉商品先看子表有没有引用现象想删掉一本图书后台管理模块返回“删除失败”控制台报Cannot delete or update a parent row: a foreign key constraint fails。原因t_order_item 表里有外键引用 t_book 表的 id只要有人下过包含这本书的订单订单明细里就有记录指向它直接删主表记录会被外键约束拦截。这个报错提示实际上在保护数据完整性不是系统坏了。解决先查引用再决定。如果只是想在后台管理里下架商品正确做法不是在 t_book 表加一个 is_delete 字段做逻辑删除而是先写一条关联查询看看有没有历史订单引用。如果确认要物理删除就先删除 t_order_item 里的相关记录再删 t_book 的记录两步要放在同一个事务里。最常见的问题是开发阶段反复测试下单订单明细表积累了一堆数据导致后续所有删除操作都失败。解决方法是把测试订单清理干净保留几本有订单的商品用来演示外键效果就够。5.4 连接没释放演示现场第二单就 500 的常见原因现象项目刚启动时一切正常点几个页面后逐渐变慢最后所有页面都报 500查看 Tomcat 日志是Connection is not available, request timed out after 3000ms。原因Druid 的 maxActive 设为 20意味着最多同时借出 20 个连接。每次请求从连接池拿一个连接如果在 finally 里没有 close连接永远不会归还。前面说了连接池的 close 是把连接还回池子不是真关。连接池耗尽后后面的请求全部阻塞等待等满 3 秒直接超时。这个报错是 Druid 默认的等待超时策略生效了说明你的连接泄漏已经积累到临界值了。解决全局搜索所有DBUtil.getConnection()的位置检查每个 Connection 是否都在 finally 中关闭。IDE 的 Find Usages 功能可以列全。另外有一个排查技巧在 druid.properties 里临时加一行testWhileIdletrue和timeBetweenEvictionRunsMillis60000这样连接池会定期检测并丢弃坏的连接缓解症状但治标不治本。最终还是要靠代码层面保证关闭这是 JavaWeb 开发最基础也最容易犯的错。5.5 图片 404 与 Session 失效部署路径和编码习惯的连带问题现象图书封面上传后页面显示裂图控制台报 404或者用户登录后过几分钟再操作就提示未登录需要重新登录。原因两个问题在一起容易误判。图片 404 多半是因为 JSP 里写的图片地址是/images/xxx.jpg这个绝对路径会直接到 Tomcat 根目录找而你的应用部署在/bookstore路径下正确的地址应该是/bookstore/images/xxx.jpg。Session 失效则可能有两层原因一是 web.xml 里没配 session-timeout默认 30 分钟后过期如果用户操作间隔长就会掉线二是项目做了重启IDEA 里改了 Java 代码触发 Tomcat 自动 restartSession 数据存在内存里重启必然丢失。解决图片地址一律用相对路径或者通过c:url标签生成带 contextPath 的完整路径这样部署路径变了也不会挂。Session 超时时间在 web.xml 配成 60 分钟避免演示时中途掉线。至于 Tomcat 重启导致 Session 丢失这是传统 JavaWeb 的内存会话局限课程设计阶段不用解决但要在文档说明里写一句“Session 保存在服务端内存中重启后失效”这反而是加分项说明你知道它的边界。6. 答辩前的最后准备数据验证、注入演示与备份收尾功能跑通之后剩下几天时间不用再写新功能了把精力花在三件事上数据验证、安全演示和备份收尾。这三件事会直接影响答辩印象分。数据验证方面准备一条“有说服力”的 SQL 查询比如图书销量排行它涉及 t_order_item 和 t_book 两张表的关联查询加分组聚合是课程设计评分标准里“综合查询能力”的直接证据SELECT b.name AS 书名, SUM(oi.quantity) AS 总销量 FROM t_order_item oi JOIN t_book b ON oi.book_id b.id GROUP BY b.id, b.name ORDER BY 总销量 DESC LIMIT 5;演示时先把这条 SQL 在 Navicat 里跑出结果再把同样的逻辑在页面上对应的排行榜功能点开给老师看两边数据对得上比任何口头解释都有说服力。安全演示则是主动展示防御意识。在登录页输入 OR 11万能密码页面应该提示“用户名或密码错误”而不是登录成功。这一步 10 秒钟就能操作但很多同学的代码压根没做过这个验证被老师当场试出来才意识到漏洞。另外用 MySQL 命令导出数据库备份生成一份完整的 SQL 文件这正好对应了题目要求的“数据库 sql”交付物mysqldump -u root -p bookstore bookstore_backup.sql至此源代码、文档说明、数据库 sql 三样交付物都齐了。文档说明里别写空话把表结构设计理由、关键接口流程、遇到的问题和解决办法按要求列清楚老师看重的就是你排查问题的过程记录。我当年课设就是没在 finally 里关连接演示现场第一单成功第二单直接 500那种汗颜感一次就够了。把这些提前验证一遍坑都是可以踩在别人前面的。希望帮到你。本文还有配套的精品资源点击获取
返回列表