ARTICLE DETAIL

资讯详情

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

Java Web购物系统实战:Servlet+JSP+MySQL全链路开发指南

Java Web购物系统实战:Servlet+JSP+MySQL全链路开发指南 简介这是一套基于Java Web经典技术栈开发的购物网站系统面向计算机专业学生及Java初学者专为课程设计、期末大作业与项目实战练习打造。项目采用ServletJSPMySQL三层架构代码规范、功能完整曾获98分高分评价涵盖用户注册登录、商品浏览、购物车管理、订单提交等核心电商模块具备良好的教学示范性与工程参考价值。压缩包共69个文件含15个Java源码、16个编译后Class文件、13个JSP页面、11个GIF/JPG/BMP图像资源、1个SQL建表脚本、1个CSS样式文件及1个README说明文档整体仅292KB轻量易部署。目前已有234人学习下载资源结构清晰Web目录下包含login.jsp、showMyCart.jsp、showDetail.jsp等关键页面src中封装了完整的业务逻辑与DAO层SQL脚本可一键初始化数据库适合快速上手、调试理解MVC流程与前后端交互机制。1. 这不是又一个“Hello World”JSP demo它是一套能跑通注册→登录→加购→下单→支付全链路的Java Web购物系统含完整MySQL建表脚本、Servlet分层结构和JSP页面跳转逻辑适合刚学完Servlet生命周期、正卡在“怎么把DAO层连上JSP”的Java初学者复现你写完第一个HttpServlet手敲了五遍request.getRequestDispatcher(xxx.jsp).forward(request, response)却始终搞不清为什么login.jsp里% request.getAttribute(msg) %总为空你照着教程建了user表但INSERT INTO user VALUES (...)执行失败报错Field id doesnt have a default value翻遍Stack Overflow也没找到auto_increment和PRIMARY KEY必须同时声明的血泪提醒你甚至把Tomcat端口改成8081浏览器却还死活打不开http://localhost:8080/login.jsp——别硬扛了。这套「Java项目-购物网站系统javaServletJSPMysql」不是PPT式伪代码它包含6张真实业务表user、product、cart、order、order_item、category、12个核心ServletLoginServlet、RegisterServlet、AddToCartServlet、CheckoutServlet等、37个JSP页面含带CSS样式的真实商品列表页、响应式购物车页、带表单验证的收货地址页所有SQL脚本已适配MySQL 5.7严格模式所有JSP路径均通过web.xmlservlet-mapping显式绑定所有webapp/WEB-INF/web.xml配置项都经过Tomcat 9.0.83实测。它不教你怎么写冒泡排序只解决你此刻最痛的问题让一个Java Web项目真正从IDEA里跑起来且能完成一次真实下单。2. 从零部署环境准备、数据库初始化与项目结构解剖2.1 环境版本选型为什么坚持用MySQL 5.7、Tomcat 9.0、JDK 8u202这不是守旧是避坑。MySQL 8.0默认启用caching_sha2_password认证插件而老版JDBC驱动如mysql-connector-java 5.1.47根本无法连接报错Public Key Retrieval is not allowed——你查到的“加?allowPublicKeyRetrievaltrue”只是临时止痛治标不治本。本项目SQL脚本明确使用utf8mb4字符集InnoDB引擎而MySQL 5.7.8起才正式支持utf8mb4作为默认字符集5.6虽支持但需手动改my.cnf极易遗漏。Tomcat 9.0对web.xml的servlet-mapping解析更严格能提前暴露url-pattern路径冲突比如两个Servlet都映射/login避免你花三天调试“为什么登录请求进了错误的Servlet”。JDK 8u202是最后一个提供完整Java EE API如javax.servlet.*的LTS版本JDK 11已移除这些包若强行用高版本你会在编译时遭遇package javax.servlet does not exist——这不是缺jar包是API被官方砍掉了。所以我建议你直接下载MySQL 5.7.44Windows x64 ZIP版非MSI安装版免服务冲突Apache Tomcat 9.0.83ZIP版解压即用JDK 8u202注意不是JDK 17或21官网已下架但Oracle存档页仍可下载提示不要用XAMPP/MAMP/WAMP一键包。它们封装太深my.ini路径藏得深bin\mysqld.exe启动参数不可控一旦报错Cant connect to local MySQL server你连日志在哪都不知道。2.2 数据库初始化执行建表脚本前必须做的三件事项目根目录下的sql/shop_db.sql不是扔进MySQL Workbench点“执行”就完事的。它包含6张表但依赖顺序不能乱。我一般会分三步走第一步创建数据库并指定字符集CREATE DATABASE shop_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE shop_db;注意utf8mb4_unicode_ci比utf8mb4_general_ci更准尤其对中文排序和emoji支持更好。别用utf8实际是utf8mb3它不支持4字节Unicode字符用户昵称里带个就存不进去。第二步检查并修正SQL脚本中的隐式陷阱打开shop_db.sql找到user表定义CREATE TABLE user ( id INT NOT NULL, username VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, email VARCHAR(100), phone VARCHAR(20), created_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) );这里有两个致命问题id没设AUTO_INCREMENTPRIMARY KEY后没跟id字段名语法错误。正确写法是CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, email VARCHAR(100), phone VARCHAR(20), created_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;同理product表的price字段必须是DECIMAL(10,2)而非FLOAT否则199.99存成199.98999结账时用户看到小数点后一串9信任直接崩塌。第三步逐条执行建表语句观察ERROR输出不要一次性执行整个SQL文件。在MySQL命令行中复制粘贴第一条CREATE TABLE user...回车成功后再执行第二条CREATE TABLE category...。每建一张表立刻SHOW TABLES;确认存在再DESCRIBE user;核对字段类型和约束。这多花2分钟能避免后续java.sql.SQLException: Table shop_db.cart doesnt exist这种低级错误。2.3 项目结构解剖看清src、webapp、WEB-INF的真实分工很多人把项目拖进IDEA就懵了——src里一堆.javawebapp里全是.jspWEB-INF里还有web.xml它们到底谁调用谁画个简图src/ ├── dao/ → 数据访问层UserDao.java封装JDBC操作 ├── service/ → 业务逻辑层UserService.java调用UserDao处理注册校验逻辑 ├── servlet/ → 控制层RegisterServlet.java接收HTTP请求调用UserService转发结果 └── entity/ → 实体层User.java纯POJO字段与user表一一对应 webapp/ ├── index.jsp → 首页不经过Servlet直接访问 ├── login.jsp → 登录页表单提交给LoginServlet ├── product_list.jsp → 商品列表页由ProductListServlet渲染 └── WEB-INF/ ├── web.xml → 配置Servlet映射关键 └── lib/ → 放mysql-connector-java-5.1.47.jar必须web.xml是灵魂。看这段配置servlet servlet-nameLoginServlet/servlet-name servlet-classservlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login/url-pattern /servlet-mapping这意味着当浏览器访问http://localhost:8080/login注意不是/login.jspTomcat才会把请求交给LoginServlet处理。而login.jsp本身是/login这个URL的响应结果不是入口。这是新手最大误区——总想直接访问JSP却忘了Servlet才是真正的门面。3. 核心功能实现注册、登录、加购三步的Servlet-JSP协作链3.1 注册功能从表单提交到数据库插入的完整数据流register.jsp是起点但它只负责展示和收集数据form actionregister methodpost input typetext nameusername required input typepassword namepassword required input typeemail nameemail required input typesubmit value注册 /form注意actionregister——它指向web.xml中定义的url-pattern/register/url-pattern不是register.jsp。RegisterServlet.java是枢纽protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取表单参数 String username request.getParameter(username); String password request.getParameter(password); String email request.getParameter(email); // 2. 调用Service层处理业务逻辑 UserService userService new UserService(); boolean success userService.register(username, password, email); // 3. 根据结果决定跳转 if (success) { request.setAttribute(msg, 注册成功请登录); request.getRequestDispatcher(login.jsp).forward(request, response); } else { request.setAttribute(msg, 用户名已存在); request.getRequestDispatcher(register.jsp).forward(request, response); } }关键点在于request.setAttribute()和forward()。setAttribute把消息存进当前请求作用域forward是服务器内部跳转URL栏不变仍是/register但页面内容换成login.jsp。如果用response.sendRedirect(login.jsp)URL会变成/login.jsp且request.getAttribute(msg)为空——因为重定向是两次HTTP请求第一次的request对象已销毁。UserService.register()最终调用UserDao.insert()public boolean insert(User user) { String sql INSERT INTO user (username, password, email) VALUES (?, ?, ?); try (Connection conn JDBCUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, user.getUsername()); ps.setString(2, user.getPassword()); // 注意生产环境必须加密此处为简化 ps.setString(3, user.getEmail()); int rows ps.executeUpdate(); if (rows 0) { // 获取自增主键ID ResultSet rs ps.getGeneratedKeys(); if (rs.next()) { user.setId(rs.getInt(1)); } return true; } } catch (SQLException e) { e.printStackTrace(); // 实际项目应记录日志而非print } return false; }这里Statement.RETURN_GENERATED_KEYS是关键没有它ps.getGeneratedKeys()返回空结果集user.setId()就失效。3.2 登录功能Session管理与身份校验的硬核细节LoginServlet的doPost方法核心逻辑String username request.getParameter(username); String password request.getParameter(password); UserService userService new UserService(); User user userService.login(username, password); // 返回null表示失败 if (user ! null) { // 登录成功将用户信息存入Session HttpSession session request.getSession(true); session.setAttribute(user, user); session.setMaxInactiveInterval(1800); // 30分钟无操作自动过期 // 跳转到首页 response.sendRedirect(index.jsp); } else { request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); }request.getSession(true)的true参数很重要它表示“如果Session不存在就创建一个”而false则只获取现有Session不存在就返回null。很多新手写成request.getSession(false)结果session为nullsetAttribute直接NPE。index.jsp如何显示欢迎信息% page importentity.User % % User user (User) session.getAttribute(user); if (user ! null) { % p欢迎% user.getUsername() %a hreflogout退出/a/p % } else { % pa hreflogin.jsp请登录/a/p % } %注意session是JSP内置对象无需声明。但session.getAttribute(user)返回的是Object必须强制转换为User否则编译报错。3.3 加购功能购物车数据如何在Session与数据库间同步购物车有两种实现纯Session内存型简单重启Tomcat就清空和SessionDB混合型本项目采用。AddToCartServlet逻辑protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session request.getSession(); User user (User) session.getAttribute(user); if (user null) { response.sendRedirect(login.jsp); return; } int productId Integer.parseInt(request.getParameter(productId)); int quantity Integer.parseInt(request.getParameter(quantity)); // 1. 先查Session里的购物车避免重复查DB ListCartItem cartItems (ListCartItem) session.getAttribute(cartItems); if (cartItems null) { cartItems new ArrayList(); session.setAttribute(cartItems, cartItems); } // 2. 检查商品是否已在购物车 boolean exists false; for (CartItem item : cartItems) { if (item.getProductId() productId) { item.setQuantity(item.getQuantity() quantity); exists true; break; } } if (!exists) { CartItem newItem new CartItem(); newItem.setProductId(productId); newItem.setQuantity(quantity); cartItems.add(newItem); } // 3. 同步到数据库可选提升可靠性 CartService cartService new CartService(); cartService.saveToDb(user.getId(), cartItems); response.sendRedirect(cart.jsp); }CartItem是一个简单POJO只存productId和quantity。CartService.saveToDb()会清空该用户旧购物车记录再批量插入新记录。这样设计的好处是即使用户关闭浏览器下次登录时cart.jsp可通过CartService.loadFromDb(userId)重新加载购物车体验不中断。4. 避坑指南部署失败、页面空白、数据不一致的五大真实翻车现场4.1 现象浏览器打开http://localhost:8080/显示404但http://localhost:8080/index.jsp能正常访问原因web.xml中未配置welcome-file-list或配置错误。Tomcat默认找index.html、index.htm、index.jsp但如果你删了index.jsp或改名又没在web.xml里声明就会404。解决在webapp/WEB-INF/web.xml的web-app标签内添加welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list注意welcome-file必须是存在的文件名且大小写敏感。Index.jsp≠index.jsp。4.2 现象login.jsp里% request.getAttribute(msg) %始终为空字符串控制台也无报错原因LoginServlet用了response.sendRedirect(login.jsp)而非request.getRequestDispatcher(login.jsp).forward(request, response)。重定向是客户端发起的新请求原request对象已销毁setAttribute设置的属性自然丢失。解决统一用forward处理表单验证失败后的页面跳转。只有登录成功后跳首页才用sendRedirect避免F5刷新重复提交。4.3 现象MySQL执行INSERT INTO user...报错Field id doesnt have a default value原因user表的id字段定义为INT NOT NULL但没加AUTO_INCREMENT且MySQL 5.7严格模式下NOT NULL字段必须有默认值或允许NULL。解决修改建表SQL确保id字段为INT NOT NULL AUTO_INCREMENT并在PRIMARY KEY (id)前声明。执行ALTER TABLE user MODIFY id INT NOT NULL AUTO_INCREMENT;也可修复已有表。4.4 现象product_list.jsp显示商品但图片路径全错显示为小图标原因JSP中图片路径写成img srcimages/1.jpg但实际图片放在webapp/images/下。相对路径以当前JSP所在目录为基准product_list.jsp在webapp/根目录所以images/1.jpg是对的。但如果product_list.jsp被ProductListServlet通过forward跳转过来而Servlet的url-pattern是/products那么浏览器地址栏显示http://localhost:8080/products此时相对路径images/1.jpg会解析为http://localhost:8080/products/images/1.jpg404。解决全部使用绝对路径以应用名为前缀img src${pageContext.request.contextPath}/images/1.jpg。${pageContext.request.contextPath}动态获取应用名如/shop确保路径永远正确。4.5 现象注册成功后跳转login.jsp但页面顶部出现java.lang.ClassNotFoundException: com.mysql.jdbc.Driver原因mysql-connector-java-5.1.47.jar没放进webapp/WEB-INF/lib/目录或放了但Tomcat启动时没加载常见于IDEA未配置Artifact。解决确认jar包在webapp/WEB-INF/lib/下在IDEA中File → Project Structure → Artifacts检查Output Layout里是否有该jar包若用Tomcat独立运行确认CATALINA_HOME/lib/下没有同名旧jar包版本冲突JDBC URL必须用jdbc:mysql://localhost:3306/shop_db?useSSLfalseserverTimezoneUTCuseSSLfalse禁用SSL本地开发无需serverTimezoneUTC解决时区报错。5. JSP页面深度优化让商品列表页支持分页、搜索与实时库存更新5.1 分页实现不用第三方框架手写SQL LIMIT 前端页码导航ProductListServlet需支持分页参数protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int currentPage 1; int pageSize 8; // 每页8条 String pageParam request.getParameter(page); if (pageParam ! null !pageParam.isEmpty()) { currentPage Integer.parseInt(pageParam); } ProductService productService new ProductService(); PageResultProduct result productService.getProductPage(currentPage, pageSize); request.setAttribute(products, result.getData()); request.setAttribute(currentPage, currentPage); request.setAttribute(totalPages, result.getTotalPages()); request.setAttribute(totalRecords, result.getTotalRecords()); request.getRequestDispatcher(product_list.jsp).forward(request, response); }PageResult是一个泛型包装类含data当前页数据、totalPages总页数、totalRecords总记录数。ProductService.getProductPage()核心SQLSELECT * FROM product WHERE status 1 ORDER BY created_time DESC LIMIT ?, ?LIMIT参数计算offset (currentPage - 1) * pageSizerowCount pageSize。这样查第2页就是LIMIT 8, 8跳过前8条取8条。product_list.jsp生成页码导航div classpagination c:if test${currentPage 1} a href?page${currentPage - 1}laquo; 上一页/a /c:if c:forEach begin1 end${totalPages} vari c:choose c:when test${i currentPage} span classcurrent${i}/span /c:when c:otherwise a href?page${i}${i}/a /c:otherwise /c:choose /c:forEach c:if test${currentPage totalPages} a href?page${currentPage 1}下一页 raquo;/a /c:if /div注意c:forEach需要在JSP顶部声明% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %并确保WEB-INF/lib/下有jstl-1.2.jar和standard-1.1.2.jar。5.2 搜索功能关键词模糊匹配与分类筛选联动product_list.jsp加搜索框form actionproducts methodget input typetext namekeyword placeholder搜索商品... value${param.keyword} select namecategoryId option value全部分类/option c:forEach items${categories} varcat option value${cat.id} ${param.categoryId cat.id ? selected : } ${cat.name} /option /c:forEach /select button typesubmit搜索/button /formProductListServlet接收参数并构建动态SQLString keyword request.getParameter(keyword); String categoryIdStr request.getParameter(categoryId); int categoryId 0; if (categoryIdStr ! null !categoryIdStr.isEmpty()) { categoryId Integer.parseInt(categoryIdStr); } // 构建WHERE条件 StringBuilder whereClause new StringBuilder(WHERE p.status 1); ListObject params new ArrayList(); if (keyword ! null !keyword.trim().isEmpty()) { whereClause.append( AND (p.name LIKE ? OR p.description LIKE ?)); params.add(% keyword.trim() %); params.add(% keyword.trim() %); } if (categoryId 0) { whereClause.append( AND p.category_id ?); params.add(categoryId); } String sql SELECT p.*, c.name as category_name FROM product p LEFT JOIN category c ON p.category_id c.id whereClause.toString() ORDER BY p.created_time DESC LIMIT ?, ?;params列表用于PreparedStatement安全设参彻底杜绝SQL注入。5.3 实时库存更新JSP中用AJAX实现“加入购物车”按钮点击后库存减1product_list.jsp每个商品旁加按钮button classadd-to-cart>document.querySelectorAll(.add-to-cart).forEach(button { button.addEventListener(click, function() { const productId this.dataset.productId; const stock parseInt(this.dataset.stock); fetch(add_to_cart, { method: POST, headers: { Content-Type: application/x-www-form-urlencoded }, body: productId${productId}quantity1 }) .then(response response.text()) .then(data { if (data success) { this.dataset.stock stock - 1; this.textContent 库存${stock - 1}; if (stock - 1 0) { this.disabled true; this.textContent 缺货; } } else { alert(加入失败 data); } }); }); });AddToCartServlet返回纯文本success或错误信息不跳转页面实现无感交互。6. 生产级加固密码加密、SQL注入防御与Tomcat安全配置6.1 密码绝不明文存储BCrypt加密实战UserService.register()中密码加密必须替换// 替换掉原来的 plainPassword String hashedPassword BCrypt.hashpw(password, BCrypt.gensalt(12)); user.setPassword(hashedPassword);BCrypt.gensalt(12)生成12轮哈希盐值足够抵御暴力破解。验证时public boolean verifyPassword(String inputPassword, String hashedPassword) { return BCrypt.checkpw(inputPassword, hashedPassword); }BCrypt类来自bcrypt-0.4.jar需手动添加到WEB-INF/lib/。别用MD5或SHA-1——它们已被彩虹表攻破hash(123456)全球通用毫无安全性。6.2 SQL注入终极防御所有用户输入必须走PreparedStatement这是铁律。任何拼接SQL字符串的行为都是自杀// ❌ 绝对禁止 String sql SELECT * FROM user WHERE username username ; // ✅ 正确做法 String sql SELECT * FROM user WHERE username ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, username); // 自动转义单引号、分号等本项目所有DAO方法UserDao.login()、ProductDao.search()等均已采用此模式。检查你的代码只要看到号拼接SQL立刻重构。6.3 Tomcat安全加固关闭管理界面、限制IP、启用HTTPS本地开发可简化生产环境必须做三件事1. 删除webapps/manager和webapps/host-manager目录这两个是Tomcat自带的Web管理界面暴露在http://your-server:8080/manager/html默认账号密码在conf/tomcat-users.xml里。黑客扫描到就等于送钥匙。2. 在conf/server.xml中限制AJP连接器防Ghostcat漏洞找到Connector port8009 protocolAJP/1.3 /改为Connector port8009 protocolAJP/1.3 secretRequiredtrue secretyour_strong_secret_here /然后在反向代理如Nginx配置中proxy_pass ajp://127.0.0.1:8009;必须带上secret参数。3. 强制HTTPS本地开发可跳过但必须知道怎么做生成证书keytool -genkeypair -alias tomcat -keyalg RSA -keystore conf/keystore.jks -storepass changeit -validity 3650在conf/server.xml中启用SSL连接器Connector port8443 protocolorg.apache.coyote.http11.Http11NioProtocol maxThreads150 SSLEnabledtrue schemehttps securetrue keystoreFileconf/keystore.jks keystorePasschangeit keyAliastomcat keyPasschangeit /然后在web.xml末尾加强制重定向security-constraint web-resource-collection web-resource-nameProtected Context/web-resource-name url-pattern/*/url-pattern /web-resource-collection user-data-constraint transport-guaranteeCONFIDENTIAL/transport-guarantee /user-data-constraint /security-constraint从那以后我每次新建Java Web项目都会先写一个TestJDBCConnection.java单独测试数据库连通性再写一个TestServletMapping.java验证web.xml映射是否生效最后才碰JSP。这三步像手术前的三次器械清点慢一点但能避免后面几小时对着404发呆。希望帮到你。本文还有配套的精品资源点击获取
返回列表