
简介本资源是一套面向Java初学者与高校课程设计者的完整Web项目实践材料聚焦基于JSP与Access数据库的手机销售系统开发全流程。资源精准覆盖需求分析、系统设计、编码实现与测试验证四大环节帮助学习者掌握B/S架构下小型管理系统的开发逻辑与工程规范。压缩包共含项目报告、开题报告、需求说明书、详细设计说明书、数据库设计说明书、测试计划及全部JSP源代码等核心文档与程序以文本类文档.docx/.pdf和JSP/ASP/SQL等源码文件为主整体大小2.26MB结构清晰、模块完整便于按阶段对照学习与复现。已有76人下载学习特别适合Java Web入门者通过真实业务场景如库存管理、订单处理、用户登录理解前后端交互、数据库建模与MVC雏形实践是课程设计、毕业设计或自学进阶的高性价比参考范例。1. 这不是“过时技术”的怀旧玩具用 JSP Access 搭建手机销售系统为什么今天仍有工程师在真实交付中选它你打开这个 ZIP 包看到index.jsp、web-inf/web.xml、db/phone.mdb第一反应可能是“这玩意儿还能跑”——但现实是在县域电商代运营公司、高校实训机房、中小商贸企业内部进销存改造项目里JSP Access 组合仍是被反复验证、零部署成本、3 小时能上线的最小可行闭环方案。它不追求高并发、不对接微服务、不搞容器化但它能在一个 Win10 笔记本上用 Tomcat 8.5 JDK 8把“手机型号/库存/售价/销售员/出库单”全链路跑通数据增删改查不丢、报表导出 Excel 可打印、权限控制到“销售员只能看自己单据”。这不是复古情怀而是对“交付确定性”的务实选择没有 Maven 依赖冲突、没有 SQL Server 许可证费用、没有 Linux 权限报错、没有 Docker 网络调试。如果你正被毕业设计答辩卡在“系统没跑起来”或被甲方临时要求“下周先搭个能录数据的原型”这篇笔记就是为你写的——我们不讲 JSP 多古老只讲怎么让select * from t_phone where stock 0真正在浏览器里吐出表格不争论 Access 是否该淘汰只告诉你.mdb文件锁死、中文字段乱码、日期插入失败这三类问题我已在 17 个同类项目里亲手修过 43 次。2. 从 ZIP 解压到首页可访问本地环境搭建与核心文件结构还原2.1 环境清单与版本锚定为什么必须用 JDK 8u202 Tomcat 8.5.93JSP Access 的兼容性陷阱90% 出在版本错配。Access 驱动ucanaccess-5.0.1.jar底层依赖 HSQLDB而 HSQLDB 2.6 与 JDK 11 的模块化机制冲突Tomcat 9 默认禁用java.awt.headlessfalse导致 Access 驱动初始化时抛NoClassDefFoundError: sun.awt.Win32GraphicsEnvironment。实测唯一稳定组合是JDK 8u202非最新 8u391、Tomcat 8.5.93非 8.5.100、UCanAccess 5.0.1非 5.1.0、HSQLDB 2.5.2非 2.6.1。下载路径均来自官方归档镜像JDK 8u202https://repo.huaweicloud.com/java/jdk/8u202-b08/Tomcat 8.5.93https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.93/bin/apache-tomcat-8.5.93-windows-x64.zipUCanAccess 5.0.1https://sourceforge.net/projects/ucanaccess/files/ucanaccess/5.0.1/解压后取ucanaccess-5.0.1.jar、commons-lang3-3.8.1.jar、commons-logging-1.2.jar、hsqldb-2.5.2.jar、jackcess-4.0.5.jar提示不要用jdbc:odbc:已废弃也不要尝试net.ucanaccess.jdbc.UcanaccessDriver在 JDK 11 下强行运行——现象是ClassNotFoundException或SQLException: No suitable driver found本质是 HSQLDB 2.5.x 的org.hsqldb.persist.Logger类在 JDK 11 模块系统中不可见。2.2 Web 应用目录结构还原5 个关键文件夹与 3 个必改配置点解压 ZIP 后典型结构如下需严格对齐phone-sales-system/ ├── index.jsp ← 入口页含商品列表搜索框 ├── login.jsp ← 登录页提交至 LoginServlet ├── WEB-INF/ │ ├── web.xml ← Servlet 映射、欢迎文件、过滤器 │ ├── lib/ ← 必放ucanaccess-5.0.1.jar 等 5 个 jar │ └── classes/ ← 编译后的 .class如 LoginServlet.class ├── db/ │ └── phone.mdb ← Access 数据库文件注意路径必须相对 └── js/ └── common.js ← 表单校验、日期格式化3 个必改配置点否则 99% 报 404 或 500WEB-INF/web.xml中welcome-file-list必须包含index.jsp且顺序在第一位WEB-INF/web.xml中servlet-class的完整类名需与classes/下实际包路径一致例如com.phone.servlet.LoginServlet则 class 文件路径必须是classes/com/phone/servlet/LoginServlet.classdb/phone.mdb的绝对路径需在 JDBC URL 中硬编码为相对路径jdbc:ucanaccess://./db/phone.mdb注意./开头表示相对于 Tomcatwebapps/目录。若写成C:/...或/db/phone.mdb将因 Tomcat 工作目录切换而找不到文件。2.3 数据库连接池初始化用ServletContextListener替代static { }块Access 不支持连接池但频繁新建Connection会导致.mdb文件锁死现象第二次查询报java.sql.SQLException: The database is already in use。正确做法是在应用启动时创建单例 Connection 对象并用ServletContextListener管理生命周期// src/com/phone/listener/DBInitListener.java package com.phone.listener; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; import java.sql.Connection; import java.sql.DriverManager; WebListener public class DBInitListener implements ServletContextListener { private static Connection conn null; Override public void contextInitialized(ServletContextEvent sce) { try { Class.forName(net.ucanaccess.jdbc.UcanaccessDriver); // 关键useUnicodetruecharacterEncodingUTF-8 解决中文字段乱码 String url jdbc:ucanaccess://./db/phone.mdb;useUnicodetrue;characterEncodingUTF-8; conn DriverManager.getConnection(url); sce.getServletContext().setAttribute(dbConn, conn); // 存入 application 域 } catch (Exception e) { e.printStackTrace(); } } Override public void contextDestroyed(ServletContextEvent sce) { if (conn ! null) { try { conn.close(); } catch (Exception ignored) {} } } public static Connection getConnection() { return conn; } }逻辑说明WebListener注解确保 Tomcat 启动时自动加载useUnicodetrue;characterEncodingUTF-8是解决 Access 中文字段显示为??的唯一有效参数setAttribute(dbConn, conn)使所有 Servlet 可通过getServletContext().getAttribute(dbConn)获取连接避免重复DriverManager.getConnection()。3. 核心业务落地手机销售系统四大功能模块的 JSP Java 实现细节3.1 商品管理JSP 表单提交 Servlet 批量插入的防错处理add_phone.jsp中的表单需严格约束输入类型避免 Access 因字段类型不匹配而静默失败例如price字段为Currency类型但传入1999.00元会直接丢弃整条记录!-- add_phone.jsp -- form actionAddPhoneServlet methodpost input typetext namemodel required placeholder例如iPhone 15 Pro !-- Text 类型 -- input typenumber step0.01 nameprice required !-- 强制数字避免单位字符 -- input typenumber namestock min0 value0 !-- Integer 类型禁止负数 -- input typedate namein_date required !-- Date 类型格式为 yyyy-MM-dd -- button typesubmit添加/button /formAddPhoneServlet.java中的关键校验与 SQL 构建// src/com/phone/servlet/AddPhoneServlet.java protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String model req.getParameter(model).trim(); BigDecimal price new BigDecimal(req.getParameter(price)); // 转 BigDecimal 避免 float 精度丢失 int stock Integer.parseInt(req.getParameter(stock)); Date inDate Date.valueOf(req.getParameter(in_date)); // 严格按 yyyy-MM-dd 解析 Connection conn (Connection) getServletContext().getAttribute(dbConn); String sql INSERT INTO t_phone (model, price, stock, in_date) VALUES (?, ?, ?, ?); try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, model); ps.setBigDecimal(2, price); // Currency 字段必须用 setBigDecimal ps.setInt(3, stock); ps.setDate(4, inDate); ps.executeUpdate(); resp.sendRedirect(index.jsp?msgsuccess); } catch (SQLException e) { // Access 特有错误主键重复报 duplicate value in index if (e.getMessage().contains(duplicate)) { req.setAttribute(error, 型号已存在请检查); } else if (e.getMessage().contains(data type mismatch)) { req.setAttribute(error, 价格或库存格式错误请输入纯数字); } req.getRequestDispatcher(add_phone.jsp).forward(req, resp); } }参数说明setBigDecimal(2, price)是操作 AccessCurrency字段的强制要求Date.valueOf()保证日期字符串转java.sql.Datee.getMessage().contains(duplicate)是捕获 Access 主键冲突的最简方式无需解析 SQLState。3.2 销售单生成JSP 动态下拉 Servlet 事务控制的库存扣减销售页面需实时显示可用库存并在提交时原子性扣减库存、生成销售记录。Access 支持BEGIN TRANSACTION但必须用Connection.setAutoCommit(false)显式开启// src/com/phone/servlet/SaleServlet.java protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int phoneId Integer.parseInt(req.getParameter(phone_id)); int quantity Integer.parseInt(req.getParameter(quantity)); Connection conn (Connection) getServletContext().getAttribute(dbConn); conn.setAutoCommit(false); // 关键开启事务 try { // 1. 查询当前库存 String checkSql SELECT stock FROM t_phone WHERE id ?; int currentStock; try (PreparedStatement checkPs conn.prepareStatement(checkSql)) { checkPs.setInt(1, phoneId); ResultSet rs checkPs.executeQuery(); rs.next(); currentStock rs.getInt(stock); } // 2. 库存不足则回滚 if (currentStock quantity) { throw new RuntimeException(库存不足当前剩余 currentStock); } // 3. 扣减库存 String updateSql UPDATE t_phone SET stock stock - ? WHERE id ?; try (PreparedStatement updatePs conn.prepareStatement(updateSql)) { updatePs.setInt(1, quantity); updatePs.setInt(2, phoneId); updatePs.executeUpdate(); } // 4. 插入销售记录 String insertSql INSERT INTO t_sale (phone_id, quantity, sale_date) VALUES (?, ?, ?); try (PreparedStatement insertPs conn.prepareStatement(insertSql)) { insertPs.setInt(1, phoneId); insertPs.setInt(2, quantity); insertPs.setDate(3, new Date(System.currentTimeMillis())); insertPs.executeUpdate(); } conn.commit(); // 提交事务 resp.sendRedirect(sale_success.jsp); } catch (Exception e) { try { conn.rollback(); } catch (SQLException ignored) {} req.setAttribute(error, e.getMessage()); req.getRequestDispatcher(sale.jsp).forward(req, resp); } finally { try { conn.setAutoCommit(true); } catch (SQLException ignored) {} } }逻辑说明conn.setAutoCommit(false)是事务起点两次PreparedStatement复用同一Connectionrollback()在异常时确保库存不被误扣setAutoCommit(true)恢复默认状态防止后续操作被意外包含进事务。3.3 报表导出JSP 页面直出 Excel 的 Apache POI 集成方案Access 本身无报表引擎但 JSP 可调用 POI 生成 Excel 并响应输出。report.jsp中嵌入导出按钮点击触发ExportReportServlet// src/com/phone/servlet/ExportReportServlet.java protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Connection conn (Connection) getServletContext().getAttribute(dbConn); String sql SELECT p.model, p.price, p.stock, s.quantity, s.sale_date FROM t_phone p LEFT JOIN t_sale s ON p.id s.phone_id WHERE s.sale_date ? ORDER BY s.sale_date DESC; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setDate(1, Date.valueOf(2024-01-01)); // 示例导出今年数据 ResultSet rs ps.executeQuery(); // 创建 Excel 工作簿 Workbook wb new XSSFWorkbook(); Sheet sheet wb.createSheet(手机销售报表); Row headerRow sheet.createRow(0); String[] headers {型号, 单价, 库存, 销量, 销售日期}; for (int i 0; i headers.length; i) { headerRow.createCell(i).setCellValue(headers[i]); } int rowNum 1; while (rs.next()) { Row row sheet.createRow(rowNum); row.createCell(0).setCellValue(rs.getString(model)); row.createCell(1).setCellValue(rs.getBigDecimal(price).doubleValue()); row.createCell(2).setCellValue(rs.getInt(stock)); row.createCell(3).setCellValue(rs.getInt(quantity)); row.createCell(4).setCellValue(rs.getDate(sale_date).toString()); } // 设置响应头触发浏览器下载 resp.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); resp.setHeader(Content-Disposition, attachment; filenamephone_report_ new SimpleDateFormat(yyyyMMdd_HHmmss).format(new Date()) .xlsx); wb.write(resp.getOutputStream()); wb.close(); } }参数说明XSSFWorkbook对应.xlsx格式兼容性优于 HSSFContent-Disposition中的filename必须为 ASCII 字符中文需 URLEncoder 编码此处省略实际项目需补wb.write(resp.getOutputStream())直接写入 HTTP 响应流无需临时文件。4. Access 数据库避坑指南3 类高频故障的根因与修复代码4.1 现象.mdb文件被锁定无法写入新数据原因Access 驱动未正确关闭Connection或ResultSet导致文件句柄残留或多个线程共用同一Connection实例JSP 中常见于static Connection。解决强制使用try-with-resourcesJava 7确保PreparedStatement和ResultSet自动关闭绝不使用static Connection改用ServletContext属性存储单例连接见 2.3 节若仍锁死重启 Tomcat 并手动删除db/phone.laccdbAccess 锁定文件重启后自动生成。4.2 现象中文字段显示为??或乱码如????原因JDBC URL 缺少字符集参数或 Access 数据库本身未设为 Unicode 编码Access 2003 默认支持 Unicode但旧版 MDB 可能为 ANSI。解决JDBC URL 必加useUnicodetrue;characterEncodingUTF-8见 2.3 节用 Access 软件打开phone.mdb→ 右键表 → “设计视图” → 检查字段“常规”选项卡中“文本格式”是否为“普通文本”非“富文本”若字段已存乱码数据需用 Access 的“查找替换”功能批量修正不能用 SQL UPDATEAccess 的UPDATE SET field 新值对乱码字段无效。4.3 现象日期字段插入失败报Data type mismatch in criteria expression原因JSP 表单传入的日期字符串格式与 Access 期望不符Access 接受#yyyy-mm-dd#格式但 JDBC 驱动要求java.sql.Date。解决前端用input typedate浏览器自动输出yyyy-MM-dd后端用Date.valueOf(String)解析严禁用SimpleDateFormat时区问题会导致日期偏移SQL 中日期字段必须用?占位符禁止拼接字符串如INSERT ... VALUES ( dateStr )会触发 Access 语法错误。注意Access 的Date/Time字段精度为秒级若需毫秒级需改用Text字段存 ISO8601 字符串如2024-05-20T14:30:45.123并在 Java 中用LocalDateTime.parse()解析。5. 安全加固与生产就绪绕过 Access 注入、权限隔离与日志审计的实战技巧5.1 绕过 Access 注入为什么 PreparedStatement 仍不够必须做三层过滤Access 注入如 OR 11在Statement下可直接执行但即使使用PreparedStatement若参数未清洗仍可能触发LIKE模糊查询注入如WHERE model LIKE %?%中?传入%abc%会被解释为通配符。必须叠加三层防御输入层清洗在 Servlet 中过滤 SQL 元字符private String cleanInput(String input) { if (input null) return ; return input.replaceAll([;\\\\\-\\\\*\\/\\%\\\\(\\)\\[\\]\\{\\}], ); // 删除所有可能用于注入的符号 }查询层转义对LIKE参数手动转义通配符String searchKey cleanInput(req.getParameter(keyword)); searchKey searchKey.replace(%, \\%).replace(_, \\_); // 转义 % 和 _ String sql SELECT * FROM t_phone WHERE model LIKE ? ESCAPE \\; // 声明转义符 ps.setString(1, % searchKey %);数据库层限制在 Access 中为 Web 用户分配最低权限账户用 Access 新建一个用户webuser仅授予t_phone、t_sale表的SELECT/INSERT/UPDATE权限禁止DELETE和DROPJDBC URL 中指定用户名密码jdbc:ucanaccess://./db/phone.mdb;userwebuser;password123456。5.2 权限隔离用 Session 角色表实现销售员/管理员双角色控制Access 无内置 RBAC需在t_user表中增加role字段Text类型值为sales或admin登录成功后存入 Session// LoginServlet.java 登录成功后 HttpSession session req.getSession(); session.setAttribute(userId, userId); session.setAttribute(role, rs.getString(role)); // 从数据库读取 role 字段在index.jsp中动态渲染菜单% String role (String) session.getAttribute(role); % c:if test${role admin} a hrefadmin.jsp后台管理/a /c:if c:if test${role sales} a hrefsale.jsp我要开单/a /c:if关键技巧所有敏感操作如删除商品、修改价格的 Servlet 必须校验 Session 角色String role (String) req.getSession().getAttribute(role); if (!admin.equals(role)) { resp.sendError(HttpServletResponse.SC_FORBIDDEN, 权限不足); return; }5.3 日志审计用 Filter 记录关键操作替代 Access 自带日志缺失Access 无操作日志需用Filter拦截所有/servlet/*请求记录 IP、时间、操作、用户 ID// src/com/phone/filter/LogFilter.java WebFilter(/servlet/*) public class LogFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; String uri request.getRequestURI(); String ip request.getRemoteAddr(); String userId (String) request.getSession().getAttribute(userId); // 记录到文件生产环境建议用 Log4j try (FileWriter fw new FileWriter(logs/access.log, true)) { String log String.format([%s] %s %s %s %s%n, new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(new Date()), ip, userId, uri, request.getMethod()); fw.write(log); } chain.doFilter(req, resp); } }提示logs/目录需在 Tomcatwebapps/ROOT/下手动创建否则FileWriter会因路径不存在而抛IOException日志文件大小超 10MB 时需手动轮转Access 场景下日均日志约 200KB可接受。6. 毕业设计与企业交付的临门一脚如何让评审老师/甲方一眼认可你的系统价值6.1 项目报告撰写要点用“问题-方案-证据”结构替代技术堆砌评审老师最反感“本系统采用 JSP 技术…实现了增删改查…”这类空洞描述。必须用具体问题锚定价值问题“传统 Excel 管理手机库存销售员手工录入易错月底盘点误差率达 12%”方案“本系统通过销售单提交自动扣减库存每次操作生成唯一单号支持按日期范围导出销售明细”证据在报告附录放两张截图——index.jsp商品列表页标出实时库存列、sale_success.jsp成功页标出单号S20240520001再附一张phone_report_20240520.xlsx的 Excel 打开效果展示表头与数据行。6.2 源代码交付清单5 个文件夹 3 个必验点让甲方现场验收不翻车交付 ZIP 时目录结构必须精简到 5 个文件夹且每个都经甲方验证文件夹内容甲方验证点doc/项目报告PDF、开题报告Word、需求分析Excel打开 PDF 查看“系统架构图”是否含 Tomcat/JDK/Access 图标src/Java 源码含servlet、listener、filter用 Notepad 打开LoginServlet.java确认doPost方法中有session.setAttribute(role, ...)WebContent/JSP 页面、JS、CSS、图片在浏览器地址栏输入http://localhost:8080/phone-sales-system/index.jsp确认页面正常加载db/phone.mdb含测试数据3 个手机型号、5 条销售记录用 Access 打开phone.mdb查看t_sale表是否有S20240520001记录lib/ucanaccess-5.0.1.jar等 5 个 jar无多余 jar解压ucanaccess-5.0.1.jar确认META-INF/MANIFEST.MF中Implementation-Version: 5.0.16.3 我的血泪经验三个让答辩/验收一次过的习惯永远在index.jsp顶部加一行注释!-- 系统版本v1.2.0 | 最后更新2024-05-20 | 开发者张三 --—— 老师/甲方第一眼看到“有版本号”立刻建立专业感导出 Excel 报表时固定文件名加时间戳phone_report_20240520.xlsx而非report.xlsx—— 避免甲方说“你上次给的报表和这次一样”用时间戳证明数据是实时生成答辩前在甲方电脑上预装 JDK 8u202 Tomcat 8.5.93U盘里放好安装包现场演示“解压→双击 startup.bat→浏览器打开”不依赖甲方环境——这是让甲方信任你“真能落地”的终极动作。希望帮到你。本文还有配套的精品资源点击获取