尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JSP+Access网上购物系统源码解析:从UCanAccess连接到Tomcat部署全攻略

JSP+Access网上购物系统源码解析:从UCanAccess连接到Tomcat部署全攻略 简介基于JSPAccess的网上购物系统设计与实现源码及配套文档面向计算机相关专业学生、毕业设计或课程设计人员解决电商系统从设计到落地的完整参考问题。系统采用B/S架构包含前端页面、后端Servlet/JSP逻辑及Access数据库涵盖商品展示、购物车、订单管理等典型模块是一套经过测试可稳定运行的完整项目方案。压缩包整体大小约1.9MB内部整合项目全套源码与完整文档源码经校验可百分百运行文档可作为设计说明或答辩参考。目前已有290人学习浏览适合需要快速搭建网上购物系统原型或理解JSPAccess开发流程的读者。资源结构清晰源码与文档分离便于直接导入开发工具运行也可依据文档进行功能扩展与二次开发为相关项目设计提供可靠支撑。1. 这套基于 JSPAccess 的网上购物系统价值不在能跑而在能改压缩包标题写着“源码文档”但这套系统的价值不在解压后那一堆 .jsp 文件里而在于它把 Web 开发最经典的三层关系都摊开了JSP 负责把数据画在页面上Servlet 负责接收请求和决定下一步去哪Access 数据库文件负责把状态持久化。比起动辄安装 MySQL、Redis 的电商项目它轻量得不像一个购物系统但也正因为轻量适合在一周之内从零跑通、逐行读懂再改成自己的设计。文章按课程设计和内部培训的路径来写从数据库连接讲到 Tomcat 排错每一段都可以对着源码操作。2. JSPAccess 的技术选型为什么老项目都用 UCanAccess 连接2.1 先解决“数据库怎么连”的问题JSPAccess 常被戏称学生项目专用但客观讲选型逻辑并不差数据库是单文件备份就是复制文件部署时只要 Tomcat 能起业务就能跑Access 的 SQL 足够简单DAO 层写起来不会有 MySQL 的方言包袱。真正的问题是连接方式。早年的教程喜欢配置 ODBC 数据源在代码里写sun.jdbc.odbc.JdbcOdbcDriver。这个方案在 JDK 8 里已经被标记移除新 JDK 直接删除相关类凡是现在还跑得起来的项目基本都换成了 UCanAccess 驱动。UCanAccess 是纯 Java 实现的 Access 数据库驱动底层把.accdb当作文件读取再借助 Jackcess 和 HSQLDB 提供 JDBC 接口。它对旧项目是黑盒替换对数据库文件本身没有破坏性。依赖清单如下我一般会手动放进WEB-INF/lib而不是让 IDE 自动管理WEBAPPshopping mkdir -p $WEBAPP/WEB-INF/{classes,lib} cp ucanaccess-5.0.1.jar jackcess-4.0.2.jar \ hsqldb-2.7.1.jar commons-lang3-3.12.0.jar \ commons-logging-1.2.jar $WEBAPP/WEB-INF/lib/这段命令把 UCanAccess 主驱动和四个关键依赖放到项目运行目录。ucanaccess本身不包含 Access 文件解析能力必须配合jackcesshsqldb负责执行 Access 方言以外的复杂查询commons-lang3和commons-logging是运行时工具库。漏掉任何一个启动时都会报NoClassDefFoundError这个坑在源码交付类项目里出现频率最高。2.2 页面、控制层、DAO 的分层职责网上购物系统的代码结构通常不会特别复杂但一个能写进文档的版本至少要把三层分开。JSP 只从request和session中取数据并渲染表单不写try-catch数据库代码Servlet 负责获取参数、调用业务方法、重定向视图DAO 负责所有Connection和PreparedStatement。这样做的好处是后期把 Access 换成 MySQL只需要替换 DAO 实现JSP 一行不用动。层典型目录职责常见类/文件表现层webapp/HTML、JSP、静态资源index.jsp、cart.jsp、login.jsp控制层controller/参数校验、路由转发LoginServlet、CartServlet数据层dao/JDBC、SQL、事务ProductDao、OrderDao工具层util/连接、字符串、分页DBHelper、PageUtil这套分层也决定了阅读源码的顺序先看DBHelper知道数据从哪来再看 DAO 知道 SQL 怎么写最后看 JSP 知道前端拿什么。真正动手改造时优先改 DAO而不是在页面上堆 Java 代码。旧源码里常出现% %直接写 JDBC 的做法能跑但改一个字段名要翻十个页面不推荐保留。3. Access 数据库设计与 JDBC 连接表结构、DBHelper 与 SQL 方言3.1 设计用户、商品、订单、订单明细四张表购物系统无论增加多少功能核心数据模型都围绕用户、商品、订单展开。Access 用AutoNumber做主键自增字段类型和 MySQL 差别不大但要注意金额字段应使用CURRENCY而不是DOUBLE避免浮点误差。订单号不要用自增主键直接展示最好单独生成带时间戳的字符串。表名关键字段说明usersid, username, password, email, create_time用户表密码建议存 MD5productsid, name, price, stock, image, category_id商品表库存是下单关键ordersid, order_no, user_id, total, status, create_time订单主表status 标记状态order_itemsid, order_id, product_id, quantity, price订单明细冗余商品快照在 Access 中执行建表 SQL 可以直接在查询设计器里运行也可以用 UCanAccess 的Statement.execute。下面这个建表语句适合在源码文档里展示字段约束CREATE TABLE products ( id AUTOINCREMENT PRIMARY KEY, name TEXT(100) NOT NULL, price CURRENCY NOT NULL, stock LONG DEFAULT 0, image TEXT(255), category_id LONG );这里把图片字段设为 TEXT存相对路径即可不要在数据库里放 Base64 字符串。AUTOINCREMENT是 Access 方言迁移到 MySQL 时改成AUTO_INCREMENT这条 SQL 基本就是一张迁移对照表。订单明细里的price必须冗余因为商品价格会变而历史订单不能跟着变。3.2 用 DBHelper 封装单连接入口JSPAccess 项目里最常见的工具类就是DBHelper它的任务只有一个提供一个统一的Connection获取入口。早期代码用DriverManager.getConnection(jdbc:odbc:shop)现在应改为 UCanAccess 的 JDBC URLpackage util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBHelper { private static final String DB_URL jdbc:ucanaccess://C:/data/shopping.accdb;openExclusivefalse;ignoreCasetrue; private static Connection conn; static { try { Class.forName(net.ucanaccess.jdbc.UcanaccessDriver); } catch (ClassNotFoundException e) { throw new RuntimeException(UCanAccess 驱动未找到请检查 WEB-INF/lib, e); } } public static synchronized Connection getConnection() throws SQLException { if (conn null || conn.isClosed()) { conn DriverManager.getConnection(DB_URL); } return conn; } }openExclusivefalse表示允许多个连接同时读取同一个 Access 文件但不建议真的多连接并发写。ignoreCasetrue让 SQL 关键字大小写不敏感能降低从 MySQL 源码迁移过来时的错误率。使用单连接而不是每次新开连接是刻意为之Access 对文件锁很敏感连接池里多个写连接同时操作同一个.accdb时容易出现Could not lock file错误。单连接牺牲了并发换取了演示环境的稳定。需要注意数据库文件路径不要放在 Tomcat 的webapps目录下因为重新部署 war 包时通常会把旧目录删掉再重建数据库文件会被一起清空。我会放在C:/data/这类独立目录并且给 Tomcat 进程设置读写权限。3.3 与 MySQL 差异较大的 SQL 写法Access 能兼容很多标准 SQL但分页、布尔值和日期字面量有自己的写法。网上购物系统的商品列表往往需要分页在 MySQL 里写作LIMIT ?, ?在 Access 里则要换思路场景MySQL 写法Access/Jet SQL 写法分页取前十条LIMIT 0,10SELECT TOP 10 * FROM products布尔判断WHERE is_sale 1WHERE is_sale -1字符串拼接CONCAT(a, b)a b日期字面量2024-01-01#2024-01-01#Access 的WHERE条件里如果使用字符串拼接要注意单引号转义否则用户输入带单引号时会造成 SQL 注入。所有 DAO 代码应当使用PreparedStatement传参避免字符串拼接。分页可以用TOP配合子查询实现但对这个体量的系统一次查出来在内存里切片也足够。这里真正要记的是不要试图在 Access 上做复杂的GROUP BY报表统计工作交给 JSP 或导出后的 Excel 处理更划算。4. 购物流程设计与实现商品列表、购物车 Session 与订单事务4.1 商品列表与登录后的个人信息展示页面商品列表页是整个系统的入口。常规做法是ProductServlet从ProductDao查出商品集合放入request转发给product_list.jsp。JSP 用JSTL c:forEach遍历集合生成商品卡片。这一步的逻辑清晰简洁不再展开细节。值得注意的反而是登录后的个人信息展示页面这是很多源码里最容易写乱的部分。个人信息展示页面在校验通过后应从session里取出用户对象而不是在 JSP 里再次查数据库。比如登录成功时把用户 ID、用户名、创建时间放入sessionJSP 里直接展示。这样页面加载快也避免了一次无意义的连接操作。安全方面要记住一点不要在 JSP 里输出未转义的用户名用户名可能包含 HTML 标签需要做HtmlUtils.htmlEscape处理否则会变成反射型 XSS 入口。4.2 加入购物车Session 与 Cookie 怎么选购物车的存储位置是这个模块的技术分水岭。放在HttpSession里实现最简单也是源码中最常见的做法放在 Cookie 里可以跨请求保存但总容量只有 4KB放到数据库购物车表则适合登录用户跨设备同步。网上购物系统的演示版本通常会优先选择 Session因为重启 Tomcat 后购物车自动清空省去定时清理逻辑。存储位置优点缺点适合场景HttpSession实现简单不落库重启丢失课程设计、演示Cookie保存期长容量小有篡改风险未登录用户数据库表持久化跨设备需要建表、清理过期数据生产级扩展对应的加入购物车 Servlet 可以这样写WebServlet(/cart/add) public class CartServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int productId Integer.parseInt(req.getParameter(productId)); int quantity Integer.parseInt(req.getParameter(quantity)); HttpSession session req.getSession(); MapInteger, Integer cart (MapInteger, Integer) session.getAttribute(cart); if (cart null) { cart new HashMap(); } cart.put(productId, cart.getOrDefault(productId, 0) quantity); session.setAttribute(cart, cart); resp.sendRedirect(req.getContextPath() /cart.jsp); } }这段代码用productId作为 keyquantity作为 value。第二次添加同一种商品时不会重复生成行而是累计数量。getOrDefault是 Java 8 的默认方法如果旧源码用的 JDK 7需要改成cart.containsKey(productId) ? cart.get(productId) quantity : quantity。发送重定向而不是直接转发是为了避免用户刷新页面时重复提交添加请求。4.3 下单与库存扣减的事务边界提交订单是整个系统里唯一需要认真对待事务的地方。如果在 JSP 里直接写 JDBC很难保证订单主表、订单明细、库存扣减三个操作的一致 性。正确顺序是生成订单号插入订单主表再插入明细最后扣减库存。任何一步失败都要回滚。Connection conn DBHelper.getConnection(); boolean oldAutoCommit conn.getAutoCommit(); conn.setAutoCommit(false); try { insertOrder(conn, orderNo, userId, total); int orderId getGeneratedId(conn); for (CartItem item : items) { insertOrderItem(conn, orderId, item); int rows updateStock(conn, item.getProductId(), item.getQuantity()); if (rows 0) { throw new SQLException(库存不足: item.getProductId()); } } conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(oldAutoCommit); }updateStock的 SQL 要写成UPDATE products SET stock stock - ? WHERE id ? AND stock ?用受影响行数判断库存是否足够不要先查询再判断。虽然 Access 对并发控制较弱但这种条件更新至少能避免两笔订单同时把库存扣成负数。把setAutoCommit(false)放在 try 外并在 finally 里恢复是避免连接状态污染的关键。UCanAccess 的单连接模式下一次事务只允许一个线程操作所以购物系统不建议并行下单测试。5. Tomcat 部署与排错WAR 包、jsp 编译目录和 0xc00000055.1 部署到 Tomcat 的最小化步骤拿到源码后第一件事不是打开 IDE而是确认 Tomcat 和 JDK 版本。老源码通常基于javax.servletAPI如果直接部署到 Tomcat 10.1 会报NoClassDefFoundError: javax/servlet/ServletException因为新版本已经切换到jakarta命名空间。最稳妥的搭配是 JDK 8 和 Tomcat 9遇到问题最少。手动部署时把整个项目目录复制到 Tomcat 的webapps目录后启动export CATALINA_HOME/opt/tomcat9 cp -r shopping $CATALINA_HOME/webapps/shopping $CATALINA_HOME/bin/startup.sh tail -f $CATALINA_HOME/logs/catalina.out启动后的访问路径是http://localhost:8080/shopping/index.jsp。如果只想访问根路径就把项目目录改名为ROOT再覆盖。看到INFO: Server startup in xxx ms只表示服务起来了不代表数据库连接正常必须打开一个商品页面或登录页验证。5.2 jsp 编译后的 class 文件保存在哪里JSP 首次被访问时会编译成 Servlet生成目录位于 Tomcat 的work目录下。路径通常是work/Catalina/localhost/shopping/org/apache/jsp/。同名 JSP 会对应一个_jsp.java和一个_jsp.class文件。知道这个路径至少有三个用处判断 JSP 是否真的编译成功清理缓存以及定位页面 500 错误对应的代码行号。如果修改了 JSP 但刷新后页面没变化最直接的办法是停止 Tomcat删除整个work目录后重启。删除后 Tomcat 会重新编译所有页面这个操作在交付给答辩环境前做一次可以避免残留编译缓存引发的诡异问题。5.3 启动崩溃 0xc0000005 和 Access 连接报错对照有同学遇到 Tomcat 启动后 JVM 直接退出提示process exited with code 3221225477十六进制就是0xc0000005含义是内存访问违规。这个错误在 JSPAccess 项目里通常不是代码逻辑问题而是使用了本地 ODBC 驱动且 JDK 位数、ODBC 驱动位数不匹配。排查顺序是先看项目里有没有JdbcOdbcDriver或jdbc:odbc:开头的连接串有就先替换成 UCanAccess没有再看是否用了不兼容的加密狗或杀毒软件拦截。现象常见原因处理方案ClassNotFoundException: UcanaccessDriver驱动 jar 没放全把依赖全部复制到WEB-INF/libCould not lock file多连接同时写 AccessDBHelper 改为单连接process exited with code 3221225477 / 0xc0000005ODBC 桥或本地库位数不匹配弃用 ODBC全部走 UCanAccessjsp compiler error类名冲突或 JDK 版本过高清理 work 目录改用 JDK 8需要注意0xc0000005也出现在某些杀毒软件扫描 Tomcat 临时文件时如果项目本身使用 UCanAccess 并且代码没变启动依旧崩溃可以把 Tomcatwork目录加入杀毒软件白名单再试。这个错误本身不是 Access 数据库报错而是操作系统层面对非法内存访问的拦截只能从运行环境入手排查。6. 答辩级增强用 JSP 导出 Excel 和做销售统计6.1 用 Apache POI 实现订单导出为 Excel为了让项目在答辩时更完整通常会增加订单导出功能。思路是新增一个ExportOrderServlet接收查询条件后查询orders表再用 POI 生成.xls文件以附件形式返回给浏览器。下面是一个兼容旧版 Excel 的最小示例WebServlet(/admin/order/export) public class ExportOrderServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { ListOrder orders new OrderDao().findAll(); try (Workbook wb new HSSFWorkbook()) { Sheet sheet wb.createSheet(订单列表); Row header sheet.createRow(0); header.createCell(0).setCellValue(订单号); header.createCell(1).setCellValue(用户名); header.createCell(2).setCellValue(金额); int rowIndex 1; for (Order o : orders) { Row row sheet.createRow(rowIndex); row.createCell(0).setCellValue(o.getOrderNo()); row.createCell(1).setCellValue(o.getUsername()); row.createCell(2).setCellValue(o.getTotal().doubleValue()); } resp.setContentType(application/vnd.ms-excel); resp.setHeader(Content-Disposition, attachment;filename java.net.URLEncoder.encode(orders.xls, UTF-8)); wb.write(resp.getOutputStream()); } } }HSSFWorkbook生成的是 Excel 97-2003 格式文件名后缀必须是.xls用.xlsx打开会提示格式不符。Content-Disposition里的文件名做了 URL 编码避免中文名称下载后乱码。需要注意内存问题订单量大时不要一次把所有数据装入List应改为流式查询或者按时间范围导出一周数据。6.2 统计模块的版本控制技巧最后一个增强点是把“销售统计”做成独立 JSP统计每日订单数和销售额。Access 对聚合查询支持有限所以更推荐查询原始订单后在 Java 内存中分组统计。核心技巧是让OrderDao提供findByDateRange方法返回ListOrder统计逻辑放在OrderService中JSP 只负责显示。这种写法在源码向 MySQL 迁移时改动最小也方便在文档中画统计流程图。导出 Excel、分页、事务这三块代码放在源码的src目录里再配上对应的测试页面整个项目就已经从“能跑”变成“能讲清楚”了。本文还有配套的精品资源点击获取
返回列表