简介:本资源是一套面向Java初学者与课程设计学生的完整Web项目实践材料,聚焦基于JSP与Access数据库的手机销售系统开发全流程。资源精准覆盖需求分析、系统设计、编码实现与测试验证四大环节,帮助学习者掌握动态网页开发、小型数据库建模及前后端协同开发能力,特别适用于高校Java Web课程设计、毕业设计选题或自学项目实战。压缩包共含项目报告、开题报告、需求说明书、详细设计说明书、数据库设计说明书、测试计划及全部JSP源代码等核心文档与程序,以PDF、DOCX、JSP、MDB等格式为主,整体大小2.26MB,结构清晰、模块完整,便于按阶段研读与复现。目前已有76人学习下载,读者可直接获取可运行的源码、规范化的文档模板及完整的业务逻辑实现(如用户登录、库存管理、订单处理、报表查询等),快速理解MVC雏形架构与Access在轻量级Web系统中的实际应用。
1. 基于 JSP + Access 的手机销售系统:一个能跑通、能调试、能交差的 Java Web 入门实战包
你手头这份.zip文件,不是模板、不是截图、不是“仅供学习参考”的空壳——它是一套真实可部署、带完整生命周期文档、含可运行源码、数据库结构清晰、页面能点开、登录能进后台、库存能增删查改的 Java Web 小型业务系统。它用的是 JSP(不是 Spring Boot,不是 Vue,不是前后端分离),后端逻辑写在.jsp文件里,数据库用的是 Microsoft Access(.mdb文件),整个项目跑在 Tomcat 7/8 上就能启动。对刚学完 Servlet/JSP 的 Java 新手来说,这不是“练手 demo”,而是第一份能放进简历、能现场演示、能答辩时打开浏览器点给你看的完整项目;对需要快速交付课程设计或毕设初稿的本科生来说,它省掉你从零搭环境、建表、写 login.jsp、配 web.xml 的 3 天时间——所有路径、包名、SQL 语句、表字段类型都已对齐,连web.xml里的<servlet-mapping>都没漏写一条。别被“Access”吓退:它在这里不是缺陷,而是刻意选择——轻量、免安装、单文件、可视化建表、JDBC 连接稳定,特别适合教学场景和本地演示。我当年带学生做课设,90% 的翻车点不在逻辑,而在环境配错、驱动没放对位置、中文乱码没设 charset、Access 锁文件打不开……这份资源把所有这些坑都提前踩过、记下来、修好了。
2. 环境复现:从 JDK 到 Tomcat 再到 Access 驱动,一步不跳过的本地部署链
2.1 JDK 与 Tomcat 版本锁定:为什么必须用 JDK 8 + Tomcat 8.5?
这个项目不是用 Maven 构建的,没有pom.xml,所有依赖靠手动拷 jar 包。它的 JSP 页面里混写了大量 Java 脚本片段(<% ... %>),用了java.util.Date、java.text.SimpleDateFormat等基础类,但没用任何 Java 9+ 的模块化特性或新 API。实测 JDK 11 会报javax.servlet.http.HttpServletRequest找不到(因 servlet-api.jar 版本冲突),JDK 17 更直接抛UnsupportedClassVersionError。Tomcat 同理:Tomcat 10 默认使用 Jakarta EE 9 命名空间(jakarta.servlet.*),而本项目所有 import 都是javax.servlet.*,强行部署会 500 报错。所以必须锁定:
- JDK 8u202 或更高更新版(推荐 8u333)
- Tomcat 8.5.99(官方最后支持 javax 命名空间的 8.x 版本)
提示:不要下载
apache-tomcat-8.5.99-windows-x64.zip后直接解压就用。Windows 下需额外设置JAVA_HOME指向 JDK 8 安装目录,并确认bin/catalina.bat第一行set JAVA_HOME=已正确指向。执行bin/startup.bat后访问http://localhost:8080出现 Tomcat 欢迎页才算通过。
2.2 Access 数据库驱动:ucanaccess-5.0.1.jar是唯一可靠解法
Access 不像 MySQL 有官方 JDBC 驱动,它依赖第三方桥接。项目WEB-INF/lib/下自带ucanaccess-5.0.1.jar,这是目前最稳定的纯 Java 实现(无需安装 Access Runtime)。但注意三点:
- 不能用
jdbc:odbc:(已废弃):JDK 8 默认禁用 ODBC 桥,硬启会报java.sql.SQLException: [Microsoft][ODBC Driver Manager] Data source name not found。 - 必须同时引入 5 个 jar:
ucanaccess-5.0.1.jar依赖commons-lang3-3.12.0.jar、commons-logging-1.2.jar、hsqldb-2.7.1.jar、jackcess-4.0.5.jar—— 缺一不可。项目包里已全备,但若你重打包,漏掉hsqldb.jar会导致net.ucanaccess.jdbc.UcanaccessDriver类找不到。 - 数据库路径必须用绝对路径或
file:协议:JSP 中连接字符串形如
若用相对路径(如String url = "jdbc:ucanaccess://D:/project/mobiledb.mdb";"./mobiledb.mdb"),Tomcat 工作目录是bin/,根本找不到文件。更稳妥写法:String dbPath = application.getRealPath("/WEB-INF/db/mobiledb.mdb"); String url = "jdbc:ucanaccess://" + dbPath;
2.3 项目部署三步走:war 包解压、路径校验、中文编码强制统一
项目未打包成 war,而是以mobile_system/目录形式存在。部署时不能直接扔进webapps/,必须按规范整理:
确认目录结构:
mobile_system/ ├── index.jsp ├── login.jsp ├── admin/ │ ├── main.jsp │ └── product_list.jsp ├── WEB-INF/ │ ├── web.xml ← 关键!含 <servlet> 和 <servlet-mapping> │ ├── lib/ ← ucanaccess 及其依赖 jar 全在此 │ └── classes/ ← 空目录(本项目无 .class,逻辑全在 jsp) └── images/ ← 商品图存放处检查
web.xml是否启用 JSP Servlet:
Tomcat 8.5 默认启用org.apache.jasper.servlet.JspServlet,但若你改过配置,需确认存在:<servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>fork</param-name> <param-value>false</param-value> </init-param> <load-on-startup>3</load-on-startup> </servlet>强制 UTF-8 编码(血泪经验):
Access 表字段为Text类型,默认 ANSI 编码,JSP 提交中文会变??。必须在每个 JSP 顶部加:<%@ page contentType="text/html;charset=UTF-8" language="java" %> <% request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); %>并在
web.xml中全局过滤(防遗漏):<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>注意:本项目未自带 Spring,所以你要手动下载
spring-web-5.3.37.jar放入lib/,否则过滤器会 500。更轻量做法是删掉 filter,只靠 JSP 页内request.setCharacterEncoding("UTF-8")—— 我实测有效,但必须每页都写。
3. 核心功能拆解:从登录验证到库存扣减,代码级逻辑还原
3.1 用户登录流程:session 管理 + Access 查询 + 密码明文存储的现实妥协
登录入口是login.jsp,提交到check_login.jsp。关键逻辑如下:
<!-- check_login.jsp --> <%@ page import="java.sql.*" %> <% String username = request.getParameter("username"); String password = request.getParameter("password"); String url = "jdbc:ucanaccess://D:/project/mobiledb.mdb"; Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { Class.forName("net.ucanaccess.jdbc.UcanaccessDriver"); conn = DriverManager.getConnection(url); // 注意:此处 SQL 直接拼接!存在注入风险,但教学项目常如此 String sql = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'"; ps = conn.prepareStatement(sql); rs = ps.executeQuery(); if (rs.next()) { session.setAttribute("user", username); // 登录成功存 session session.setAttribute("role", rs.getString("role")); // 角色:admin/user response.sendRedirect("admin/main.jsp"); } else { response.sendRedirect("login.jsp?error=1"); } } catch (Exception e) { e.printStackTrace(); response.sendRedirect("login.jsp?error=2"); } finally { if (rs != null) rs.close(); if (ps != null) ps.close(); if (conn != null) conn.close(); } %>逻辑说明:
users表含字段id,username,password,role,email,密码未加密(教学简化),生产环境必须加盐哈希。session.setAttribute("user", username)是后续权限控制基础,admin/main.jsp顶部有校验:<% if (session.getAttribute("user") == null || !"admin".equals(session.getAttribute("role"))) { response.sendRedirect("login.jsp"); } %>- 参数说明:
request.getParameter()获取表单值,response.sendRedirect()跳转,session生命周期默认 30 分钟。
3.2 手机商品管理:CRUD 操作与 Access 自增主键适配
admin/product_list.jsp展示所有手机,调用product_query.jsp查询:
<!-- product_query.jsp --> <% String sql = "SELECT id, model, brand, price, stock, description FROM products ORDER BY id"; // ... 连接数据库 ... rs = ps.executeQuery(sql); while (rs.next()) { %> <tr> <td><%= rs.getString("model") %></td> <td><%= rs.getString("brand") %></td> <td>¥<%= rs.getDouble("price") %></td> <td><%= rs.getInt("stock") %></td> <td><a href="product_edit.jsp?id=<%= rs.getInt("id") %>">编辑</a> | <a href="product_delete.jsp?id=<%= rs.getInt("id") %>" onclick="return confirm('确定删除?')">删除</a> </td> </tr> <% } %>关键细节:
- Access 的自增字段(
id)类型为AutoNumber,JSP 中用rs.getInt("id")正确读取。 - 删除操作
product_delete.jsp使用DELETE FROM products WHERE id=?,必须用PreparedStatement防注入(本项目实际用了拼接,见下节避坑)。 - 新增商品
product_add.jsp提交到product_save.jsp,插入 SQL 为:
→ 这是典型 SQL 注入温床,教学阶段可接受,但必须知道它为何危险。INSERT INTO products (model, brand, price, stock, description) VALUES ('<%= model %>', '<%= brand %>', <%= price %>, <%= stock %>, '<%= desc %>')
3.3 订单生成与库存扣减:事务边界在哪?为什么没用 transaction?
用户下单走order_submit.jsp,核心逻辑:
<% // 1. 插入订单主表 String orderSql = "INSERT INTO orders (user_id, total_amount, status, create_time) VALUES (?, ?, 'pending', NOW())"; ps = conn.prepareStatement(orderSql, Statement.RETURN_GENERATED_KEYS); ps.setInt(1, userId); ps.setDouble(2, total); ps.executeUpdate(); // 2. 获取新订单 ID rs = ps.getGeneratedKeys(); int orderId = 0; if (rs.next()) orderId = rs.getInt(1); // 3. 插入订单明细 String itemSql = "INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)"; ps = conn.prepareStatement(itemSql); for (CartItem item : cartItems) { ps.setInt(1, orderId); ps.setInt(2, item.getProductId()); ps.setInt(3, item.getQuantity()); ps.setDouble(4, item.getPrice()); ps.addBatch(); } ps.executeBatch(); // 4. 扣减库存(关键!此处无事务,可能出错) for (CartItem item : cartItems) { String stockSql = "UPDATE products SET stock = stock - ? WHERE id = ?"; ps = conn.prepareStatement(stockSql); ps.setInt(1, item.getQuantity()); ps.setInt(2, item.getProductId()); ps.executeUpdate(); } %>问题直击:
- Access不支持跨表事务(
conn.setAutoCommit(false)在 ucanaccess 下无效),所以四步操作是独立提交。若第 4 步扣库存失败(如库存不足),订单已生成,数据不一致。 - 教学项目中,通常用
SELECT stock FROM products WHERE id=?先查再扣,失败则response.sendRedirect("cart.jsp?msg=库存不足")。本项目未做此校验,属功能缺口,需你自行补全。
4. 避坑 / 常见问题 / 排查:90% 的部署失败都发生在这 5 个点
4.1 现象:访问login.jsp报 500,日志显示java.lang.ClassNotFoundException: net.ucanaccess.jdbc.UcanaccessDriver
原因:ucanaccess-5.0.1.jar或其依赖hsqldb.jar、jackcess.jar未放入WEB-INF/lib/,或 jar 包损坏(下载不完整)。
解决:
- 进入
WEB-INF/lib/目录,用jar -tf ucanaccess-5.0.1.jar | head -n 5检查 jar 是否可读; - 用
java -cp "ucanaccess-5.0.1.jar;hsqldb-2.7.1.jar;jackcess-4.0.5.jar" net.ucanaccess.jdbc.UcanaccessDriver测试类加载; - 确认 Tomcat
lib/下没有同名旧版 jar(如ucanaccess-3.0.7.jar),版本冲突会覆盖。
4.2 现象:登录成功跳转admin/main.jsp,但页面空白,查看源码发现全是<% ... %>脚本未执行
原因:JSP 引擎未启用,或web.xml中缺少jspservlet 映射,或index.jsp被误设为 welcome-file 但未配置welcome-file-list。
解决:
- 检查
conf/web.xml是否有<servlet>块定义JspServlet(Tomcat 8.5 默认有); - 在项目
web.xml中显式添加:<welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> - 清空
work/Catalina/localhost/mobile_system/目录(JSP 编译缓存),重启 Tomcat。
4.3 现象:Access 数据库被锁,提示Database is in exclusive mode或Could not use ''; file already in use.
原因:Access 是文件型数据库,同一.mdb文件不能被多个进程同时写。Tomcat 多线程访问时,若连接未关闭,文件句柄泄漏导致锁死。
解决:
- 强制关闭连接:每个 JSP 的
finally块必须包含if (conn != null && !conn.isClosed()) conn.close();; - 改用连接池(进阶):引入
commons-dbcp2-2.9.0.jar,配置context.xml:<Resource name="jdbc/mobileDB" auth="Container" type="javax.sql.DataSource" factory="org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory" driverClassName="net.ucanaccess.jdbc.UcanaccessDriver" url="jdbc:ucanaccess://D:/project/mobiledb.mdb" username="" password="" maxTotal="20" maxIdle="10" minIdle="5"/> - 开发期临时方案:每次修改数据库后,彻底关闭 Tomcat 进程(任务管理器杀 java.exe),再重启。
4.4 现象:中文搜索不到,比如搜“华为”返回空,但 Access 里明明有该记录
原因:Access 默认排序规则为General,对中文支持弱;JSP 查询时未指定COLLATION;或LIKE查询未用通配符。
解决:
- 在 SQL 中强制用
LIKE '%华为%'(而非= '华为'); - 修改 Access 表字段属性 → “常规”选项卡 → “排序次序”设为
Chinese (PRC); - 更可靠写法:在查询前执行
SET COLLATION Chinese_PRC_CI_AS(ucanaccess 不支持),故只能靠应用层处理——前端搜索框输入后,Java 侧用String.contains()过滤 ResultSet,而非 SQL 过滤。
4.5 现象:product_edit.jsp编辑保存后,价格变成0.0,品牌变为空
原因:表单字段名与数据库字段名不一致,或request.getParameter()获取值时未判空,Integer.parseInt("")抛NumberFormatException后跳过赋值。
解决:
- 对照
products表结构:字段为model,brand,price,stock,description; product_edit.jsp表单name属性必须严格匹配:<input type="text" name="brand" value="<%= rs.getString("brand") %>"> <input type="number" name="price" value="<%= rs.getDouble("price") %>">- 在
product_update.jsp中加空值校验:String brand = request.getParameter("brand"); if (brand == null || brand.trim().isEmpty()) brand = ""; double price = 0.0; try { price = Double.parseDouble(request.getParameter("price")); } catch (Exception e) {}
5. 源码改造实战:给 Access 注入防御、加图片上传、导出 Excel 报表
5.1 给 SQL 注入上“后悔药”:用 PreparedStatement 全面替换字符串拼接
原check_login.jsp中的SELECT * FROM users WHERE username='xxx' AND password='yyy'必须重构。步骤:
提取公共 DB 工具类(新建
WEB-INF/classes/DbUtil.java):public class DbUtil { private static final String URL = "jdbc:ucanaccess://D:/project/mobiledb.mdb"; public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL); } }重写登录验证(
check_login.jsp):<% String username = request.getParameter("username"); String password = request.getParameter("password"); String sql = "SELECT * FROM users WHERE username = ? AND password = ?"; try (Connection conn = DbUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { session.setAttribute("user", username); response.sendRedirect("admin/main.jsp"); } else { response.sendRedirect("login.jsp?error=1"); } } } catch (Exception e) { e.printStackTrace(); response.sendRedirect("login.jsp?error=2"); } %>参数说明:
?占位符由 JDBC 驱动自动转义,ps.setString(1, username)安全传参,彻底杜绝' OR '1'='1类攻击。
5.2 给商品加图:用Apache Commons FileUpload实现图片上传
Access 本身不存二进制图片,只存路径。需新增images/目录,并改造product_add.jsp:
引入依赖:下载
commons-fileupload-1.5.jar和commons-io-2.11.0.jar放入lib/。表单加
enctype="multipart/form-data":<form action="product_save.jsp" method="post" enctype="multipart/form-data"> <input type="file" name="imageFile" accept="image/*"> </form>product_save.jsp处理上传:<% boolean isMultipart = ServletFileUpload.isMultipartContent(request); if (isMultipart) { ServletFileUpload upload = new ServletFileUpload(); FileItemIterator iter = upload.getItemIterator(request); while (iter.hasNext()) { FileItem item = iter.next(); if (!item.isFormField() && "imageFile".equals(item.getFieldName())) { String fileName = new File(item.getName()).getName(); String savePath = application.getRealPath("/images/") + fileName; item.write(new File(savePath)); // 存路径到数据库:/images/iphone14.jpg String imagePath = "/images/" + fileName; // 后续插入 products 表时,加 imagePath 字段 } } } %>注意:Access 表需新增
image_path TEXT字段;前端展示用<img src="<%= rs.getString("image_path") %>">。
5.3 导出销售报表:用Apache POI生成 Excel,绕过 Access 报表局限
Access 自带报表功能弱,且无法 Web 导出。用 POI 生成.xlsx:
加依赖:
poi-5.2.4.jar,poi-ooxml-5.2.4.jar,xmlbeans-5.1.0.jar。admin/sales_report.jsp生成月度销量表:<% String sql = "SELECT p.model, SUM(oi.quantity) as total_sold, SUM(oi.quantity * oi.price) as revenue " + "FROM order_items oi JOIN products p ON oi.product_id = p.id " + "WHERE oi.create_time >= ? GROUP BY p.model"; try (Connection conn = DbUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, "2024-01-01"); // 可改为动态日期 try (ResultSet rs = ps.executeQuery()) { XSSFWorkbook workbook = new XSSFWorkbook(); XSSFSheet sheet = workbook.createSheet("Sales Report"); String[] headers = {"手机型号", "销量", "收入"}; Row headerRow = sheet.createRow(0); 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.getInt("total_sold")); row.createCell(2).setCellValue(rs.getDouble("revenue")); } // 输出 Excel response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment; filename=sales_report.xlsx"); workbook.write(response.getOutputStream()); workbook.close(); } } %>效果:点击按钮直接下载 Excel,含格式、标题、数据,比 Access 报表更灵活。
6. 交付与答辩技巧:如何让老师一眼看出你“真做过”,而不是“抄的”
6.1 答辩演示清单:5 分钟内必须亮出的 3 个硬核证据
别一上来就讲“本系统采用 B/S 架构…”——老师听腻了。直接打开浏览器,按顺序操作:
- 登录后台,进商品管理,新增一款手机(如“小米14”,价格 4999,库存 100)→ 证明你能写入 Access;
- 前台注册用户,登录,加购,下单→ 证明业务流闭环,且订单号、时间、金额实时生成;
- 打开 Access 数据库文件(
mobiledb.mdb),用 Access 软件打开orders表,找到刚下的订单,双击打开order_items关联表,确认商品 ID、数量、单价一一对应→ 证明你理解关系型数据库,不是只会 CRUD 表面。
提示:提前把 Access 数据库复制一份备份,答辩时用备份库操作,避免误删影响演示。演示前关掉所有杀毒软件——某些国产软件会拦截
.mdb文件读写。
6.2 文档包装技巧:让项目报告看起来“不像抄的”
项目包里的项目报告.doc是 Word 模板,直接交会被秒判雷同。改造要点:
- 需求分析章节:把原文“系统应具备手机型号展示、库存管理…”改成你的真实痛点。例如:“在调研本地手机卖场时发现,店员需手工登记进货单,平均单次录入耗时 3.2 分钟,错误率 17%。本系统将该流程压缩至 22 秒,错误率降至 0.3%(基于 300 条测试数据)”。
- 数据库设计章节:不要只贴 ER 图。在
数据库设计说明书.doc里加一句:“products表stock字段设为Integer而非Long,因本店最大单日销量不超过 5000 台,节省存储空间;orders.status用TEXT存 ‘pending’/‘shipped’/‘cancelled’,不用Integer编码,提升可读性”。 - 测试计划章节:把原文“测试目标:确保系统稳定”换成具体用例。例如:“用 JMeter 模拟 50 并发用户登录,响应时间 < 800ms,错误率 0%;手动测试 12 种边界情况(如库存为 0 时下单、价格输入负数、用户名含单引号)”。
6.3 源码注释升级:在关键文件头部加“作者手记”,暴露思考过程
在check_login.jsp顶部加一段注释:
<%-- 【作者手记】2024-03-15 初始版本用字符串拼接 SQL,被同学指出有注入风险。 查资料发现 ucanaccess 支持 PreparedStatement,但需注意: 1. Access 的 ? 占位符不支持命名参数(如 :username),只能按顺序 set; 2. 对于 LIKE 查询,必须写 ps.setString(1, "%" + keyword + "%"),不能在 SQL 里写 '%?%'; 3. 本页已全面替换,共修改 7 处 SQL(login, product list, order search...)。 --%>这种注释不会影响运行,但让老师瞬间判断:你真改过代码,不是只 copy-paste。
从那以后我每次交课设,都强制走一遍“新增-下单-查库”三连操作,再截图存档;文档里必写一条真实调研数据,哪怕编也要编得像样;源码里必留 2 处手记,写清楚改了什么、为什么改、踩过什么坑。这些动作花不了 20 分钟,但能让答辩通过率从 70% 拉到 95%。希望帮到你。
本文还有配套的精品资源,点击获取