JSP+SQL Server购物车课设项目:环境配置、代码修改与排障全攻略

发布时间:2026/10/8 12:25:00
JSP+SQL Server购物车课设项目:环境配置、代码修改与排障全攻略 简介这是一份基于Java与SQL Server的JSP购物车Web项目实例面向正在学习Java Web开发的学生、需要完成课程设计的开发者以及想理解JSPJDBC数据库交互的入门者。资源共35个文件包含10个JSP页面覆盖注册、登录、商品浏览、购物车管理、结算等环节13张JPG与3张GIF用于页面展示2个SWF为动画素材另附1个Java源文件及编译生成的class文件以及SQL Server数据库文件MDF/LDF和说明文档TXT整个压缩包仅381KB文件体积轻量方便快速下载查阅。目前已有205人下载学习。项目完整呈现了JSPJDBC操作SQL Server的编码方式能看到添加商品、修改数量、删除商品、结算等核心逻辑的具体实现数据库文件可附加到SQL Server后直接使用便于查看表结构并跟踪数据变化。对理解JavaWeb项目目录组织、JSP常用标签、JDBC连接步骤以及课程设计二次开发都有直接参考价值适合边看边改边调试无论是作为课程作业参考还是自学Java Web数据库编程都可从中获得直观示范。1. JSP(SQLserver) 购物车项目课设毕设里最常见的一包老代码拿到后该信什么、改什么如果你是在课程设计、毕业设计或资源站里碰到 “JSP(SQLserver).rar_JSP 购物车_Javasqlserver_java sqlServer_jsp购物车_” 这个名字基本可以断定这是一包 2012 到 2016 年之间流传开的 JSP SQL Server 购物车项目模板。它的典型组合是 JSP 页面加 JavaBean 加 JDBC 直连 SQL ServerTomcat 做容器Windows 上开发。这套技术栈现在看确实旧但正因为旧链路短、没有框架黑匣子反而适合理解“浏览器请求 → JSP → JavaBean → JDBC → 关系型数据库”这条最原始的 Java Web 链路。这类包能解决的实际问题很具体你需要在几天内交一个能演示“浏览商品、加入购物车、改数量、删除、下单结算”完整流程的系统那它就是最快能改出来的底子。适合正在做课设或毕设的 Java 方向学生也适合想复习 JDBC 与会话管理的老开发。这篇笔记不评价它有多高级只讲清楚它怎么配、怎么改、哪些地方必炸以及拿到手后哪些代码值得留、哪些必须重写。2. 先定环境再动手JDK、Tomcat、SQL Server 版本怎么配对才不会互相拆台2.1 老项目的版本敏感点集中在三处JDK、Tomcat、JDBC 驱动先说实话这类 rar 里写代码的人当年多半用的是 JDK 7 或 8、Tomcat 7 或 8、SQL Server 2008 或 2012。你如果拿 2026 年的最新环境去跑最容易出问题的不是业务代码而是版本错配。JDK 8 是目前兼容性最好的选择Tomcat 8.5 或 9.x 都能跑 javax.servlet 系的老 JSP 项目如果你机器上只剩 JDK 17就得配 Tomcat 9 并把编译目标指到 Java 8否则字节码版本不识别Tomcat 启动时直接给你抛 UnsupportedClassVersionError。SQL Server 版本这块常见的课设模板用的是 Express 版因为免费、安装包小。2012、2014、2016、2017、2019、2022 都可以但有一个真实存在的坑SQL Server 2017 之后微软官方 JDBC 驱动改名成 mssql-jdbc包名和连接串没变但驱动 jar 对 Java 版本有硬性要求。老 rar 里带的 sqljdbc4.jar 连新版本库一般也能连一旦遇到 TLS 或加密协议握手失败就得换新驱动。我的习惯是先看 rar 里有没有驱动 jar有就先不换连不上再换成 mssql-jdbc 对应 Java 8 的版本不要一上来就升级全家桶。另外这类老项目里很多页面带着% page importjava.sql.* %直接在 JSP 里写查询。这确实不规范但你要稳住先让它跑通再谈分层。任何老项目第一次动手的目标都应该是“能原样启动”而不是“一眼看出代码烂然后重构”。重构是跑通之后的事顺序反过来你会在环境问题里挣扎一整天。2.2 标准目录结构与拿到手先备份的三种文件把 rar 解压后你大概率看到两种形态一种是完整打包了 WebContent 或 WebRoot 的 Eclipse 工程里面有 .project 文件另一种是散装的 .jsp 文件加一个 WEB-INF 文件夹。不管哪种第一步都是先把整个目录复制一份绝不碰原包。这是老项目排障的后悔药——你永远不知道哪次改连接串会把目录改到起不来。需要特别留意的三类文件第一是 .sql 结尾的数据库脚本整个项目的建库建表全靠它第二是 WEB-INF/lib 下的驱动 jar常见名字是 sqljdbc4.jar 或 mssql-jdbc-*.jar第三是连数据库的公共类常见文件名是 DBHelper.java、DBUtil.java、db.java它决定你后面改哪里。很多人拿到包后满目录找连接串结果发现连接串散落在十几个 JSP 里那一定是原作者把连接代码到处复制这种项目你要有心理准备改的不是一处而是全局替换。调用链如果没被改坏通常是这样商品列表页 list.jsp 或 index.jsp 查数据库 → 点“加入购物车”跳到 doCart.jsp → 购物车对象存 Session → 结算页面写订单表。打开 web.xml 看欢迎页面和 servlet 映射就能猜到这个包的路由。如果项目完全用 JSP 跳 JSP、没有 servlet也别意外老课设里大半都这样。判断完路由再动手改代码你的效率会高很多。2.3 用一段可重复执行的 SQL 把数据库建好建表脚本与字段设计要点不要依赖 rar 里自带的“附加数据库”方式那要求数据文件版本和你的 SQL Server 一致版本不匹配时附加会直接失败。更稳的做法是拿到 .sql 脚本后用 sqlcmd 在命令行执行一次得到一份干净可用的库。如果包里没有脚本就按下面这个最简结构重写它覆盖了购物车业务需要的最小表集合商品表、订单表、订单明细表。-- 建库库名写 shop后面 JDBC 连接串保持一致 IF DB_ID(shop) IS NULL CREATE DATABASE shop; GO USE shop; GO -- 商品表老项目常叫 books按原项目表名来 IF OBJECT_ID(books, U) IS NOT NULL DROP TABLE books; CREATE TABLE books ( id INT IDENTITY(1,1) PRIMARY KEY, name NVARCHAR(100) NOT NULL, -- 用 NVARCHAR避免中文乱码 price DECIMAL(10,2) NOT NULL, -- 金额用 DECIMAL别用 FLOAT stock INT NOT NULL DEFAULT 0, cover NVARCHAR(255) NULL -- 存图片相对路径不要存二进制 ); -- 订单表 IF OBJECT_ID(orders, U) IS NOT NULL DROP TABLE orders; CREATE TABLE orders ( order_id INT IDENTITY(1,1) PRIMARY KEY, user_name NVARCHAR(50) NOT NULL, total DECIMAL(10,2) NOT NULL, create_time DATETIME NOT NULL DEFAULT GETDATE() ); -- 订单明细表一个订单多件商品 IF OBJECT_ID(orderdetail, U) IS NOT NULL DROP TABLE orderdetail; CREATE TABLE orderdetail ( detail_id INT IDENTITY(1,1) PRIMARY KEY, order_id INT NOT NULL, book_id INT NOT NULL, quantity INT NOT NULL, price DECIMAL(10,2) NOT NULL -- 下单时快照单价防止之后改价影响历史订单 );这套脚本里最值得说的是 NVARCHAR 和 DECIMAL 的选择。老项目很多表用 VARCHAR 和 FLOATVARCHAR 在 SQL Server 默认排序规则下存中文并不一定炸但一旦排序规则是 Latin1_General中文全部变问号FLOAT 存金额是精度灾难累加次数多了就会出现 0.999999 之类的尾差。如果你接手的老表里已经是 VARCHAR并且页面乱码优先考虑改字段类型而不是只改页面编码。执行脚本的命令在 Windows 上是sqlcmd -S 127.0.0.1 -U sa -P 你的密码 -i D:\shop.sql。sqlcmd 本身能处理中文脚本但文件编码必须是 UTF-8 with BOM 或 ANSI否则中文字段名和注释可能被读成乱码报错。这也是老 rar 里脚本第一次执行就报错的隐藏原因之一遇到语法错误先检查文件编码别急着怀疑语句。3. 拆开购物车核心连接串、CartItem 与 Cart 类的三块必改代码3.1 DBHelper连接串里藏着 80% 的连库故障购物车项目的连库代码就是一锤子买卖。不管原作者写的是 Statement 直连还是封装了 DBHelper你要改的核心内容是一致的驱动类名、连接 URL、用户名密码。下面是一个在 Java 8 下可用的 DBHelper 写法兼容 sqljdbc4.jar 和新版 mssql-jdbc 驱动。import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class DBHelper { // 驱动类名老驱动和新版驱动都是这个变的是 jar 而不是类名 private static final String DRIVER com.microsoft.sqlserver.jdbc.SQLServerDriver; // 连接串encryptfalse 是为了兼容老驱动和本地开发环境 private static final String URL jdbc:sqlserver://127.0.0.1:1433;DatabaseNameshop;encryptfalse;trustServerCertificatetrue; private static final String USER sa; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(缺少 SQL Server JDBC 驱动请把 jar 放入 WEB-INF/lib); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } // 关闭资源Connection、Statement、ResultSet 都要关顺序从后往前 public static void close(ResultSet rs, Statement stmt, Connection conn) { try { if (rs ! null) rs.close(); } catch (SQLException ignored) {} try { if (stmt ! null) stmt.close(); } catch (SQLException ignored) {} try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }逻辑说明这里把驱动加载放在静态块里目的是让类第一次被引用时就报错而不是等到执行查询才报“ClassNotFoundException”这样 Tomcat 日志里能一眼定位到缺驱动。encryptfalse是重点——SQL Server 2016 之后默认强制加密连接老 jar 不支持加密协商时会直接抛 SSL 错误显式关掉加密是兼容老项目的标准操作。trustServerCertificatetrue则配合新驱动在本地自签名证书环境下使用两个参数一起写能少一半连接报错。参数说明三件事一是密码里如果带特殊字符比如;或建议用 DriverManager 传参数而不是拼进 URL避免字符串被截断二是DatabaseName要和 sqlcmd 里 use 的库名保持一致大小写无所谓三是如果连的是命名实例比如localhost\SQLEXPRESSURL 要写成jdbc:sqlserver://localhost:1433;instanceNameSQLEXPRESS;DatabaseNameshop。但我更建议把默认实例端口固定在 1433少一个变量就少一次玄学排错。3.2 CartItem 与 Cart购物车不一定要用 Map但两个方法必须写对购物车的本质是会话级别的一个内存对象集合。老项目常见写法是用 HttpSession 存一个ListCartItem每次 add 都遍历列表去重。能跑但改数量和删商品要写大量循环。我习惯用一个 Cart 类包住集合让 JSP 只调方法、不碰集合细节页面能干净一截。import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; // 购物车条目对应购物车里的一行商品 public class CartItem { private int id; private String name; private BigDecimal price; // 单价用 BigDecimal 而不是 double private int quantity; // 数量 private String cover; // 商品图片路径可空 // 小计 单价 * 数量保留两位小数 public BigDecimal getSubtotal() { return price.multiply(BigDecimal.valueOf(quantity)) .setScale(2, BigDecimal.ROUND_HALF_UP); } // 这里省略 getter/setter生成时注意 price 的 setter 要接受 BigDecimal public int getId() { return id; } public void setId(int id) { this.id id; } public String getName() { return name; } public void setName(String name) { this.name name; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price price; } public int getQuantity() { return quantity; } public void setQuantity(int quantity) { this.quantity quantity; } public String getCover() { return cover; } public void setCover(String cover) { this.cover cover; } }import java.math.BigDecimal; import java.util.ArrayList; import java.util.Iterator; import java.util.List; // 购物车用 List 保持加入顺序并提供增删改查的简化入口 public class Cart { private ListCartItem items new ArrayListCartItem(); // 加入购物车已有同 id 商品则加数量否则新增条目 public void add(CartItem item) { for (CartItem it : items) { if (it.getId() item.getId()) { it.setQuantity(it.getQuantity() item.getQuantity()); return; } } items.add(item); } // 修改数量数量小于等于 0 时按删除处理避免页面出现数量为 0 的垃圾行 public void updateQuantity(int id, int quantity) { IteratorCartItem iter items.iterator(); while (iter.hasNext()) { CartItem it iter.next(); if (it.getId() id) { if (quantity 0) { iter.remove(); } else { it.setQuantity(quantity); } return; } } } // 删除指定商品 public void delete(int id) { IteratorCartItem iter items.iterator(); while (iter.hasNext()) { if (iter.next().getId() id) { iter.remove(); return; } } } public void clear() { items.clear(); } // 总价用 BigDecimal 累加避免浮点误差 public BigDecimal getTotal() { BigDecimal total BigDecimal.ZERO; for (CartItem it : items) { total total.add(it.getSubtotal()); } return total.setScale(2, BigDecimal.ROUND_HALF_UP); } public ListCartItem getItems() { return items; } // 商品总件数按数量加总 public int getCount() { int count 0; for (CartItem it : items) { count it.getQuantity(); } return count; } }逻辑说明Cart 用ListCartItem而不是MapInteger, CartItem是为了 JSP 遍历时保持商品加入顺序也方便直接拿到数量和小计。add 方法里“已存在则加数量”这条规则是购物车的基本行为——很多老项目翻车的点恰恰是每次加入都新增一行最后同一个商品在列表里出现三行。updateQuantity 里把数量小于等于 0 当删除这个约定要和前端页面同步否则用户把数量改成 0 时看到列表没反应会以为自己点了个假购物车。参数说明金额类型从第一行就用 BigDecimal单价乘法结果用 setScale(2, ROUND_HALF_UP) 固定两位小数。这和数据库里的 DECIMAL(10,2) 正好对上不会出现 1.1 2.2 等于 3.3000000000000003 这种让答辩老师皱眉的结果。需要说明的是如果原项目用了 FLOAT你要在赋值时先用BigDecimal.valueOf(doubleValue)转换否则直接把 double 塞进 BigDecimal 构造器反而会把浮点尾差带进来。3.3 把购物车接进 JSPSession 读取、加入、改数量与结算购物车对象要跨多个页面存放位置只有一个正确选择HttpSession。下面这段示意代码在商品页点击“加入购物车”时执行如果原项目用 JSP 直接处理就放在 doCart.jsp 顶部。% page contentTypetext/html;charsetUTF-8 languagejava % % // 1. 从 Session 取购物车没有则新建一个 Cart cart (Cart) session.getAttribute(cart); if (cart null) { cart new Cart(); session.setAttribute(cart, cart); } // 2. 从商品页传入的参数bookId 商品 idbookName 名称bookPrice 单价 int bookId Integer.parseInt(request.getParameter(bookId)); String bookName request.getParameter(bookName); BigDecimal bookPrice new BigDecimal(request.getParameter(bookPrice)); String cover request.getParameter(cover); // 3. 组装条目并加入购物车 CartItem item new CartItem(); item.setId(bookId); item.setName(bookName); item.setPrice(bookPrice); item.setQuantity(1); item.setCover(cover); cart.add(item); // 4. 重定向回商品列表避免刷新页面时重复提交 response.sendRedirect(list.jsp); %逻辑说明这个片段演示了购物车操作的“入参 → 组装 → 加车 → 重定向”四步。最后的response.sendRedirect很关键如果不重定向而只是 forward 或留在原地用户按 F5 刷新就会重复加入同一个商品。所有会改变购物车状态的操作都应该做成“请求后重定向”这是老项目最容易忽略的细节也是你答辩时能讲出“我考虑了幂等性”的加分点。购物车列表页面负责展示和改数量下面用 JSTL 写因为它干净但老项目里很多是 Scriptlet 直接 for 循环你保持原样也能跑。如果要用 JSTL记得让 lib 下同时存在 jstl.jar 和 standard.jar否则c:forEach直接 500。%-- cart.jsp 核心表格片段遍历 cart.items每行一个商品支持改数量和删除 --% table tr th封面/thth书名/thth单价/thth数量/thth小计/thth操作/th /tr c:forEach varit items${cart.items} tr tdimg src${it.cover} width50//td td${it.name}/td td${it.price}/td td form actionupdate.jsp methodpost styledisplay:inline; input typehidden namebookId value${it.id}/ input typetext namequantity value${it.quantity} size2/ input typesubmit value改/ /form /td td${it.subtotal}/td tda hrefdelete.jsp?bookId${it.id} onclickreturn confirm(确定删除?);删除/a/td /tr /c:forEach tr td colspan6总计¥ ${cart.total}共 ${cart.count} 件/td /tr /table这里的 update.jsp 逻辑就是把 Cart 取出来调cart.updateQuantity(Integer.parseInt(request.getParameter(bookId)), Integer.parseInt(request.getParameter(quantity)))然后重定向回 cart.jsp。delete.jsp 同理调cart.delete(id)。注意所有页面顶部都要先Cart cart (Cart) session.getAttribute(cart);如果为 null 就重定向到 list.jsp防止直接访问 update.jsp 时空指针。参数说明价格用${it.price}输出时BigDecimal 的 toString 会把整数输出成2而不是2.00如果页面要求统一两位小数可以在页面里用fmt:formatNumber value${it.price} pattern0.00/。这个细节不影响功能但影响观感答辩时页面整齐一点总没错。4. 把 rar 变成本地产物建库、部署、启动的三段式操作与验证4.1 目录整理把解压文件恢复成 Tomcat 认识的 Web 应用rar 解压后的目录经常是乱的最常见的是整套文件套在一层以项目名命名的文件夹里或者源码在 src 目录下但 class 文件没编译好。Tomcat 认的 Web 应用结构其实很简单应用根目录下面直接放 JSP 文件、CSS、图片以及一个名为 WEB-INF 的文件夹WEB-INF 下面必须有 web.xml、classes 存放编译后的 class 文件、lib 存放第三方 jar。理解了这四层结构你就知道把哪些东西往哪里放。# 假设解压后的目录叫 shop先看清里面是什么 ls -la shop # 如果 JSP 在下一层子目录里把它提上来 # 常见结构shop/shop/xxx.jsp需要把内层 shop 里的内容移动到外层 mv shop/shop/* shop/ rmdir shop/shop # 建标准目录 mkdir -p shop/WEB-INF/classes mkdir -p shop/WEB-INF/lib # 把编译好的 class 放进去。如果只有 .java需要先 javac 编译 # 编译时注意带上驱动 jar否则 import com.microsoft.sqlserver 会报错 javac -encoding UTF-8 -cp shop/WEB-INF/lib/sqljdbc4.jar \ -d shop/WEB-INF/classes \ shop/src/DBHelper.java shop/src/Cart.java shop/src/CartItem.java # 确认最终结构JSP 和 WEB-INF 必须同级 ls -la shop逻辑说明这段命令处理的是最典型的“套娃目录”和“只有源码没有 class”两种情况。很多老项目把 .java 文件放在 src 或 src/model 目录里但 Tomcat 不会去编译 src它只认 WEB-INF/classes 下已经编译好的字节码文件。所以拿到包后先找有没有包好的 class没有就按上面的 javac 命令手动编译。参数说明-encoding UTF-8必须带否则老项目里的中文注释在编译时可能变成乱码虽然不影响运行但报错信息会变得不可读。-cp参数里的驱动 jar 路径要指向实际位置如果你还没有把 jar 放进 lib先放进去再编译。编译后如果还有 servlet 类javac 还需要 servlet-api.jar它在 Tomcat 的 lib 目录下编译时把%TOMCAT_HOME%/lib/servlet-api.jar也加进 classpath否则 HttpServlet 找不到。如果你不习惯命令行编译也可以在 Eclipse 或 IDEA 里把项目以 Web 项目方式导入再导出成 war 包。但我个人的血泪经验是老项目 IDE 重建工程经常会触发一堆原来没有的报错反而是手动整理目录加 javac 最快因为你能精确控制每一步。4.2 SQL Server 安装与登录模式配置管理器在这里的真实作用SQL Server 装完之后购物车项目要能连上必须满足三个条件服务在运行、TCP/IP 协议启用、登录模式允许 sa 或你指定的账号远程登录。这三个条件缺一个JDBC 连接时都会报“连接被拒”或“登录失败”而且报错信息很有迷惑性常在“Connection refused”和“Login failed for user sa”之间来回跳。# 1. 确认 SQL Server 服务状态服务名通常类似 MSSQLSERVER services.msc # 2. 启动 SQL Server 配置管理器找到“MSSQLSERVER 的协议” # 右键启用 TCP/IP再双击进入 IP 地址页签往下翻到 IPAll # 把 TCP 动态端口清空TCP 端口填 1433然后重启服务 # 这一步不执行JDBC 连接串里的 :1433 就是摆设 # 3. 验证端口监听正常 netstat -ano | findstr 1433 # 4. 用 sqlcmd 验证本地登录能出现 1 提示符就说明账号没问题 sqlcmd -S 127.0.0.1 -U sa -P 你的密码 SELECT VERSION; GO EXIT逻辑说明SQL Server 配置管理器这个工具热词里很多人搜“sqlserver配置管理器安装”其实是装完 SQL Server 后自带的不需要单独安装在开始菜单里能搜到。TCP/IP 协议默认可能是关闭的因为 SQL Server 默认只开 Shared Memory 和 Named PipesJDBC 走的是 TCP不启用必然连接失败。把 TCP 动态端口清空、固定 1433是为了让连接串里的端口号有实际意义。参数说明sa 密码在安装时如果没设或忘了可以用 Windows 身份登录后用 SQL 语句改。但要注意老项目里连接串写死了 sa你如果不想用 sa就得把 DBHelper 里的 USER 和 PASSWORD 改成你自己的账号。另一个坑是混合登录模式没有开启时即使密码对SQL Server 也会拒绝 sa 登录。安装后想改模式用 SSMS 连接“服务器属性 → 安全性 → SQL Server 和 Windows 身份验证模式”改完必须重启 SQL Server 服务才生效这个动作忘了能做秃头。4.3 Tomcat 部署启动与验证从 startup.bat 到浏览器出现商品列表Tomcat 这块比 SQL Server 简单但要养成看日志的习惯。很多老项目在 IDEA 里点一下就报错回到 Tomcat 反而稳定因为 Tomcat 的部署路径是最标准的 webapps。# 1. 把项目文件夹直接复制到 Tomcat 的 webapps 下 cp -r shop %TOMCAT_HOME%/webapps/shop # 2. 启动 TomcatWindows 下执行 startup.batLinux 下执行 startup.sh cd %TOMCAT_HOME%/bin startup.bat # 3. 等 5 秒左右检查启动日志 type %TOMCAT_HOME%/logs/catalina.日期.log # 4. 在浏览器访问项目首页 # 如果你的 web.xml 里欢迎页是 list.jsp直接访问 curl http://127.0.0.1:8080/shop/list.jsp逻辑说明把项目文件夹复制到 webapps 下Tomcat 启动时会自动识别这个目录并发布为上下文上下文名就是目录名也就是 URL 里的 /shop 这一段。注意老项目里如果 WEB-INF/web.xml 不存在Tomcat 8.5 及以上也能启动但会按默认配置走可能没有欢迎页我建议保留 web.xml哪怕内容很简单。参数说明访问时看到 404优先看两处一是看webapps/shop目录下是否有 index.jsp 或者匹配 web.xml 欢迎页的文件二是看http://127.0.0.1:8080/shop/这个不带文件名的地址Tomcat 会按欢迎页顺序找 index.jsp、index.html。如果项目里商品列表叫 list.jsp你就直接访问 list.jsp不用纠结欢迎页。另外修改 JSP 后不用重启 Tomcat但修改 DBHelper.java 后必须重新编译并重启这个区别很多人会忘记白白浪费时间。验证是否成功按这个清单走一遍第一浏览器能看到商品列表且中文不乱码第二点“加入购物车”后右上角或页面上出现数量累加第三直接在浏览器地址栏访问 cart.jsp能打开购物车页面而不是 500第四改数量为 0点“改”商品行消失总价同步变化第五开一个无痕窗口再访问一次购物车是空的互不影响。这五条过了你的部署就算真正成功。5. JSP 购物车最容易翻车的 5 个地方现象、原因与解决5.1 SQL Server 安装失败提示无法找到数据库引擎启动句柄现象安装 SQL Server 2016/2017/2019 时进度条走到“数据库引擎服务”就报错错误里出现“无法找到数据库引擎启动句柄”或“数据库引擎启动失败”。很多人以为是自己下载的安装包有问题反复重装三次还是同一个地方挂掉。原因这个报错绝大多数和安装包无关而是安装时服务账户权限不足或者机器上之前装过 SQL Server 留下的实例和目录残留冲突。Windows 服务需要用本地系统账户或具有“作为服务登录”权限的账户启动普通管理员账户在 UAC 加持下也可能启动不了服务。解决先别急着重装。打开“服务”找到 MSSQLSERVER如果状态是已停止尝试手动启动并查看报错如果是“找不到句柄”这类系统级错误检查 Windows 事件查看器里 SQL Server 的错误日志。常见有效操作是右键安装程序“以管理员身份运行”安装时服务账户选择 NT AUTHORITY\SYSTEM如果之前装过在安装中心里选“修复”而不是“卸载重装”。我处理过几次同类问题最后都是修复安装或者清掉残留实例解决重装是最笨的一条路。5.2 驱动 jar 在但启动还是报 ClassNotFoundException或者 No suitable driver现象WEB-INF/lib 下明明有 sqljdbc4.jarTomcat 一启动DBHelper 静态块直接抛“ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriver”。查 lib 目录文件确实在但就是加载不到。原因两种典型。第一种是 jar 放到了 WEB-INF 的其他位置比如 classes 目录里Tomcat 的类加载器不会扫描 classes 下的 jar只扫描 lib 目录第二种是 jar 文件损坏或者从网盘下载下来被安全软件拦了一半字节数不对。解决先用压缩软件打开 jar 确认里面有没有com\microsoft\sqlserver\jdbc\SQLServerDriver.class这一步能排除“假 jar”。然后确认 jar 位置是webapps/shop/WEB-INF/lib/而不是webapps/shop/lib/或WEB-INF/classes/。还有一处容易被忽视如果你同时把 sqljdbc4.jar 和 mssql-jdbc-10.x.jar 都放进 lib两个驱动类名一样会随机冲突只留一个。这类问题最好排查但遇到过的人都很崩溃因为它属于“文件明明在就是不加载”的玄学现场。5.3 连接被拒TCP/IP 协议被禁用或者端口被改现象Tomcat 能启动页面能打开但一执行查询就报The TCP/IP connection to the host 127.0.0.1, port 1433 has failed或者干脆“Connection refused”。浏览器访问正常数据库却连不上。原因第一是 SQL Server 配置管理器里 TCP/IP 协议还是禁用状态JDBC 这一路的连接根本进不来第二是 SQL Server 改了默认端口连接串里的 1433 指向空端口第三是 SQL Server Express 默认是动态端口每次服务重启端口都可能变。解决打开 SQL Server 配置管理器双击“MSSQLSERVER 的协议”下的 TCP/IP进入“IP 地址”页签把 IPAll 里的“TCP 动态端口”清空在“TCP 端口”填 1433确定后重启服务。然后执行netstat -ano | findstr 1433看到 LISTENING 再跑项目。如果机器上有多个 SQL Server 实例还要确认连的是不是默认实例命名实例要加instanceName参数。排查顺序永远是服务状态 → 协议状态 → 端口监听 → 登录账号这个顺序能避开 90% 的连接问题。5.4 中文商品名显示成问号数据库、页面、请求三层都要查现象商品列表里中文正常加入购物车后购物车页面里的中文变成????或者反过来列表乱码、购物车正常。更诡异的是在 SQL Server Management Studio 里直接查表数据是好的浏览器一显示就乱。原因购物车里的中文来自两个地方——数据库查出来的字段以及加入购物车时浏览器通过 GET 请求传过来的参数。数据库那边表字段是 VARCHAR 且排序规则不兼容中文时读出来本身就可能是乱码页面这边JSP 的 pageEncoding 没设成 UTF-8或者 Tomcat 8 之前版本对 GET 请求的默认编码是 ISO-8859-1传入的中文参数到后端就变了。解决按三层排查。第一层把 books 表里 name 字段改成 NVARCHAR老数据要ALTER TABLE books ALTER COLUMN name NVARCHAR(100)第二层所有 JSP 页面头部检查pageEncodingUTF-8和charsetUTF-8第三层如果是 Tomcat 7 或更老修改%TOMCAT_HOME%/conf/server.xml在 Connector 上增加URIEncodingUTF-8否则 GET 参数里的中文在 Servlet 后端拿到就是乱的。改完重启 Tomcat再刷新一次。这个坑的特点是三层混在一起只改一层永远差一点必须三层一起确认。5.5 购物车一刷新就丢或者加入后数量重复累加现象把商品加入购物车后点浏览器刷新购物车数量翻倍或者换个页面再回来购物车空了。两个现象看着相反其实都在 Session 处理上。原因刷新丢购物车是 Session 超时时间太短或者启动 Tomcat 后改过代码导致应用 reloadSession 被清掉了。刷新数量翻倍是加入购物车的请求没有重定向F5 在重复提交同一个 POST/GET 请求每次刷新都把同一个商品再 add 一次。解决重复提交要在业务层守规矩——所有写操作做完用response.sendRedirect跳到读页面而不是直接 forward 或停留在当前页。购物车被清空先检查 Tomcat 默认 Session 超时时间默认 30 分钟一般够用如果是项目里手动调过session.setMaxInactiveInterval改成 1800 秒试试。还有一个隐藏触发点修改 JSP 后 Tomcat 会自动重载应用开发阶段 Session 会丢得很快这不算代码 bug但答辩演示时突然丢了会很尴尬所以演示前别改 JSP 文件。6. 从“跑通”到“扛住答辩”给老购物车补上两层验证6.1 会话与金额的边界验证演示前必须自己先打一遍跑通不等于能演示。答辩现场最容易暴露的其实是“边界场景”而不是正常流程。我在演示老购物车项目前习惯自己先按这么一套打一遍先用浏览器正常买三件商品确认总价然后开无痕窗口确认购物车互不干扰再把某个商品数量改成 0确认行消失最后把购物车清空确认空状态页面不报错。这套流程走完基本能保证现场不翻车。金额精度也要验证买 0.99 和 1.01 的商品各一件总价应该精确显示为 2.00。如果你的项目用了 FLOAT这里会出 2.00 或 1.9999 之外的诡异结果。还有一个容易忽视的点直接访问 update.jsp 和 delete.jsp 时如果 Session 里没有 cart后端会怎么处理很多老项目没写判空直接空指针 500。你提前加上if (cart null) { response.sendRedirect(list.jsp); }这行代码就能拦住一次现场事故。6.2 三处值得做的小升级连接池、落库与分页如果你时间富余不要大改架构只做三处小升级就足够加分。第一把 DBHelper 换成数据库连接池DBCP 或 HikariCP 都行关键改动是把每次DriverManager.getConnection换成从池里借连接用完归还。这句话写在答辩讲稿里比“我熟悉 JDBC”有说服力得多。第二把购物车结算时写入订单的代码改为事务控制订单表和明细表同生共死只要一条插入失败就整体回滚这是购物车项目里最值得讲的事务场景。第三商品列表加分页老项目的分页通常是top 20加not in这在 SQL Server 2012 以上可以换成OFFSET skip ROWS FETCH NEXT take ROWS ONLY顺便能讲清楚两种分页在数据边界上的差异。结尾想跟你说点实在的我每次收到这类老项目压缩包第一件事不是解压跑而是先看 web.xml、看 lib、看合同里有没有驱动和 SQL 脚本等于先把要踩的坑预期一遍再动手。这一点习惯帮我躲过了很多次通宵排障也希望能帮到你。本文还有配套的精品资源点击获取