JavaWeb项目实战:从Servlet到JDBC的完整业务链路
发布时间:2026/10/7 16:58:20 锦皓数字建站

简介本资源是一套完整的JavaWeb仿小米在线商城ShoppingMall实战项目面向Java初学者及Web开发入门者聚焦ServletJSPMySQL技术栈的综合应用训练帮助学习者掌握电商类系统的核心功能开发流程。压缩包共2个文件含1个主项目源码ZIP含HTML/CSS/JavaScript/jQuery前端页面、Java Servlet后端逻辑、JDBC数据库连接及完整MVC结构代码和1个SQL数据库脚本shop.sql便于一键导入与本地部署总大小39.94MB。已有28049人学习下载热度持续走高。读者可直接运行项目体验商品浏览、详情查看、购物车添加与价格实时计算等典型业务流程代码结构清晰、注释充分涵盖前后端交互、会话管理、数据库增删改查等关键知识点是巩固JavaWeb基础、理解分层架构与项目落地的优质实践素材。1. 为什么一个“仿小米商城”的JavaWeb项目成了校招面试官最常翻的压箱底考题不是因为它有多炫酷——页面甚至用的是JSP原生HTMLBootstrap 3也不是因为用了什么高并发黑科技——它跑在Tomcat 8.5上MySQL 5.7单库单表连Redis都还没加。真正让它被反复拎出来考的是它像一把解剖刀把HTTP请求怎么从浏览器飞进Servlet、Session怎么管用户登录态、商品数据怎么从MySQL查出来再塞进JSP页面、订单怎么跨表事务提交、甚至JDBC连接池怎么配才不拖垮服务器——全摊开在阳光下一环扣一环错一环就404或空指针。这个项目不追求“上线即百万DAU”它要解决的是一个刚学完Servlet、JSP、JDBC的Java初学者如何把零散知识点焊成一条能跑通的业务流水线。你能在IDEA里跑起来说明你真懂了“Web容器”不是个虚词你改得了购物车增删逻辑才算摸到了MVC分层的边你调通了MySQL中文乱码和Tomcat日志报错才敢说“我部署过JavaWeb”。它不是玩具是照妖镜——照出你到底会不会写真实业务里的if-else、try-catch、SQL拼接和跳转路径。如果你正卡在“学完框架但不会搭项目”“能看懂代码但不敢动一行”那这个ShoppingMall就是你该亲手敲一遍的临界点。2. 从零启动用IDEATomcatMySQL搭起能跑通的最小骨架2.1 创建标准JavaWeb模块别跳过web.xml它还在扛大梁很多新手直接用Maven Archetype生成webapp结果发现web.xml是空的连servlet都没配——这会导致Tomcat根本不知道该把/login请求交给哪个类处理。真实企业项目里web.xml虽已非强制但它是理解请求生命周期的锚点。我们手动建一个标准结构!-- src/main/webapp/WEB-INF/web.xml -- ?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 !-- 配置首页 -- welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list !-- 配置LoginServlet -- servlet servlet-nameLoginServlet/servlet-name servlet-classcom.mall.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login/url-pattern /servlet-mapping !-- 配置商品列表Servlet -- servlet servlet-nameProductListServlet/servlet-name servlet-classcom.mall.servlet.ProductListServlet/servlet-class /servlet servlet-mapping servlet-nameProductListServlet/servlet-name url-pattern/product/list/url-pattern /servlet-mapping /web-app注意version4.0对应Servlet 4.0规范Tomcat 9但本项目用Tomcat 8.5所以必须用version3.1否则启动报错web.xml is invalid。这是IDEA新建项目时容易忽略的细节——它默认选最新版而你的Tomcat版本没跟上。2.2 在IDEA里正确关联Tomcat不是点“Add Configuration”就完事很多人卡在这一步点击Run弹出“Cannot run project: no artifacts configured”。这不是代码问题是IDEA没把war包打进去。关键操作链File → Project Structure → Artifacts→ 点→Web Application: Archive→For shoppingmall你的模块名展开左侧树勾选Output Layout下的Available Elements里的shoppingmall:war explodedBuild → Build Artifacts → Build生成target/shoppingmall.war或target/shoppingmall目录Run → Edit Configurations → → Tomcat Server → Local→Deployment选项卡 →→Artifact→ 选择shoppingmall:war exploded最关键一步在Application context里填/mall不是/否则访问http://localhost:8080/会404必须输http://localhost:8080/mall提示如果启动后页面空白先检查Tomcat控制台是否打印INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [...]。没有这行说明Artifact没挂上回去重做第1步。2.3 MySQL建库建表用utf8mb4不是utf8小米商城要支持emoji商品名比如“旗舰机”、用户昵称带生僻字utf8在MySQL里实际只支持3字节存不了4字节UTF-8字符。必须用utf8mb4且要配全三处-- 1. 创建数据库时指定字符集 CREATE DATABASE shoppingmall CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 2. 建表时显式声明即使库已设表级仍需确认 CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, email VARCHAR(100) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; -- 3. JDBC连接URL加参数重点 -- jdbc:mysql://localhost:3306/shoppingmall?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai血泪经验只改数据库和表字符集不加JDBC参数Java里读出来的中文仍是??。因为JDBC驱动默认用latin1解码必须靠characterEncodingutf8mb4告诉它“用四字节UTF-8解”。3. 核心业务落地从登录到下单手写ServletJDBC链路3.1 登录功能Session管理用户状态不是靠隐藏域传ID新手常犯错误登录成功后把user_id存在表单隐藏域里下次请求再传回来——这等于把用户ID裸奔在HTML里谁都能改。正确做法是用HttpSession绑定用户对象// com.mall.servlet.LoginServlet.java protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); // 1. 查询数据库这里简化实际应DAO分离 User user UserDao.findByUsername(username); if (user ! null password.equals(user.getPassword())) { // 2. 把用户对象存入Session不是存ID字符串 req.getSession().setAttribute(user, user); // ← 关键 resp.sendRedirect(req.getContextPath() /index.jsp); } else { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } }逻辑说明req.getSession()会创建新Session若不存在或返回已有Session。setAttribute(user, user)把整个User对象序列化存进内存后续所有请求只要req.getSession().getAttribute(user)就能取到——Tomcat自动维护Session ID Cookie浏览器每次带过来服务器就知道“这是谁”。3.2 商品列表JDBC分页查询避免SELECT * 拉崩数据库小米商城首页要展示几十个商品但数据库有上万条。不能SELECT * FROM products必须分页// com.mall.dao.ProductDao.java public ListProduct findProductsByPage(int pageNum, int pageSize) { String sql SELECT id, name, price, img_path FROM products LIMIT ?, ?; ListProduct list new ArrayList(); try (Connection conn JdbcUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, (pageNum - 1) * pageSize); // 起始位置第一页从0开始 ps.setInt(2, pageSize); // 每页条数 try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Product p new Product(); p.setId(rs.getInt(id)); p.setName(rs.getString(name)); p.setPrice(rs.getBigDecimal(price)); p.setImgPath(rs.getString(img_path)); list.add(p); } } } catch (SQLException e) { e.printStackTrace(); } return list; }参数说明LIMIT ?, ?中第一个?是offset偏移量第二个是limit条数。(pageNum - 1) * pageSize确保第1页取0~9条第2页取10~19条。只查必要字段id,name,price,img_path不查description等大文本字段减少网络传输和内存占用。3.3 购物车添加用HashMap模拟Session级购物车不是存数据库真实商城购物车初期不用DB存——用户未登录时也要加购登录后才合并。用MapInteger, Integer存productId, quantity最轻量// com.mall.servlet.AddToCartServlet.java protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int productId Integer.parseInt(req.getParameter(pid)); int quantity Integer.parseInt(req.getParameter(quantity)); // 1. 从Session获取购物车Map首次为空 MapInteger, Integer cart (MapInteger, Integer) req.getSession().getAttribute(cart); if (cart null) { cart new HashMap(); req.getSession().setAttribute(cart, cart); } // 2. 更新数量已存在则累加否则新增 cart.put(productId, cart.getOrDefault(productId, 0) quantity); // 3. 重定向回商品页避免重复提交 resp.sendRedirect(req.getContextPath() /product/list); }为什么不用数据库因为未登录用户没user_id无法关联。Session级购物车是行业通用做法——直到用户登录再把Session cart merge到DB cart。注意Session默认30分钟超时购物车自然清空符合业务预期。4. 避坑指南那些让新手调试到凌晨三点的“玄学”错误4.1 现象JSP页面中文显示为??但数据库里明明是中文原因JSP文件本身编码、Tomcat响应编码、浏览器解析编码三者不一致。常见组合JSP文件用GBK保存Windows记事本默认但% page contentTypetext/html;charsetUTF-8 %声明UTF-8 → 解析错Tomcatconf/server.xml里Connector没加URIEncodingUTF-8→ GET请求参数乱码浏览器没识别到Content-Type里的charset按默认ISO-8859-1解解决IDEA里右下角确认JSP文件编码是UTF-8File → File Encoding → UTF-8web.xml里加过滤器强制编码filter filter-nameCharacterEncodingFilter/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-nameCharacterEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping即使不用Spring也可手写一个HttpServletRequestWrapper实现3. Tomcatconf/server.xml中Connector port8080 ... URIEncodingUTF-8 /4.2 现象java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver原因MySQL 8.0驱动类名从com.mysql.jdbc.Driver改为com.mysql.cj.jdbc.Driver但很多教程还写旧版或jar包没放进WEB-INF/lib。解决Maven依赖用mysql:mysql-connector-java:8.0.33适配MySQL 5.7/8.0手动下载jar包必须复制到src/main/webapp/WEB-INF/lib/不是lib文件夹必须在WEB-INF下Class.forName(com.mysql.cj.jdbc.Driver)必须执行JDBC 4.0可省略但显式写更稳4.3 现象登录成功后跳转index.jsp但页面显示usernull原因req.getSession().setAttribute(user, user)执行了但index.jsp里% session.getAttribute(user) %输出null。排查链检查web.xml里session-configsession-timeout30/session-timeout/session-config是否被注释默认30分钟没问题查Tomcat日志catalina.out里是否有SEVERE: Error filterStart——说明过滤器初始化失败Session可能失效最隐蔽坑index.jsp顶部没写% page sessiontrue %JSP默认sessiontrue但如果之前有% page sessionfalse %整个页面就拿不到Session。4.4 现象订单提交时INSERT INTO orders和INSERT INTO order_items两条SQL一条成功一条失败数据不一致原因没用事务。JDBC默认自动提交每条SQL单独生效。解决conn.setAutoCommit(false); // 关闭自动提交 try { // 执行order插入 // 执行order_items批量插入 conn.commit(); // 全部成功才提交 } catch (Exception e) { conn.rollback(); // 任一失败就回滚 throw e; } finally { conn.setAutoCommit(true); // 恢复自动提交 }注意MySQL表引擎必须是InnoDBMyISAM不支持事务建表时确认ENGINEInnoDB。5. 进阶验证用Postman日志断点三步定位真实瓶颈5.1 用Postman绕过前端直测Servlet接口逻辑前端页面可能掩盖问题比如AJAX没捕获错误、表单没提交、JS阻止了默认行为。直接用Postman发请求看后端是否真干活POST /mall/loginBody选x-www-form-urlencoded填usernameadminpassword123观察响应是302重定向成功还是200返回登录页失败如果返回200打开Tomcat日志logs/catalina.out搜索LoginServlet看是否打印了usernull或SQL异常堆栈技巧在Servlet里加System.out.println(LoginServlet received: username);比前端alert更可靠。日志是你的第一现场证据。5.2 日志分级INFO只打关键路径DEBUG打SQL和参数盲目System.out.println会让日志爆炸。用Log4j 1.2轻量不用SLF4J!-- src/main/resources/log4j.properties -- log4j.rootLoggerINFO, stdout log4j.appender.stdoutorg.apache.log4j.ConsoleAppender log4j.appender.stdout.layoutorg.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern%d{yyyy-MM-dd HH:mm:ss} [%p] %c{1} - %m%n # 只对DAO层打DEBUG log4j.logger.com.mall.daoDEBUG// com.mall.dao.UserDao.java private static final Logger logger Logger.getLogger(UserDao.class); public static User findByUsername(String username) { logger.debug(Querying user by username: {}, username); // ← DEBUG级生产环境关掉 // ... JDBC查询 }价值当登录失败时看日志里有没有Querying user by username: admin如果有说明请求进来了如果没有问题在Servlet前过滤器、web.xml映射。5.3 断点调试在IDEA里看清Session和Request的真实内容光看代码猜不出req.getSession().getAttribute(user)是null还是对象。必须进Debugger在LoginServlet.doPost里req.getSession().setAttribute(user, user)后打断点Run Debug模式启动发送登录请求执行到断点左侧Variables窗口展开req→session→attributes→ 找user键值如果user是null说明UserDao.findByUsername返回null立刻跳去查SQL和数据库数据玄学终结者所有“为什么不行”的问题90%靠Debugger亲眼看到变量值就能破。不要猜要看见。5.4 性能基线用Apache Bench测出单机QPS天花板别等上线才慌——本地用ab压测知道你的TomcatMySQL能扛多少# 模拟100并发请求1000次商品列表 ab -n 1000 -c 100 http://localhost:8080/mall/product/list典型结果Requests per second: 128.42 [#/sec] (mean) Time per request: 778.720 [ms] (mean)说明单机Tomcat 8.5 MySQL 5.7纯JDBC查10条商品QPS约128。如果低于50一定是代码级问题比如没关ResultSet、没用连接池如果高于200恭喜你的基础链路很干净。我的习惯每次加一个新功能比如加购物车必跑一次ab对比。如果QPS掉30%立刻查新加的DAO方法——八成是忘了rs.close()或ps.close()导致连接池耗尽。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。