尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

JavaWeb电子商城毕业设计实战:从数据库建模到答辩避坑指南

发布时间:2026/9/28 3:07:21

资讯中心
01
ARTICLE

JavaWeb电子商城毕业设计实战:从数据库建模到答辩避坑指南

JavaWeb电子商城毕业设计实战:从数据库建模到答辩避坑指南
简介这是一份面向计算机相关专业毕业设计的JavaWeb电子商城系统源码与数据库备份适合正在完成课程设计或毕业设计、希望以完整项目为参考的高校学生及初级开发者。项目基于JSPServletJSTLELMySQL8JDBC技术栈实现涵盖用户、商品、购物车、分类、订单等典型商城模块可在EclipseTomcat7JDK10环境下配置运行。压缩包共含417个文件大小约9.1MB其中42个Java文件承载Servlet及Dao层业务逻辑20个JSP页面覆盖前台浏览与后台管理界面另含28个HTML页面、12个CSS样式、9个JS脚本、216张商品展示图片以及可直接导入MySQL的SQL数据库脚本。目前已有103人学习下载适合通过完整项目梳理JavaWeb请求响应流程、JDBC数据访问、EL与JSTL视图渲染等关键环节也可作为答辩演示与功能说明的参考资料。读者拿到后可对照源码理解分层结构并迁移到自己的毕设项目中大幅缩短起步时间。1. 毕业设计选型JavaWeb电子商城系统为什么是好上手又难被问倒的方向如果你在资源站里搜过毕设题目大概率见过这个组合基于JavaWeb的电子商城系统源码加数据库。名字听起来平平无奇但在我陪跑过好几届本科毕业设计的经验里这恰恰是完成率最高、答辩容错率最大的选题类型。它不是最亮眼的题目却是踩坑最少的路业务链路完整覆盖Servlet、JSP、JDBC、Session、Filter这些JavaWeb核心考点又不依赖支付网关、消息队列这些外部服务整个系统就一个Tomcat加一个MySQL全链路你一个人能控制住。下面这套落地路径是我实际带项目时整理的从数据库建模到IDEA配置从核心代码到避坑清单照着做就能把一套能讲清楚原理的商城系统跑在自己的机器上。2. 先把数据库定下来六张表、字段边界与一份能直接跑的建表SQL拿到任何一份电子商城源码我都建议先别急着往IDEA里导第一步永远是打开SQL脚本把表结构读懂再启动项目。数据库是这个系统真正的地基后面所有Servlet、Service、DAO都在为这张关系网服务字段少一个写代码时就要绕一大圈。2.1 功能边界毕设商城不需要做成京东做电子商城最容易犯的错是把目标定得太大。支付接口、优惠券、秒杀、商品推荐、物流查询这些功能看起来是商城标配实际上每一个都是坑。支付需要商户号和签名机制没有真实商户号就只能模拟秒杀真正难的不是秒杀本身而是并发控制和防止超卖推荐系统更是另一个领域的内容放进来只会让答辩变成大型翻车现场。我在给学生定需求时通常会划一条线商城只做“最小闭环”用户注册与登录密码不能明文存储商品按分类展示支持分页浏览商品详情展示购物车增删改提交订单模拟支付并更新订单状态用户查看自己的订单列表管理员登录后台处理商品上下架和订单状态这条闭环能走通JavaWeb课程里最核心的知识点已经覆盖大半而且每个功能你都能在答辩时讲清楚来龙去脉。管理员后台不必做重能维护商品状态就行。关于角色权限我习惯在用户表里加一个role字段0是普通用户1是管理员不单独建admin表这样少一张表权限判断逻辑只集中在一个地方。2.2 六张表的关系从用户到订单明细怎么串起来按上面的边界核心表可以压到六张t_user、t_category、t_product、t_cart、t_order、t_order_item它们之间的关系很直观t_product属于一个t_categoryt_cart通过user_id和product_id分别关联用户与商品t_order通过user_id关联用户t_order_item通过order_id关联订单又通过product_id弱关联商品先看用户表。字段包括id、username、password、nickname、phone、role、create_time。password列我建议定成64位char存的是MD5加盐之后的值不是明文。role存放用户角色0是普通用户1是管理员后台登录和前台登录共用一张表。分类表字段很简单id、name、description、sort。sort是排序权重数字小的排前面。分类不要设计成无限级分类一方面那是另一个复杂度级别另一方面毕设答辩里一级分类足够还可以直接在下拉框中展示。商品表字段有id、category_id、name、subtitle、main_image、price、stock、status、create_time。这里有个新手容易漏掉的字段——status用来表示上架和下架。商城一定不能让下架商品出现在用户端后台商品管理中会反复用到这个字段。price用DECIMAL(10,2)不要用float二进制浮点数处理金额会出现精度问题答辩时这是一个可讲的细节。购物车表有四个字段id、user_id、product_id、quantity、create_time。购物车有两种设计一种是临时存Session一种是持久化存数据库。毕设里建议用数据库表这样用户关闭浏览器再回来购物车还在答辩时还能讲一句持久化购物车和Session购物车的区别属于加分项。订单表是整个设计的核心字段为id、order_no、user_id、total_price、status、receiver_name、receiver_phone、receiver_address、create_time、pay_time。status是订单状态机0代表待付款1代表待发货2代表待收货3代表已完成4代表已取消。这个状态字段会贯穿用户端和管理员后台后面下单代码里也靠它驱动流程。最后是订单明细表t_order_item字段为id、order_id、product_id、product_name、product_image、price、quantity、total_price。注意我故意把product_name、product_image、price冗余进来了。订单生成后商品可能改价、改名甚至下架订单里的快照不能变这就是冗余字段的价值。答辩时能主动说清楚这一点评委会觉得你是真的在考虑业务而不是在背表结构。2.3 建表SQL与初始数据执行一遍就能进入开发状态下面这份建表脚本以MySQL 5.7或8.0为例字符集统一用utf8mb4。utf8mb4比utf8多支持了emoji等四字节字符虽然商城未必用得到但作为规范可以直接养成习惯。脚本里我顺手带了一个管理员账号、三个商品分类和几条演示商品导入后不用面对空荡荡的后台。CREATE DATABASE IF NOT EXISTS mall DEFAULT CHARACTER SET utf8mb4; USE mall; DROP TABLE IF EXISTS t_order_item; DROP TABLE IF EXISTS t_order; DROP TABLE IF EXISTS t_cart; DROP TABLE IF EXISTS t_product; DROP TABLE IF EXISTS t_category; DROP TABLE IF EXISTS t_user; CREATE TABLE t_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL COMMENT MD5加盐后的密文, nickname VARCHAR(30) DEFAULT , phone VARCHAR(11) DEFAULT , role TINYINT DEFAULT 0 COMMENT 0普通用户 1管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE InnoDB COMMENT 用户表; CREATE TABLE t_category ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(30) NOT NULL, description VARCHAR(255) DEFAULT , sort INT DEFAULT 0 ) ENGINE InnoDB COMMENT 商品分类表; CREATE TABLE t_product ( id BIGINT PRIMARY KEY AUTO_INCREMENT, category_id BIGINT NOT NULL, name VARCHAR(100) NOT NULL, subtitle VARCHAR(255) DEFAULT , main_image VARCHAR(255) DEFAULT , price DECIMAL(10,2) NOT NULL, stock INT DEFAULT 0, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_category_id (category_id), KEY idx_status (status) ) ENGINE InnoDB COMMENT 商品表; CREATE TABLE t_cart ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, product_id BIGINT NOT NULL, quantity INT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_product (user_id, product_id) ) ENGINE InnoDB COMMENT 购物车表; CREATE TABLE t_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, user_id BIGINT NOT NULL, total_price DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0 COMMENT 0待付款 1待发货 2待收货 3已完成 4已取消, receiver_name VARCHAR(20) NOT NULL, receiver_phone VARCHAR(11) NOT NULL, receiver_address VARCHAR(255) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME DEFAULT NULL, KEY idx_user_id (user_id), KEY idx_status (status) ) ENGINE InnoDB COMMENT 订单表; CREATE TABLE t_order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, product_id BIGINT NOT NULL, product_name VARCHAR(100) NOT NULL, product_image VARCHAR(255) DEFAULT , price DECIMAL(10,2) NOT NULL, quantity INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, KEY idx_order_id (order_id) ) ENGINE InnoDB COMMENT 订单明细表; -- 初始数据admin / admin123 的MD5值 INSERT INTO t_user (username, password, nickname, role) VALUES (admin, 0192023a7bbd73250516f069df18b500, 管理员, 1); INSERT INTO t_category (name, description, sort) VALUES (手机数码, 手机、耳机、充电器等, 1), (电脑办公, 笔记本、键鼠、显示器, 2), (服饰鞋包, 男装、女装、箱包, 3); INSERT INTO t_product (category_id, name, subtitle, main_image, price, stock, status) VALUES (1, 智能手机 X, 6.5英寸 128GB, /static/images/p1.jpg, 1999.00, 100, 1), (1, 蓝牙耳机 Pro, 主动降噪 长续航, /static/images/p2.jpg, 399.00, 200, 1), (2, 轻薄笔记本, 16GB内存 512GB固态, /static/images/p3.jpg, 4999.00, 50, 1), (2, 无线键鼠套装, 2.4G连接 静音按键, /static/images/p4.jpg, 129.00, 300, 1), (3, 休闲双肩包, 防泼水 大容量, /static/images/p5.jpg, 159.00, 150, 1);执行时注意表顺序先建用户和分类再建商品最后建订单相关表避免外键逻辑上的错乱。存储引擎统一用InnoDB原因很直接订单流程需要事务支持MyISAM不支持事务下单时一旦中途报错数据会处于半完成状态。这一步不用额外配置什么MySQL默认引擎就是InnoDB。3. 让源码在本地跑起来项目结构、Maven依赖与IDEA部署配置拿到源码最让人烦躁的不是业务代码而是环境起不来。这一章我按从零到能打开首页的顺序把项目结构、依赖配置和IDEA里Tomcat的运行设置写清楚照着操作二十分钟内应该能看到登录页面。3.1 项目结构三层架构的包怎么划凡是叫“JavaWeb电子商城”的毕设项目最稳的结构还是经典三层架构。Controller层放Servlet负责接收请求参数和转发视图Service层放业务逻辑事务边界在这里控制Dao层放JDBC数据访问。实体类放entity工具类放util过滤器放filter。mall ├── pom.xml └── src/main ├── java/com/example/mall │ ├── controller # Servlet接收参数和跳转 │ ├── service # 业务接口含订单事务 │ ├── dao # JDBC数据访问 │ ├── entity # User/Product/Order等实体类 │ ├── filter # 登录拦截、编码过滤器 │ └── util # DBUtil、MD5Util等 ├── resources │ └── db.properties # 数据库连接配置 └── webapp ├── WEB-INF/web.xml ├── static # css/js/images ├── index.jsp ├── login.jsp ├── register.jsp └── user ├── cart.jsp ├── order_list.jsp └── order_confirm.jsp我见过不少源码把JDBC直接写在Servlet里前几个页面的功能看着很简单做到购物车和订单就开始失控。因为十几个Servlet都要查数据库复制粘贴会让你改一处漏三处最后连自己都找不到数据是从哪里查出来的。所以就算你只是拿源码来改也建议保持三层结构不要为了方便把SQL塞进Servlet。3.2 Maven依赖与连接池参数这里最容易翻车pom.xml 是整个JavaWeb项目依赖的入口常见的坑集中在Servlet版本和MySQL驱动版本上。我用的配置如下project xmlnshttp://maven.apache.org/POM/4.0.0 modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdmall/artifactId version1.0/version packagingwar/packaging properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.9/version /dependency /dependencies build finalNamemall/finalName /build /projectservlet-api和jsp-api的scope必须是provided因为Tomcat本身就带这两套jar不再包装进去否则运行时会遇到jar包冲突。jstl是JSP页面里用c:forEach遍历商品列表必需的。MySQL驱动版本要跟本机MySQL对应5.7用5.1.498.x用8.0.33混用最常见的报错是Unable to load authentication plugin caching_sha2_password。数据库连接池我用Druid比自己在代码里手动Class.forName再DriverManager.getConnection要稳得多尤其在并发访问时会明显看出来差别。连接配置放在resources/db.propertiesjdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/mall?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456 jdbc.initialSize5 jdbc.maxActive20注意driver的值MySQL 8.x 必须写成com.mysql.cj.jdbc.Driver5.x 才是com.mysql.jdbc.Driver写错会在项目启动时直接报ClassNotFoundException。URL里的characterEncodingutf8和serverTimezoneAsia/Shanghai对中文乱码和日期时间显示都有直接影响少一个后面都会莫名其妙的出问题。之后写一个工具类读取这个配置package com.example.mall.util; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; public class DBUtil { private static DataSource dataSource; static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties)) { Properties props new Properties(); props.load(in); dataSource DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }这段代码在整个项目里只有一个静态代码块应用启动时加载一次连接池后面所有DAO都从DBUtil.getConnection()拿连接。这样做的好处是连接复用不会出现每次请求都新建数据库连接的资源浪费。3.3 IDEA运行JavaWeb项目的完整配置从Artifact到Tomcat在IDEA里运行JavaWeb项目和运行普通Java程序不一样必须把项目打包成Web Artifact再部署到Tomcat。完整步骤我按顺序列一遍用IDEA打开项目根目录下的pom.xml选择Open as Project等Maven把依赖下载完。打开File → Project Structure → Project把SDK设为1.8。打开Project Structure → Modules确认Web模块存在src/main/webapp被标记为Web资源目录。打开Project Structure → Artifacts点加号新建一个Web Application: Exploded名字会自动识别成mall:war exploded。打开Run/Debug Configurations添加Tomcat Server → Local在Application Server里选择本机Tomcat安装目录。切到Deployment标签页点加号把mall:war exploded加进去Application Context填/mall。启动前确认MySQL服务已经运行并且第2节的SQL脚本已经执行过。点击运行浏览器访问http://localhost:8080/mall/。这里最大的区别是选择war exploded而不是war。war是把项目打包成压缩包再部署每次改代码都要重新打包exploded是解压目录模式IDEA可以直接把编译后的class和资源增量同步到Tomcat调试体验好得多。4. 核心业务实现登录鉴权、商品分页、购物车与下单事务环境跑通之后下一步就是把业务链路上最关键的几个点写扎实。注册、商品查询这类简单逻辑网上一搜很多这里我挑三个最有讲头也最容易被答辩追问的部分登录拦截、分页查询、订单事务。4.1 登录鉴权用Filter拦截未登录访问如果没有登录拦截用户可以在浏览器地址栏直接输入http://localhost:8080/mall/user/cart.jsp进入购物车页面。短小的JSP项目里最常见的防法是每个页面开头写一段判断Session的Java代码复制粘贴十几遍总有一天会漏一页。我的做法是写一个Filter统一拦截。package com.example.mall.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; WebFilter(/*) public class LoginFilter implements Filter { Override public void init(FilterConfig filterConfig) { } 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(); String ctx req.getContextPath(); // 直接放行登录、注册、静态资源和首页 if (uri.endsWith(.css) || uri.endsWith(.js) || uri.endsWith(.jpg) || uri.endsWith(.png) || uri.contains(/login) || uri.contains(/register) || uri.equals(ctx /index.jsp) || uri.equals(ctx /)) { chain.doFilter(request, response); return; } HttpSession session req.getSession(false); Object user session null ? null : session.getAttribute(user); if (user null) { resp.sendRedirect(ctx /login.jsp); } else { chain.doFilter(request, response); } } Override public void destroy() { } }uri.endsWith(.css)这串判断是必须的否则登录页面的样式表会被拦下来页面渲染出来是纯HTML裸样式这是新手最容易疑惑的现象。req.getSession(false)带false参数表示如果当前没有Session就直接返回null不会顺手创建一个新Session这个细节值得记一下。放行规则这个集合里登录、注册、首页、静态资源都不需要登录其余路径统一校验。4.2 商品分页JDBC查询加上limit偏移量计算商品列表的分页在JavaWeb里是个高频考点核心SQL只有一个LIMIT 偏移量, 每页条数。偏移量不能直接拿页码来算而是(pageNum - 1) * pageSize这是很多人翻到第二页就数据错乱的原因。package com.example.mall.dao; import com.example.mall.entity.Product; import com.example.mall.util.DBUtil; import java.math.BigDecimal; import java.sql.*; import java.util.ArrayList; import java.util.List; public class ProductDao { public ListProduct findPage(int pageNum, int pageSize) throws SQLException { String sql SELECT id, category_id, name, subtitle, main_image, price, stock, status FROM t_product WHERE status 1 ORDER BY create_time DESC LIMIT ?, ?; ListProduct list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, (pageNum - 1) * pageSize); ps.setInt(2, pageSize); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Product p new Product(); p.setId(rs.getLong(id)); p.setName(rs.getString(name)); p.setSubtitle(rs.getString(subtitle)); p.setMainImage(rs.getString(main_image)); p.setPrice(rs.getBigDecimal(price)); p.setStock(rs.getInt(stock)); list.add(p); } } } return list; } public int countOnSale() throws SQLException { String sql SELECT COUNT(*) FROM t_product WHERE status 1; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { rs.next(); return rs.getInt(1); } } }在Servlet里取参数时必须做类型转换和边界判断。用户手动把URL改成?pageNum-1或者?pageNum999是很常见的操作后端不拦住就会出现空白页。int pageNum 1; int pageSize 8; String p req.getParameter(pageNum); if (p ! null !p.isBlank()) { pageNum Integer.parseInt(p); } int total productDao.countOnSale(); int totalPage Math.max(1, (int) Math.ceil(total * 1.0 / pageSize)); if (pageNum 1) pageNum 1; if (pageNum totalPage) pageNum totalPage;total * 1.0 / pageSize这一步是为了把整数除法转成浮点除法再向上取整否则5条数据每页8条时5 / 8在Java整数运算里直接等于0分页控件会显示空白。4.3 下单事务先扣库存再插明细回滚时机不能错订单是商城系统的核心事务场景我说的“事务”不是Spring里的Transactional而是JDBC原生事务这更符合JavaWeb课程设计的技术栈。下单至少涉及四步操作插入订单、插入订单明细、扣减商品库存、清空购物车这四步必须在一个数据库连接里完成要么全部成功要么全部回滚。public boolean createOrder(Long userId, String receiverName, String receiverPhone, String receiverAddress, ListCartItem items) throws SQLException { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 计算总额 BigDecimal total new BigDecimal(0); for (CartItem item : items) { total total.add(item.getPrice().multiply(new BigDecimal(item.getQuantity()))); } // 2. 插入订单订单状态默认0待付款 String orderNo NO System.currentTimeMillis(); String insertOrder INSERT INTO t_order(order_no, user_id, total_price, status, receiver_name, receiver_phone, receiver_address) VALUES(?,?,?,0,?,?,?); long orderId; try (PreparedStatement ps conn.prepareStatement(insertOrder, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, orderNo); ps.setLong(2, userId); ps.setBigDecimal(3, total); ps.setString(4, receiverName); ps.setString(5, receiverPhone); ps.setString(6, receiverAddress); ps.executeUpdate(); try (ResultSet rs ps.getGeneratedKeys()) { rs.next(); orderId rs.getLong(1); } } // 3. 插入订单明细同时扣库存 String insertItem INSERT INTO t_order_item(order_id, product_id, product_name, product_image, price, quantity, total_price) VALUES(?,?,?,?,?,?,?); String deductStock UPDATE t_product SET stock stock - ? WHERE id ? AND stock ?; for (CartItem item : items) { try (PreparedStatement ps conn.prepareStatement(insertItem)) { ps.setLong(1, orderId); ps.setLong(2, item.getProductId()); ps.setString(3, item.getProductName()); ps.setString(4, item.getProductImage()); ps.setBigDecimal(5, item.getPrice()); ps.setInt(6, item.getQuantity()); ps.setBigDecimal(7, item.getPrice().multiply(new BigDecimal(item.getQuantity()))); ps.executeUpdate(); } try (PreparedStatement ps conn.prepareStatement(deductStock)) { ps.setInt(1, item.getQuantity()); ps.setLong(2, item.getProductId()); ps.setInt(3, item.getQuantity()); int rows ps.executeUpdate(); if (rows 0) { conn.rollback(); // 库存不足回滚 return false; } } } // 4. 清空购物车 String clearCart DELETE FROM t_cart WHERE user_id ?; try (PreparedStatement ps conn.prepareStatement(clearCart)) { ps.setLong(1, userId); ps.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { if (conn ! null) { conn.rollback(); } throw e; } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } }这段代码里最值得讲的是库存扣减的SQL写法UPDATE t_product SET stock stock - ? WHERE id ? AND stock ?。在同一个事务里插入明细和扣减库存的顺序不会影响最终一致性但把库存校验放进UPDATE语句的条件里可以让数据库帮我们判断“库存够不够”同时避免高并发下的超卖问题。conn.rollback()之后的return false是给上层Servlet一个提示告诉用户“库存不足”而不是直接抛500。事务完成后finally块里要把setAutoCommit(true)恢复然后把连接归还给Druid连接池。连接池里的连接是不关闭的如果漏掉重置自动提交下一次这个连接被借出时事务状态可能是错的这种问题不出现则已一出现就是查半天都找不到原因的玄学故障。5. JavaWeb电子商城实战避坑乱码、连接池假死、404与分页错乱这一章是我自己在这类项目上翻过车之后沉淀下来的清单。每一条都是真实场景写出来是希望你能在答辩前把这些雷扫干净。5.1 中文乱码页面、请求、数据库三层各管各的编码现象页面上的商品名称显示成???或者注册用户名存进数据库后变成乱码有些页面第一次打开正常提交表单后跳出来的全是问号。原因乱码是三层编码不一致共同造成的。第一层是JSP页面的pageEncoding第二层是HTTP请求和响应的字符集第三层是数据库连接URL里的字符集参数。只改其中一处问题不会彻底解决。解决JSP文件头部统一写% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %在web.xml里配置一个全局过滤器把请求和响应的编码都设为UTF-8filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping如果项目没用Spring就自己写一个实现Filter的类在doFilter里调用request.setCharacterEncoding(UTF-8)。同时确认MySQL连接URL里带了characterEncodingutf8。三层全对了乱码基本不会出现。5.2 数据库连接不释放系统运行五分钟就开始假死现象项目刚启动时一切正常登录、商品列表都能用过了几分钟点击任何页面都转圈控制台开始刷出wait millis 10000 active 20 max 20之类的报错。原因这不是Tomcat的问题是连接池被耗尽。代码里conn获取了但没有关闭或者关闭动作写在非finally的位置一旦中间有异常抛出连接就永久不归还连接池。Druid连接池默认最大20个连接满20个后新的请求只能等待等待超时就把页面卡死。解决所有使用Connection的地方统一使用JDK 7以上的try-with-resources写法让连接、Statement、ResultSet都自动关闭。如果是指定的参考资料里写了手写的finally块不要把conn.close()放在业务代码行后面要放在finally块里。这条是血泪经验排查起来很费时间但从写第一步就接入连接池的自动关闭能帮你少熬一个通宵。5.3 部署后一直404Artifact没打全控制台不报错现象Tomcat正常启动日志没有任何异常访问http://localhost:8080/mall/却始终404。原因IDEA的Artifact配置不完整。最常见的情况是WEB-INF/lib下没有包含Maven依赖或者src/main/webapp没有被标记为Web资源目录项目部署到Tomcat下面后是一个空壳。解决回到Project Structure → Artifacts检查mall:war exploded的输出结构里是否包含WEB-INF/lib、WEB-INF/classes、static、JSP文件。如果lib为空把Available Elements里所有jar包右键选Put into WEB-INF/lib。改完以后删除之前的Artifact重新建一次再启动。访问路径也要确认上下文是/mall在Deployment标签里能看到红色路径提示。5.4 分页翻到后面数据错乱limit边界和类型转换现象商品列表第一页正常第二页也能显示点第三页或最后一页的时候页面空白或者数据重复。原因分页SQL里的偏移量算错这是最常见的。偏移量应该等于(pageNum - 1) * pageSize如果直接把pageNum传给LIMIT第一页没问题第二页就会从第2条数据开始取第三页从第3条开始每页只有8条的话越到后面错得越明显。另一个隐蔽原因是前端把页码作为字符串拼接pageNum做了字符串相加比如2 1变成了21。解决Servlet里接收参数后先把字符串Integer.parseInt转成int再做(pageNum - 1) * pageSize运算。同时加上边界判断pageNum 1时置为1pageNum totalPage时置为当前最后一页。这样即使用户手动改URL参数也不会看到空白页。5.5 改完代码不生效Tomcat跑的还是旧逻辑现象修改了Servlet代码重新运行页面行为跟改之前一样甚至删除了一整个JSP文件刷新页面还能打开。原因这个遇到过的人很多。IDEA里Tomcat默认配置是On Update action: Restart Server你改了代码但没有触碰Tomcat自身它不会主动重新编译部署。更隐蔽的是IDEA的自动编译没有开启改完Java文件直接运行用的还是旧的class文件。解决在Tomcat的Run Configuration里把On Update action和On frame deactivation都设为Update classes and resources这样改动后只要IDE失去焦点就会自动编译部署。每次改完代码按CtrlF9强制重新编译然后看Tomcat控制台有没有重新部署的日志。如果改了JSP页面不生效还要检查Tomcat的JSP缓存路径必要时直接Redeploy。6. 答辩前最后一步把演示流程和数据准备到万无一失功能做完了数据库跑通了离拿高分还差最后一个动作把演示当作一场小型的上线演练来做。我见过太多功能完好但演示翻车的场景结论是数据准备和流程排练比写代码更影响最终结果。6.1 十到十五分钟的演示脚本不要到了现场才临场发挥。我习惯把演示流程固定成一条不用思考的链路先用管理员账号登录后台确认能看到商品列表和订单管理入口退出登录注册一个新用户走一遍注册流程用新用户登录浏览商品分类翻两页分页列表把两三个商品加入购物车进购物车修改数量提交订单在模拟支付页确认金额回到后台把订单状态从待付款改成待发货切回用户端查看订单列表确认状态同步。这条链路走下来正好覆盖注册、搜索、分页、购物车、订单、权限几个核心模块。答辩现场最怕的不是功能出错而是你花了两分钟在页面上找入口评委的注意力一分散后面想拉回来就难了。6.2 数据准备与细节加分项演示用的数据库不要拿初始数据硬撑。建议手动在t_product里插入二三十条商品数据把分类覆盖全每类至少五六条。图片要真实存在main_image字段指向static/images/下的本地文件不要引用外链图片答辩教室可能没网外链图一加载失败整个页面就变难看了。几个加分的小细节成本不高但效果很好密码加密工具类里不要只做一次MD5加个固定盐答辩时可以讲“即使数据库泄露明文密码也无法直接还原”商品列表按create_time倒序展示这是一个默认用户习惯在订单相关的表上主动建立索引并且在答辩时说出idx_user_id、idx_order_id这两条索引的作用很多学生写完了都不知道数据库有索引这回事你能说出来就是差异。我自己的习惯是答辩前三天把数据库整个删掉重新执行一遍初始化脚本然后按演示链路完整走三遍。第一遍验证功能第二遍练手速第三遍看着表故意把操作做错一次比如库存不足直接下单确认系统的提示正常。这个习惯帮我在答辩现场躲过一次数据残留导致的商品列表为空也让我在评理委员追问事务时能从容地说出回滚时机。项目本身不难难的是把每个环节的细节都掌握在自己手里。希望这篇笔记能帮你少踩几个坑顺利把毕业设计这一关过了。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。