ARTICLE DETAIL

资讯详情

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

JSP购物车项目部署实战:从SQL Server附加到JDBC连接与Session会话管理

JSP购物车项目部署实战:从SQL Server附加到JDBC连接与Session会话管理 简介基于Java与SQL Server的JSP购物车项目面向Java Web初学者和数据库开发爱好者完整演示了从用户注册、登录、商品浏览到购物车增删改、结算下单的闭环流程也覆盖了常见电商页面的交互细节。压缩包共35个文件体积约381KB包含10个JSP页面、Java源码与class文件、SQL Server数据库文件及日志文件还提供图片、动画等页面素材后端业务逻辑与前端界面布局均可学习。当前已有203人浏览学习。资源附有数据库备份和文本说明可对照学习数据库连接、预编译防注入、会话临时存储购物车、订单持久化等关键实践目录按登录注册、商品展示、购买处理等模块划分适合在课程设计、毕业设计或Java Web入门阶段作为完整项目范本。购物车功能支持添加、修改数量、删除和清空操作整体结构紧凑便于按页面和模块拆解研究能帮助快速建立Web开发全局认识。1. 这个 JSP 购物车除了老恰好是 JavaSQLServer 最清楚的实战样板“JSP购物车(SQLserver版)”不是新项目甚至是 2008 年前后端一体的老典型注册、登录、商品浏览、加购、结算一条线全用 JSP 和 SQL Server 实现。对正在找 JSP 课程设计或毕业设计源码的人来说它的价值恰恰在“老”——没有 Spring没有 MyBatisJDBC 裸连数据库购物车直接扔在 Session 里每一步逻辑都摊在明面上。只想补 Java Web 基本功的开发者也值得把它当作一个能跑通的完整闭环节来拆。这个包里的东西很杂JSP 页面十几个、SQL Server 的 MDF 数据库文件、编译好的 class、一堆图片和 Flash 素材。看着乱但只要按“页面—数据—依赖”三类分一遍整个购物车的骨架就出来了。2. 拆压缩包还原购物车全流程文件命名里藏着完整业务闭环2.1 先拆包页面、数据、依赖三类资源对应三个层一个压缩包到手别急着往 IDE 里导。我拆这种老项目的习惯是先对文件分拣再看代码。按职责分这个包可以切成三类。第一类是 JSP 页面。index.jsp 是首页入口list.jsp 是商品列表展示login.jsp 负责登录register.jsp 配合 do_register.jsp 完成注册流程register_confirm.jsp 是注册成功回执purchase.jsp 是购买结算页。这些页面共同组成购物车系统的表现层。第二类是数据资源。data 目录下的 Web_Data.MDF 是 SQL Server 主数据文件Web_Log.LDF 是配套事务日志。商品、用户、订单都在这两个文件里属于数据层。第三类是依赖和辅助资源。classes--bean 目录放编译好的 class 字节码一般是数据库连接工具类和购物车相关的 JavaBeanimages 和 pic 目录下的 gif、jpg 是页面素材8.swf、lucky.swf 是 Flash 动画read me.txt 和 mycodes.net说明.txt 是部署提示。这个包还带了个“源码之家.url”那只是网站快捷方式跟代码运行无关。从完整度看这是个“能跑”的课程设计标配。唯一缺的是标准 Web 工程的 WEB-INF 目录也就是说它没有 web.xml不能直接扔进 Tomcat 的 webapps 就跑需要搭一层工程骨架。这个后文部署章节会专门处理。2.2 顺着文件名串业务流注册到结算每个环节由谁承担拿到这套 JSP 文件其实不用看代码就能把购物车闭环还原出来。用户进站先落在 index.jsp 或 list.jsp商品数据从 SQL Server 查出来渲染在页面上。用户点“加入购物车”商品写入 Session 中的购物车结构再点“去结算”进入 purchase.jsp。登录页没有独立的处理文件说明 login.jsp 既展示登录表单又处理 POST 请求——这是老 JSP 项目很常见的写法一个页面身兼两职。注册链路更清晰register.jsp 展示表单提交到 do_register.jsp 做参数校验和数据库插入最后跳转到 register_confirm.jsp 显示注册成功。页面之间的跳转关系靠文件名就能串起来这也是老项目比现在 Spring Boot 项目好读的地方没有路由层URL 和 JSP 文件一一对应。有个细节值得拿出来说包里同时存在 purchase.jsp、原来purchase.jsp、刚改purchase.jsp 三个购买相关文件。这是原作者改结算页时一版一版备份留下的痕迹。拆包时别当重复文件删三个都留着页面出问题时还能拿旧版本对照。2.3 MDF 与 LDF购物车的数据底座和它的历史包袱Web_Data.MDF 是购物车系统真正的数据主力。按这类项目的通用设计数据库里至少有三组核心表用户表、商品表、订单表。表名可能是 users、goods、orders 之类具体以附加数据库后看到的为准。商品表支撑 list.jsp 的展示用户表配合 register 和 login订单表在 purchase.jsp 结算时写入。老项目直接给 MDF 而不是建库脚本对使用者来说其实是省事的。拿到 MDF 附加到 SQL Server 实例就能看到表结构不用手工建库建表。但这里埋了个历史包袱MDF 文件内部有版本号老版本 SQL Server 生成的库新版本实例未必认。这一点会直接影响部署成败放到第 5 章细说。Web_Log.LDF 是事务日志文件。SQL Server 做崩溃恢复、事务回滚都依赖它。附加时 MDF 和 LDF 最好放在同一目录文件名保持对应关系单独拿 MDF 去附加很大概率会报“日志文件找不到”。3. 部署到 Tomcat SQL Server附加数据库到 JDBC 连接串全流程3.1 环境选型JDK 8、Tomcat 8.5 配合 SQL Server 2019 的稳定组合老项目跑在现代环境上版本不能无脑追新每一步都要考虑兼容性。JDK 选 8。老 JSP 项目绝大多数基于 Java 6/7 语法写的JDK 8 完全向下兼容跑起来不会有语法层面的问题。JDK 11 以后模块化了部分 API老代码偶尔会踩到已删除类的坑没必要冒险。Tomcat 选 8.5 或 9.0。这两个版本仍然用 javax.servlet 包名和老代码匹配。Tomcat 10 以后换成了 jakarta.servlet老项目的 Servlet、Filter、标签库全部会报类找不到。这个坑太典型了不少人在第一步就把时间烧在这里。SQL Server 看情况。手边是 SQL Server 2019 或 2022 可以先尝试附加MDF 版本够新就能挂上。附加失败的话备选方案是找一台 SQL Server 2008 R2 或 2012 的机器把库升级后再迁移出来。SQL Server 本身向下兼容做得一般老库往新实例搬经常要过一道中转。3.2 把 Web_Data.MDF 附加进 SQL Server图形操作与 SQL 语句附加数据库有两条路。有 SSMS 图形界面的话右键“数据库”节点选“附加”把 Web_Data.MDF 加进去确认日志文件路径正确就行。用 T-SQL 也一样。先把两个文件放到一个干净目录比如 D:\sqldata\然后执行-- 把文件放到 D:\sqldata\ 目录下 CREATE DATABASE Web_Data ON (FILENAME ND:\sqldata\Web_Data.MDF) FOR ATTACH;如果提示日志文件无法匹配可以试试让 SQL Server 重建日志CREATE DATABASE Web_Data ON (FILENAME ND:\sqldata\Web_Data.MDF) FOR ATTACH_REBUILD_LOG;FOR ATTACH_REBUILD_LOG 会忽略原有日志文件内容根据数据文件重建 LDF。这个选项适合 MDF 完整、LDF 损坏或路径对不上的场景。附加完用下面这句确认数据库确实在线SELECT name, state_desc FROM sys.databases WHERE name Web_Data;state_desc 返回 ONLINE 就说明库已经挂上。这一步别跳过附加操作经常看着成功实际库还是 OFFLINE 状态。很多后续 JDBC 连接超时问题源头就在这里。3.3 JDBC 驱动与连接串老驱动类名和新连接写法不能混用JSP 项目连 SQL Server驱动是绕不开的一环。包里的 classes--bean 如果还带着老驱动类名大概率是 com.microsoft.jdbc.sqlserver.SQLServerDriver连接串前缀是 jdbc:microsoft:sqlserver://……。这套老驱动在当年的 SQL Server 2000/2005 上能用放到现在的 SQL Server 2019 上很可能直接连不上而且微软早就停止维护了。我一般直接换成微软官方新版驱动 mssql-jdbc。本地没有 jar 的话去 Maven 中央仓库下载 mssql-jdbc 7.4.1.jre8.jar或者任何支持 Java 8 的后续版本都行。驱动放进项目的 WEB-INF/lib 目录连接代码这样写Class.forName(com.microsoft.sqlserver.jdbc.SQLServerDriver); String url jdbc:sqlserver://localhost:1433;DatabaseNameWeb_Data;encryptfalse;trustServerCertificatetrue; String user sa; String password 123456; Connection conn DriverManager.getConnection(url, user, password);连接串里几个参数各有用途。jdbc:sqlserver 是新驱动的固定前缀和老的 microsoft:sqlserver 有区别拼错一个就报找不到驱动程序。encryptfalse 关闭加密传输老 SQL Server 实例没有配置证书时这个参数能省掉一大堆 SSL 握手问题。trustServerCertificatetrue 表示即使证书不可信也继续连接本地开发用没问题。DatabaseName 指定要访问的库名对应刚才附加的 Web_Data。注意一个细节新版驱动的 jar 不要用 Maven 自动下载的方式引入老项目在 IDEA 里手工引 jar 到 WEB-INF/lib 更稳。网上不少“idea sqlserver jdbc 自动下载失败”的求助帖多半是网络受限加项目结构不标准两个问题叠在一起。3.4 部署进 Tomcat手工补 WebRoot 与 WAR 打包两种方式这个压缩包没有标准 Eclipse 工程结构没有 src 目录也没有 WebRoot/WEB-INF/web.xml。要让它在 Tomcat 里跑起来我通常走两条路之一。第一条是在 IDE 里新建一个 JavaWeb 空工程把包里的 JSP 页面全部拷贝到 WebRoot或 webapp根目录classes--bean 里的 class 文件拷到 WEB-INF/classes驱动 jar 放 WEB-INF/lib。web.xml 这个文件得手工建里面至少声明 welcome-file 指向 index.jsp有需要还可以配置 Filter、Servlet。第二条更直接绕开 IDE直接用 Tomcat 跑。进入 Tomcat 的 webapps 目录新建一个文件夹叫 cart把 JSP 文件全放进去在 WEB-INF 下手动建 classes 和 lib 两个子目录。目录结构大概是webapps/cart/ ├── index.jsp ├── list.jsp ├── login.jsp ├── register.jsp ├── do_register.jsp ├── register_confirm.jsp ├── purchase.jsp ├── images/ └── WEB-INF/ ├── web.xml ├── classes/ └── lib/然后把 Tomcat bin 目录下的 startup.bat 跑起来访问 http://localhost:8080/cart/index.jsp 就能看到首页。这种方式适合不想被 IDE 折腾的人改一个文件刷新一次页面调试效率反而高。如果最后要交付的是 WAR 包记得在打包时排除数据库文件。Web_Data.MDF 和 Web_Log.LDF 是本地数据文件打进去只会让压缩包体积变大部署时还得重新附加属于纯添乱。4. 走读购物车核心逻辑Session 的临时存储与结算落库的完整衔接4.1 购物车数据结构Session 里放 HashMap 还是 ListJSP 购物车的信息存储分两段用户浏览未结算时放在 Session确认结算后写入数据库。Session 是 HTTP 无状态协议下的临时会话存储购物车天然适合放这里。这类老项目最常见的结构是直接在 Session 里挂一个 HashMap键是商品 ID值是购买数量。用 HashMap 而不是 List 的原因很直接加购同一件商品时HashMap 只需要判断 key 是否存在存在就把 value 加一不存在就 put 一条新记录时间复杂度 O(1)。List 得遍历整个集合找相同商品商品一多页面响应就变慢。我在拆这种项目时看到的典型实现长这样// 从 Session 取出购物车没有就新建一个 HttpSession session request.getSession(); MapString, Integer cart (MapString, Integer) session.getAttribute(cart); if (cart null) { cart new HashMapString, Integer(); } String productId request.getParameter(pid); String quantityParam request.getParameter(quantity); int quantity quantityParam null ? 1 : Integer.parseInt(quantityParam); // 已存在则数量累加否则新增 if (cart.containsKey(productId)) { cart.put(productId, cart.get(productId) quantity); } else { cart.put(productId, quantity); } session.setAttribute(cart, cart);这里把临时购物车数据全放在 Session 里最大好处是减少对 SQL Server 的访问。商品只在从数据库查询时读一次加购、改数量、删除都不碰数据库最后结算才写订单表。实际项目中还有人用 List 每个 CartItem 对象包含商品 ID、名称、单价、数量。这样在购物车页面展示时不需要再回查数据库直接就能把名称和价格渲染出来但也意味着加购时必须带着商品信息复制一份快照商品价格后续调整时购物车里显示的是旧价。两种各有取舍HashMap 方案更轻List 方案展示更省事。4.2 加购、改数量、删除、清空四个动作走同一套数据路径购物车的四个基本动作逻辑上完全一致从 Session 取出购物车修改集合内容再放回 Session。不管哪个操作数据库都不用动。改数量的核心是重新赋值MapString, Integer cart (MapString, Integer) session.getAttribute(cart); String productId request.getParameter(pid); String action request.getParameter(action); if (update.equals(action)) { int newQuantity Integer.parseInt(request.getParameter(quantity)); if (newQuantity 0) { // 数量小于等于 0 视为删除该商品 cart.remove(productId); } else { cart.put(productId, newQuantity); } } else if (remove.equals(action)) { cart.remove(productId); } else if (clear.equals(action)) { session.removeAttribute(cart); cart null; } if (cart ! null) { session.setAttribute(cart, cart); }注意这里对数量小于等于 0 的处理。很多购物车测试点里会把数量改成 0、-1 甚至非数字字符如果代码不做防御Integer.parseInt 会直接抛 NumberFormatException页面白屏。老项目里这种细节经常被忽略但面试或答辩时拿出来讲反而是加分项。购物车写在 Session 里还有一个好处页面跳转不丢。HTTP 无状态每次请求都是独立的Session 靠 Cookie 里的 JSESSIONID 维持身份。用户从 list.jsp 点加购跳转到购物车页面再到 purchase.jsp只要 Session 没失效购物车数据就一直跟着。4.3 结算页为什么被反复改订单落库与库存扣减的业务逻辑包里出现三个 purchase.jsp 版本不是偶然。结算页是整个系统里业务最重的一环要做的事包括校验登录状态、读取 Session 购物车、计算总价、写入订单表、扣减库存、清空购物车任何一个环节出错都得改代码。典型的结算逻辑大概这样// 判断用户是否登录未登录跳回 login.jsp Object userId session.getAttribute(userId); if (userId null) { response.sendRedirect(login.jsp); return; } MapString, Integer cart (MapString, Integer) session.getAttribute(cart); if (cart null || cart.isEmpty()) { response.sendRedirect(list.jsp); return; } // 遍历购物车逐项写入订单明细并扣减库存 Connection conn null; PreparedStatement psOrder null; PreparedStatement psDetail null; PreparedStatement psUpdateStock null; try { conn DriverManager.getConnection(url, user, password); conn.setAutoCommit(false); // 开启事务 // 插入主订单得到订单号 // 遍历 cart逐条插入订单明细 // 更新商品表库存UPDATE goods SET stock stock - ? WHERE id ? conn.commit(); // 全部成功提交事务 session.removeAttribute(cart); // 清空购物车 } catch (Exception e) { if (conn ! null) { conn.rollback(); // 任何一个步骤失败回滚 } throw e; }这段逻辑里最重要的是事务。先插入订单再扣库存最后清 Session 购物车。如果不用事务订单写入了但库存没扣过两天商品超卖账都对不上。PreparedStatement 在这里不是可选项订单数据直接来自用户输入用字符串拼接 SQL 等于把 SQL 注入的风险敞在门口。包里有三个 purchase 版本也侧面说明原作者当时在这页上折腾了很久。这类老结构里没有独立 Servlet 处理下单请求所有逻辑直接写在 JSP 里业务一复杂页面代码就膨胀改一次崩一次。这也是后文要做加固改造的原因。5. 避坑手册老 JSP 项目部署时最容易翻车的 5 个位置5.1 现象Tomcat 10 跑起来就 404 或报 ClassNotFoundException有人拿到包后直接装最新版 Tomcat 10.x启动没问题访问 JSP 页面却报错控制台提示找不到 javax.servlet.ServletException 之类的类。原因Tomcat 10 起把所有 Servlet API 的包名从 javax.servlet 改成了 jakarta.servlet。老项目里所有 import javax.servlet.* 的 Java 代码和 JavaBean 全部失效类都找不到页面自然反不出来。解决换成 Tomcat 8.5 或 9.0。这两个版本还是 javax 包名是跑老项目的黄金版本。安装时也注意别在 PATH 里同时混多个 Tomcat端口冲突和版本混乱会让人误判问题方向。5.2 现象JDBC 连接报 ClassNotFoundException驱动类名怎么改都没用项目里引用的是 com.microsoft.jdbc.sqlserver.SQLServerDriver这个类在本地却始终找不到。原因这是微软最早一批 JDBC 驱动的类名对应的 jar 是 msbase.jar、mssqlserver.jar、msutil.jar 三个早已停止维护也和新版 SQL Server 不兼容。代码里 Class.forName 加载的类在当前的驱动 jar 里根本不存在。解决换新版官方驱动 mssql-jdbc-jre8 系列把 Class.forName 改成 com.microsoft.sqlserver.jdbc.SQLServerDriver连接串前缀同步改成 jdbc:sqlserver://。驱动 jar 放到 WEB-INF/lib 下不要在 classpath 里同时保留老驱动两个驱动的类名有重叠容易互相干扰。5.3 现象附加 MDF 时报“数据库版本不兼容”或直接失败SQL Server 2019 附加 Web_Data.MDF弹窗提示版本不支持。原因MDF 文件内部有数据库版本号Web_ 前缀的文件多是 SQL Server 2000 或 2005 时代生成的。SQL Server 新版本对过老的数据文件没有直接附加通道版本跨度太大时就拒绝挂载。解决找一台 SQL Server 2008 R2 或 2012 的实例把库附加进去让数据库版本先升一级然后备份成 BAK再拿到新实例还原。没有旧实例的话就用脚本工具把表结构和数据导成 SQL 脚本在新库里重建。这个方案虽然笨但老数据能保下来。5.4 现象驱动换了还是连不上连接串报 SSL 或登录失败Class.forName 过了DriverManager.getConnection 却抛异常提示 SSL connection 或证书相关错误。原因新版 SQL Server JDBC 驱动默认启用加密连接。本地 SQL Server 没有配置受信任的证书握手阶段就被掐断。这个问题在新手环境里出现频率极高几乎每个装新版 SQL Server 的人都会遇到一次。解决连接串里显式关闭加密并信任服务器证书String url jdbc:sqlserver://localhost:1433;DatabaseNameWeb_Data;encryptfalse;trustServerCertificatetrue;encryptfalse 和 trustServerCertificatetrue 两个参数配合本地开发基本不会再出 SSL 相关报错。生产环境不要这么干但咱们这是课设和本地复现安全边界自己心里有数就行。5.5 现象页面中文乱码注册的中文用户名显示成问号页面能打开但 list.jsp 里的中文商品名全是乱码往数据库写入中文也变成 ??。原因老项目里 JSP 页面编码、HTTP 请求编码、数据库排序规则三处可能不一致。2008 年左右的代码常用 GBK 或 GB2312现在数据库默认排序规则多为 Chinese_PRC_CI_AS 或 Latin 系列页面和数据库之间编码对不上中文就崩。解决三处一起查。JSP 文件第一行确认 contentType 的 charset 是 UTF-8SQL Server 数据库排序规则确认支持中文请求层面可以在 web.xml 里加一个编码过滤器把所有请求强制按 UTF-8 解码。这三步都做了乱码问题基本根治。6. 低成本加固路线私人实战中的三条关键改动6.1 把连接串抽进 db.properties从此数据库迁移不用再大海捞针翻代码老项目把 JDBC 连接串直接写死在每个 JSP 页面里改一个数据库密码要搜遍全项目。实际操作中我建议把连接信息抽到一个 db.properties 文件加一个 DBUtil 类统一管理drivercom.microsoft.sqlserver.jdbc.SQLServerDriver urljdbc:sqlserver://localhost:1433;DatabaseNameWeb_Data;encryptfalse;trustServerCertificatetrue usersa password123456public class DBUtil { static { try { Class.forName(com.microsoft.sqlserver.jdbc.SQLServerDriver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws Exception { java.util.Properties props new java.util.Properties(); props.load(DBUtil.class.getClassLoader().getResourceAsStream(db.properties)); String url props.getProperty(url); String user props.getProperty(user); String password props.getProperty(password); return java.sql.DriverManager.getConnection(url, user, password); } }所有页面里的连接代码都收敛到这个类里以后换机器只需改 properties 文件不用动 Java。这个改动对答辩演示的好处是老师问“数据库地址变了怎么办”直接现场改 properties 重启比翻代码挖连接串利落得多。6.2 加一个 LoginFilter没登录直接弹回登录页不用每个 JSP 里手写判断老项目的通病是每个需要登录的页面都复制一段 userId 判空代码漏写一处就变成未登录也能下单。与其到处补不如加一个过滤器统一拦截public class LoginFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpSession session req.getSession(); if (session.getAttribute(userId) null) { // 未登录跳回登录页 ((HttpServletResponse) response).sendRedirect(login.jsp); return; } chain.doFilter(request, response); } }在 web.xml 里声明过滤器拦截 purchase.jsp 和会员相关页面。这样登录校验只维护一处安全逻辑不再散落在各个 JSP 里系统的防御边界也清晰了。6.3 全局替换 SQL 拼接为 PreparedStatement防注入最直接的一步老代码里最常见的安全问题是 SQL 用字符串拼接比如把用户输入直接塞进 WHERE 子句。解决办法是全部换成 PreparedStatement预编译语句天然转义特殊字符。逐页找拼接、逐个替换工程量大但收效最实在。从那以后我拆到这种老 JSP 项目第一件事就是全局搜 SQL 里有没有字符串拼接有就直接改成 PreparedStatement。数据库版本、编码、驱动这些坑都可以靠经验快速趟过去注入漏洞不补代码跑得再好也是白搭。希望这篇拆包笔记能帮你把这份老资源变成真正能跑、能讲、能交付的东西。本文还有配套的精品资源点击获取
返回列表