JSP+SQL Server购物车实战:从零搭建可上线的Java Web基础项目
简介这是一份面向Java Web初学者与高校课程设计者的JSPSQL Server购物车实战项目资源聚焦电商核心功能实现帮助学习者掌握动态网页开发、数据库交互及前后端协同逻辑。资源共35个文件包含10个JSP页面如login.jsp、purchase.jsp、register.jsp等、13张界面图片含logo.jpg、bb系列商品图、2个SQL Server数据库文件.mdf与.ldf、1个Java Bean类、1个编译后的class文件及配套说明文档整体压缩包仅381KB轻量易部署。已有203人下载学习适合在TomcatSQL Server环境下快速运行调试。读者可直接获取完整可运行的购物车源码结构、数据库建表脚本、用户注册登录与商品增删改查全流程实现以及关键的安全防护实践如PreparedStatement防SQL注入和Session购物车临时存储方案是理解JSP Web应用分层架构的典型教学案例。1. 这不是“老古董演示”一个能真跑、能改、能上线的 JSP SQL Server 购物车为什么现在还值得动手做你点开这个压缩包.rar看到JSP(SQLserver).rar_JSP 购物车_Javasqlserver_java sqlServer_jsp购物车_——别急着关掉。它不是毕业设计交差用的“摆设工程”而是一套完整闭环的 Java Web 基础链路实操样本从 JSP 页面渲染、Servlet 控制流转、DAO 层 JDBC 直连 SQL Server到购物车核心逻辑添加/删除/数量校验/总价计算全部落地。它不依赖 Spring Boot 自动装配不包装 Hibernate 复杂映射就用最原始的request.getParameter()、session.setAttribute()、PreparedStatement和ResultSet把数据从 SQL Server 表里捞出来、塞进页面、再写回去。正因如此它成了Java Web 入门者调试 Servlet 生命周期、理解 session 作用域、排查 SQL Server 连接超时、验证字符集乱码问题的“黑匣子解剖台”。如果你正卡在java.sql.SQLException: No suitable driver found、javax.servlet.ServletException: java.lang.NullPointerException或SQL Server 返回空结果但日志没报错这类问题上这个项目不是怀旧是救命稻草。它适合两类人刚学完 Java 基础想搭第一个 Web 应用的新手以及需要快速验证 SQL Server JDBC 配置、复现 session 失效场景的老手。2. 搭建环境绕过“SQL Server 安装失败”和“JSP 找不到驱动”的双重陷阱2.1 选对版本组合为什么 SQL Server 2019 JDK 8 Tomcat 8.5 是当前最稳的三角很多新手一上来就装 SQL Server 2022结果发现 JDBC 驱动mssql-jdbc-12.4.2.jre11.jar在 JDK 8 下直接抛UnsupportedClassVersionError或者用 Tomcat 10却忘了 JSP 规范已从javax.*迁移到jakarta.*导致所有import javax.servlet.*全部报红。真实生产环境里80% 的遗留系统仍运行在 JDK 8 Tomcat 8.5 SQL Server 2016/2019 组合上。我们按此组合落地SQL Server推荐安装SQL Server 2019 Express Edition免费、轻量、兼容性好安装时务必勾选“SQL Server 和 Windows 身份验证模式”并记下 SA 密码后续 JDBC 连接要用JDK必须用JDK 8u391LTS 最后稳定版JAVA_HOME指向C:\Program Files\Java\jdk1.8.0_391PATH加入%JAVA_HOME%\binTomcat下载Apache Tomcat 8.5.99官方最后维护版解压后修改conf/server.xml中Connector port8080为8081避免与 IIS 冲突JDBC 驱动去 Microsoft 官网下载mssql-jdbc-8.4.1.jre8.jar注意后缀jre8不要用 Maven 仓库里带jre11的版本放入Tomcat/webapps/yourapp/WEB-INF/lib/。提示SQL Server 安装后用 SSMSSQL Server Management Studio连接localhost\SQLEXPRESS新建数据库shopping_db执行建表脚本见 2.3 节。若连不上检查 SQL Server 配置管理器 → SQL Server 网络配置 → SQLEXPRESS 的协议 → TCP/IP 是否启用并确认 IP 地址选项卡中IPAll的 TCP 端口填了1433。2.2 数据库建模三张表撑起购物车骨架字段类型直击 SQL Server 实战坑点购物车功能不需要复杂 ER 图核心就三张表users用户、products商品、cart_items购物车项。关键在字段类型选择——SQL Server 的varchar和nvarchar区别、int与bigint的容量边界、datetime2替代datetime的必要性全在这里体现-- 创建 shopping_db 数据库若不存在 IF NOT EXISTS (SELECT name FROM sys.databases WHERE name Nshopping_db) CREATE DATABASE shopping_db COLLATE Chinese_PRC_CI_AS; GO USE shopping_db; GO -- 用户表密码用 varchar(64) 存 SHA-256 加密后字符串非明文 CREATE TABLE users ( id INT IDENTITY(1,1) PRIMARY KEY, username NVARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, -- 注意SHA-256 输出是64位十六进制字符串用 VARCHAR 足够 email NVARCHAR(100), created_time DATETIME2 DEFAULT GETDATE() ); -- 商品表price 用 DECIMAL(10,2) 精确存储金额避免 float 浮点误差 CREATE TABLE products ( id INT IDENTITY(1,1) PRIMARY KEY, name NVARCHAR(100) NOT NULL, description NVARCHAR(500), price DECIMAL(10,2) NOT NULL CHECK (price 0.01), stock INT NOT NULL DEFAULT 0 CHECK (stock 0) ); -- 购物车项表user_id product_id 组成联合主键避免同一用户重复加同款商品 CREATE TABLE cart_items ( user_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL DEFAULT 1 CHECK (quantity 0), added_time DATETIME2 DEFAULT GETDATE(), PRIMARY KEY (user_id, product_id), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE );参数说明NVARCHAR用于用户名、商品名等需中文支持的字段N中文插入才不乱码VARCHAR用于密码哈希值ASCII 字符无需 Unicode 开销DECIMAL(10,2)确保价格计算无精度丢失如99999999.99最大值DATETIME2比DATETIME精度更高100 纳秒级、范围更广0001-9999 年且是 SQL Server 2008 推荐标准。2.3 JDBC 连接池初始化别在每个 Servlet 里 new Connection用 JNDI 统一管理把数据库连接硬编码在CartServlet.java里那是教学 demo 的写法。真实项目必须用连接池。Tomcat 自带org.apache.tomcat.jdbc.pool.DataSource只需两步配置在Tomcat/conf/context.xml中添加全局资源注意username和password填你 SQL Server 的 SA 账号Resource namejdbc/shoppingDB authContainer typejavax.sql.DataSource factoryorg.apache.tomcat.jdbc.pool.DataSourceFactory driverClassNamecom.microsoft.sqlserver.jdbc.SQLServerDriver urljdbc:sqlserver://localhost:1433;databaseNameshopping_db;encryptfalse;trustServerCertificatetrue; usernamesa passwordYourStrongPass123 maxActive20 minIdle5 maxWait10000 validationQuerySELECT 1 testOnBorrowtrue/在webapp/META-INF/context.xml中声明资源引用让应用能查到该 JNDI 名?xml version1.0 encodingUTF-8? Context ResourceLink namejdbc/shoppingDB globaljdbc/shoppingDB typejavax.sql.DataSource/ /Context逻辑说明url中encryptfalse;trustServerCertificatetrue是开发环境绕过 SSL 证书校验的必需参数生产环境必须配证书validationQuerySELECT 1让连接池每次借出前执行简单查询验证连接有效性testOnBorrowtrue避免拿到失效连接。这样任何 Servlet 只需InitialContext ctx new InitialContext(); DataSource ds (DataSource) ctx.lookup(java:comp/env/jdbc/shoppingDB);即可获取连接不用关心创建和关闭。3. 核心逻辑实现购物车增删改查的 JSP Servlet 闭环代码拆解3.1 添加商品到购物车Session 与数据库双写解决“页面刷新后购物车消失”问题购物车数据存在 Session 还是数据库答案是都存但用途不同。Session 存临时浏览态用户未登录时的购物车数据库存持久态用户登录后绑定到 user_id。本项目采用“登录态优先写库未登录态暂存 Session”的混合策略。关键在AddToCartServlet.java// AddToCartServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取参数注意JSP 表单 nameproductId不是 id String productIdStr request.getParameter(productId); String quantityStr request.getParameter(quantity); if (productIdStr null || productIdStr.trim().isEmpty()) { request.setAttribute(error, 商品ID不能为空); request.getRequestDispatcher(/error.jsp).forward(request, response); return; } int productId Integer.parseInt(productIdStr); int quantity 1; if (quantityStr ! null !quantityStr.trim().isEmpty()) { quantity Math.max(1, Integer.parseInt(quantityStr)); // 防止 quantity 0 } // 2. 获取当前用户模拟登录态实际应从 session.getAttribute(user) 取 HttpSession session request.getSession(); User user (User) session.getAttribute(user); try { CartService cartService new CartService(); if (user ! null) { // 已登录写入数据库 cart_items 表 cartService.addToCartByUserId(user.getId(), productId, quantity); // 同时同步更新 Session 中的购物车缓存提升页面响应速度 ListCartItem cartItems cartService.getCartItemsByUserId(user.getId()); session.setAttribute(cartItems, cartItems); } else { // 未登录只存 Sessionkey 为 guestCart避免与登录态冲突 ListCartItem guestCart (ListCartItem) session.getAttribute(guestCart); if (guestCart null) { guestCart new ArrayList(); session.setAttribute(guestCart, guestCart); } // 合并相同商品找到已有项则 quantity 新 quantity否则 add 新项 boolean found false; for (CartItem item : guestCart) { if (item.getProductId() productId) { item.setQuantity(item.getQuantity() quantity); found true; break; } } if (!found) { guestCart.add(new CartItem(0, productId, quantity, new Date())); } } response.sendRedirect(request.getContextPath() /cart.jsp?msgadd_success); } catch (SQLException e) { e.printStackTrace(); request.setAttribute(error, 添加失败 e.getMessage()); request.getRequestDispatcher(/error.jsp).forward(request, response); } }参数说明Math.max(1, Integer.parseInt(quantityStr))强制数量不低于 1对应热词vue3设置购物车个数不小于0的业务逻辑session.setAttribute(guestCart, ...)使用独立 key 避免与登录态cartItems冲突cartService.addToCartByUserId(...)内部执行INSERT INTO cart_items (user_id, product_id, quantity) VALUES (?, ?, ?)若主键冲突同一 user_id product_id 已存在则转为UPDATE cart_items SET quantity quantity ? WHERE user_id ? AND product_id ?。3.2 购物车页面渲染JSP EL 表达式 JSTL 标签告别 scriptlet 脚本混乱cart.jsp不该出现% while(rs.next()) { %这种嵌套 Java 代码。用 JSTL EL 实现清晰分离% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % html headtitle我的购物车/title/head body h2购物车清单/h2 !-- 显示提示消息 -- c:if test${not empty param.msg} c:choose c:when test${param.msg add_success} p stylecolor:green✅ 商品已成功加入购物车/p /c:when c:when test${param.msg update_success} p stylecolor:green✅ 数量已更新/p /c:when /c:choose /c:if !-- 购物车表格 -- table border1 tr th商品名称/th th单价/th th数量/th th小计/th th操作/th /tr c:choose c:when test${empty cartItems and empty guestCart} trtd colspan5 aligncenter购物车为空请先添加商品/td/tr /c:when c:otherwise !-- 合并登录态与游客态购物车 -- c:set varallCartItems value${empty cartItems ? guestCart : cartItems}/ c:forEach items${allCartItems} varitem tr td${item.productName}/td tdfmt:formatNumber value${item.price} typecurrency//td td form action${pageContext.request.contextPath}/UpdateCartServlet methodpost input typehidden nameitemId value${item.id}/ input typenumber namequantity value${item.quantity} min1 stylewidth:60px/ input typesubmit value更新/ /form /td tdfmt:formatNumber value${item.price * item.quantity} typecurrency//td td a href${pageContext.request.contextPath}/DeleteCartItemServlet?itemId${item.id}删除/a /td /tr /c:forEach !-- 总计行 -- tr td colspan3 alignrightstrong总计/strong/td td colspan2 strong fmt:formatNumber value${allCartItems.stream().mapToDouble(i - i.price * i.quantity).sum()} typecurrency/ /strong /td /tr /c:otherwise /c:choose /table a href${pageContext.request.contextPath}/products.jsp继续购物/a /body /html逻辑说明c:forEach遍历cartItems或guestCartfmt:formatNumber自动处理货币格式¥ 符号、千分位allCartItems.stream().mapToDouble(...).sum()是 JSP 2.3 支持的 EL 方法调用计算总价input typenumber min1前端强制最小值与后端Math.max(1, ...)形成双重校验。3.3 更新与删除事务控制与并发安全防止“库存扣成负数”UpdateCartServlet.java和DeleteCartItemServlet.java必须包裹在数据库事务中。尤其更新数量时若不加锁高并发下可能SELECT quantity得到 5UPDATE quantity 5 - 1执行两次最终变成 3 而非预期 4。解决方案用 SQL Server 的UPDLOCK和ROWLOCK提示// CartService.java 中的 updateQuantity 方法 public void updateCartItemQuantity(int itemId, int newQuantity) throws SQLException { String sql UPDATE cart_items WITH (UPDLOCK, ROWLOCK) SET quantity ? WHERE id ?; try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { conn.setAutoCommit(false); // 开启事务 ps.setInt(1, newQuantity); ps.setInt(2, itemId); int rows ps.executeUpdate(); if (rows 0) { throw new SQLException(购物车项不存在或已被删除); } conn.commit(); // 提交事务 } catch (SQLException e) { // 回滚事务 try (Connection conn dataSource.getConnection()) { conn.rollback(); } throw e; } }参数说明WITH (UPDLOCK, ROWLOCK)告诉 SQL Server 对该行加更新锁UPDLOCK且只锁住目标行ROWLOCK避免锁表conn.setAutoCommit(false)关闭自动提交手动控制 commit/rollbackif (rows 0)检查是否真的更新了行防止恶意 itemId 攻击。4. 避坑指南90% 的 JSP SQL Server 购物车项目翻车点都在这五条4.1 现象页面显示中文乱码如 “正常”但数据库里存的是正常中文原因JSP 文件本身编码、Tomcat 请求/响应编码、SQL Server 数据库排序规则、JDBC URL 参数四者不统一。常见错误是 JSP 保存为 ANSI 编码或pageEncodingGBK但浏览器发送 UTF-8。解决JSP 文件用 UTF-8 无 BOM 格式保存用 Notepad → 编码 → 转为 UTF-8JSP 顶部声明% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Tomcatconf/web.xml中添加filter强制请求编码filter filter-nameencodingFilter/filter-name filter-classorg.apache.catalina.filters.SetCharacterEncodingFilter/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-mappingJDBC URL 加characterEncodingUTF-8虽然 SQL Server 驱动不严格认这个参数但加上无害SQL Server 数据库排序规则设为Chinese_PRC_CI_AS大小写不敏感、中文排序友好。4.2 现象java.sql.SQLException: No suitable driver found for jdbc:sqlserver://...原因JDBC 驱动 JAR 未正确放入WEB-INF/lib/或驱动类名写错com.microsoft.sqlserver.jdbc.SQLServerDriver不能写成com.microsoft.jdbc.sqlserver.SQLServerDriver或 Tomcat 启动时未加载该 JAR。解决检查webapps/yourapp/WEB-INF/lib/下是否存在mssql-jdbc-8.4.1.jre8.jar文件名必须完全匹配在web.xml中servlet上方添加listener加载驱动虽非必需但可提前暴露问题listener listener-classcom.microsoft.sqlserver.jdbc.SQLServerDriver/listener-class /listener启动 Tomcat 后查看logs/catalina.out是否有Loaded JDBC driver日志。4.3 现象登录后购物车数据清空或不同用户看到彼此的购物车原因Session 未正确绑定用户或购物车数据存错 scope如把cartItems存在application而非session。解决登录成功后session.setAttribute(user, user)必须执行且user对象含唯一id字段所有购物车操作前先User user (User) session.getAttribute(user)判空再分支绝对禁止session.setAttribute(cartItems, items)后在其他 Servlet 中session.getAttribute(cartItems)直接强转使用——必须先判空再判类型instanceof List清理购物车时DELETE FROM cart_items WHERE user_id ?的?必须来自user.getId()而非从 URL 参数取。4.4 现象添加商品时报java.lang.NumberFormatException: For input string: 原因JSP 表单中input nameproductId为空提交request.getParameter(productId)返回空字符串Integer.parseInt()直接崩溃。解决在 Servlet 中所有getParameter()后立即判空String pid request.getParameter(productId); if (pid null || pid.trim().isEmpty()) { // 返回错误页或重定向 }前端表单加required属性input typenumber nameproductId required使用Integer.valueOf(pid)替代Integer.parseInt(pid)前者对 null 返回 0但需业务允许默认值。4.5 现象SQL Server 报错Invalid object name cart_items但 SSMS 里表明明存在原因SQL Server 默认 schema 是dbo但建表时未显式指定或 JDBC 查询时未带 schema 前缀。解决建表时明确写CREATE TABLE dbo.cart_items (...)所有 SQL 查询写全名SELECT * FROM dbo.cart_items检查数据库用户权限SA 用户默认有db_owner权限但若用其他用户需GRANT SELECT, INSERT, UPDATE, DELETE ON dbo.cart_items TO [username]。5. 进阶技巧用 SQL Server 的MERGE语句原子化购物车操作替代手写 IF EXISTS购物车“添加或更新”逻辑商品已存在则 quantity newQty不存在则 INSERT传统写法是先SELECT COUNT(*)判断再INSERT/UPDATE。这在高并发下有竞态风险。SQL Server 提供MERGE语句一条 SQL 完成“存在则更新不存在则插入”且自带行级锁保障原子性。改造CartService.addToCartByUserId()// CartService.java public void addToCartByUserId(int userId, int productId, int quantity) throws SQLException { String sql MERGE INTO dbo.cart_items AS target USING (SELECT ? AS user_id, ? AS product_id, ? AS quantity) AS source ON (target.user_id source.user_id AND target.product_id source.product_id) WHEN MATCHED THEN UPDATE SET quantity target.quantity source.quantity WHEN NOT MATCHED THEN INSERT (user_id, product_id, quantity) VALUES (source.user_id, source.product_id, source.quantity);; try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { conn.setAutoCommit(false); ps.setInt(1, userId); ps.setInt(2, productId); ps.setInt(3, quantity); ps.executeUpdate(); conn.commit(); } catch (SQLException e) { try (Connection conn dataSource.getConnection()) { conn.rollback(); } throw e; } }参数说明MERGE语法中USING (...) AS source构造一行虚拟数据源ON子句定义匹配条件user_id product_idWHEN MATCHED执行UPDATEquantity target.quantity source.quantity实现累加WHEN NOT MATCHED执行INSERT。此写法比先查后插/更安全、高效且避免了应用层的 if-else 分支。5.1 验证 MERGE 是否生效用 SQL Server Profiler 抓取真实执行计划光看 Java 代码不保险必须验证 SQL Server 真的执行了MERGE。启动 SQL Server ProfilerSSMS → 工具 → SQL Server Profiler新建跟踪筛选EventClass SQL:BatchCompleted且TextData LIKE %MERGE%然后在浏览器触发一次添加商品操作。你会看到类似日志exec sp_executesql NMERGE INTO dbo.cart_items AS target ...,NP1 int,P2 int,P3 int,P1101,P2205,P32同时在Messages标签页看到1 row(s) affected—— 如果是MATCHED则UPDATE影响 1 行如果是NOT MATCHED则INSERT影响 1 行。这是你确认原子性落地的唯一铁证。5.2 购物车性能压测用 Apache JMeter 模拟 100 并发用户揪出连接池瓶颈Tomcat 默认连接池最大 20 连接当 100 用户同时抢购必然排队。用 JMeter 验证并调优创建线程组线程数100Ramp-Up Period10秒每秒启动 10 用户添加 HTTP 请求路径/AddToCartServlet?productId1quantity1添加监听器 → 汇总报告观察Average Response Time和Error %若错误率 5%说明连接池不够。回到context.xml调大maxActive50重启 Tomcat 再测。真正的调优不是盲目加数字而是看 JMeter 的 Active Threads Over Time 图——如果线程数曲线在 50 处持平说明连接池已成瓶颈需继续加大如果曲线平滑上升至 100则说明连接池足够瓶颈在 SQL Server 或应用逻辑。我带新人做这个项目时总强调JSP 不是过时技术而是理解 Web 基础的显微镜SQL Server 不是淘汰数据库而是学习事务、锁、执行计划的练兵场。当你亲手把MERGE语句跑通、用 JMeter 看到错误率从 30% 降到 0%那种“原来底层是这么回事”的顿悟比背十道八股文实在得多。希望帮到你。本文还有配套的精品资源点击获取