FEATURED · 精选文章

JSP+Access手机销售系统开发详解:从建库到部署全流程

发布时间 / 2026/9/11 11:57:10
来源 / 创域科博编辑部
栏目 / 资讯中心
JSP+Access手机销售系统开发详解:从建库到部署全流程 简介这是一套围绕手机销售场景、采用JSP与Access数据库开发的完整项目资料包面向需要完成课程设计或毕业设计的Java学习者。资料内容覆盖从需求分析、系统设计、编码实现到测试验收的完整软件生命周期能帮助使用者解决项目文档不完整、代码结构不清晰等典型问题。压缩包体积仅2.26MB内部包含项目报告、开题报告、需求说明书、详细设计说明书、数据库设计说明书、测试计划以及全部源代码各类文档与代码相互配套便于按阶段查阅。目前已有75人学习。借助这些资料可以深入领会JSP动态网页技术与关系型数据库的结合方式掌握手机型号展示、库存管理、订单处理、用户注册登录等核心功能的实现细节同时参考规范化的项目文档写法为独立完成同类管理系统积累实战经验。1. JSPAccess手机销售系统课程设计里最需要先想清楚的那几件事很多人拿到基于jspaccess的手机销售系统这个题目第一反应是找现成源码但我更建议你先想清楚一件事Access这种文件型数据库配上JSP这种动态页面技术到底解决的是业务逻辑验证还是生产级并发。答案是前者。JSP把手机型号、价格、库存动态渲染成HTMLAccess把数据落在本地一个.accdb文件里整个系统不需要独立数据库服务器杀毒软件不拦拷给老师演示也方便。这个组合特别适合时间紧、又需要同时交项目报告、源代码和开题文档的课程设计。下面按从建库到部署再到验收演示的顺序把一个可复现的路径讲清楚。2. JSPAccess手机销售系统的Access库从需求分析到表结构2.1 需求分析落地成表用户、手机、订单的主表设计需求分析文档里会写管理员维护手机信息用户浏览下单落到Access里就是四张表。以下是常见字段方案表名关键字段类型用途UsersuserId, userName, password, isAdmin自动编号/文本/数字登录注册区分普通用户和管理员PhonephoneId, brand, model, price, stock自动编号/文本/货币/整数商品展示与库存扣减OrdersorderId, userId, orderDate, totalPrice自动编号/数字/日期/货币订单主表OrderDetaildetailId, orderId, phoneId, quantity, price自动编号/数字/数字/货币订单子表记录每部手机购买数量设计时让userId关联Users表phoneId关联Phone表这样按品牌统计销量时不需要解析长文本字段。Access里打开“数据库工具→关系”把Orders.userId连到Users.userIdOrderDetail.phoneId连到Phone.phoneId勾选“实施参照完整性”和“级联删除相关记录”。数据库层面先挡住脏数据JSP层再配合DAO校验两条线兜底。2.1.1 用SQL建表而不是鼠标点建表虽然Access支持可视化建表但项目报告里通常会要求贴SQL语句。在Access的查询设计视图里切到SQL视图执行CREATE TABLE Phone ( phoneId AUTOINCREMENT PRIMARY KEY, brand TEXT(50), model TEXT(50), price CURRENCY, stock INTEGER, description MEMO );AUTOINCREMENT对应JSP里的自增主键CURRENCY是Access的货币类型避免double浮点误差MEMO是长文本放手机参数和卖点介绍。在JDBC读ResultSet时CURRENCY映射为BigDecimal正好可以直接set到页面上不会出现0.999999这类金额误差。2.2 JSP访问Access用UCanAccess替代JDBC-ODBC桥早期教材让配ODBC数据源类名是sun.jdbc.odbc.JdbcOdbcDriver。JDK 8开始移除了JDBC-ODBC桥新项目直接报ClassNotFoundException。现在常见做法是使用UCanAccess它通过Jackcess直接读写.mdb/.accdb文件不需要在控制面板建DSN也不依赖系统位数。把ucanaccess、jackcess以及依赖的commons-lang、commons-logging放入WEB-INF/lib然后写一个连接工具类package util; import java.sql.*; public class DBUtil { // 固定外部路径避免相对路径找错目录 private static final String URL jdbc:ucanaccess:///E:/phonesale/db/phone.accdb; static { try { Class.forName(net.ucanaccess.jdbc.UcanaccessDriver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConn() throws SQLException { return DriverManager.getConnection(URL); } }注意URL里三个斜杠jdbc:ucanaccess:///后面跟绝对路径。如果只写两个斜杠会被当成host:port格式去解析必然连不上。若把.accdb放进Tomcat的webapps目录锁会和文件锁冲突建议放外部目录。UCanAccess驱动在JDK 8及以上运行更稳低于这个版本建议直接升级JDK。2.3 把SQL收进DAO订单事务别散在JSP里有些参考代码直接在JSP脚本片段里写JDBC页面一多就难以维护。按照项目报告里“系统设计”一节的要求三层结构更完整。PhoneDAO负责商品查询UserDAO负责登录OrderDAO负责下单事务。下面是一个带事务的下单方法public boolean createOrder(int userId, ListCartItem cart) { Connection conn null; try { conn DBUtil.getConn(); conn.setAutoCommit(false); // 关闭自动提交订单与明细同生共死 String insertOrder INSERT INTO Orders(userId, orderDate, totalPrice) VALUES(?,?,?); PreparedStatement ps1 conn.prepareStatement(insertOrder, Statement.RETURN_GENERATED_KEYS); ps1.setInt(1, userId); ps1.setDate(2, new java.sql.Date(System.currentTimeMillis())); double total cart.stream().mapToDouble( i - i.getPrice().doubleValue() * i.getQuantity()).sum(); ps1.setDouble(3, total); ps1.executeUpdate(); ResultSet keys ps1.getGeneratedKeys(); int orderId keys.next() ? keys.getInt(1) : 0; String insertDetail INSERT INTO OrderDetail(orderId, phoneId, quantity, price) VALUES(?,?,?,?); String updateStock UPDATE Phone SET stock stock - ? WHERE phoneId ? AND stock ?; for (CartItem item : cart) { try (PreparedStatement ps2 conn.prepareStatement(insertDetail)) { ps2.setInt(1, orderId); ps2.setInt(2, item.getPhoneId()); ps2.setInt(3, item.getQuantity()); ps2.setBigDecimal(4, item.getPrice()); ps2.executeUpdate(); } // 乐观更新库存不够时影响行数为0直接回滚 try (PreparedStatement ps3 conn.prepareStatement(updateStock)) { ps3.setInt(1, item.getQuantity()); ps3.setInt(2, item.getPhoneId()); ps3.setInt(3, item.getQuantity()); if (ps3.executeUpdate() 0) { throw new SQLException(库存不足或手机不存在); } } } conn.commit(); return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { e.printStackTrace(); } } return false; } finally { if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }关键点是setAutoCommit(false)之后所有SQL必须在同一个事务里执行中途任一一步失败就rollback。WHERE phoneId ? AND stock ?这一段是防止超卖的关键JDBC setInt时参数顺序不能错。Access本身并发能力弱这种乐观更新方式在课程设计场景里足够。3. 手机销售系统页面流程列表、购物车、管理员的增删改查3.1 手机列表页JSTL循环渲染ResultSet手机销售系统的门面是首页的手机列表。Servlet从PhoneDAO取List 后请求转发到list.jsp% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table border1 tr th品牌/thth型号/thth价格/thth库存/thth操作/th /tr c:forEach items${phoneList} varp tr td${p.brand}/td td${p.model}/td td${p.price}/td td${p.stock}/td tda hrefaddCart?phoneId${p.phoneId}加入购物车/a/td /tr /c:forEach /table这里最核心的是contentType里的UTF-8否则从Access里读出的中文会变成问号。EL表达式用${p.brand}访问getterPhone类里必须有getBrand这个方法而不是把字段直接暴露。如果想让列表更灵活也可以引入jsp jsonarray import把phoneList序列化成JSON交给前端Bootstrap Table渲染分页排序会方便很多。3.2 用户登录与注册Session存登录状态登录页提交到LoginServlet先查Users表成功后再把用户对象放进SessionString username request.getParameter(username); String password request.getParameter(password); User user userDAO.findByUsernameAndPassword(username, password); if (user ! null) { // 登录成功才写session失败保留原页面 HttpSession session request.getSession(); session.setAttribute(user, user); if (user.getIsAdmin() 1) { response.sendRedirect(admin/index.jsp); } else { response.sendRedirect(index.jsp); } } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); }findByUsernameAndPassword内部必须用PreparedStatement参数化查询不能把username和password拼接进SQL字符串否则登录接口就是注入重灾区。登出时调用session.invalidate()清掉整个会话只remove一个属性容易造成权限残留。3.3 加入购物车与下单Session临时存储与事务写入购物车放Session用户加购不写库结账时才走2.3的事务方法HttpSession session request.getSession(); ListCartItem cart (ListCartItem) session.getAttribute(cart); if (cart null) { cart new ArrayList(); session.setAttribute(cart, cart); } int phoneId Integer.parseInt(request.getParameter(phoneId)); for (CartItem item : cart) { if (item.getPhoneId() phoneId) { item.setQuantity(item.getQuantity() 1); return; } } // 从Phone带出名称和价格购物车页面不用反复查库 CartItem item new CartItem(); item.setPhoneId(phoneId); item.setQuantity(1); Phone phone phoneDAO.findById(phoneId); item.setName(phone.getModel()); item.setPrice(phone.getPrice()); cart.add(item);CartItem里同时存phoneId、name、price是因为JSP渲染购物车时只需要这三项没必要每次都回查Phone表。真正下单时再从Phone表读一次价格做复核避免Session里价格被人为改掉。3.4 后台管理手机信息维护的update/delete管理员按品牌或型号搜索后编辑手机价格和库存public void updatePhone(Phone p) { String sql UPDATE Phone SET brand?, model?, price?, stock?, description? WHERE phoneId?; try (Connection conn DBUtil.getConn(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, p.getBrand()); ps.setString(2, p.getModel()); ps.setBigDecimal(3, p.getPrice()); ps.setInt(4, p.getStock()); ps.setString(5, p.getDescription()); ps.setInt(6, p.getPhoneId()); int rows ps.executeUpdate(); // 更新行数为0说明记录不存在主动抛异常便于前端提示 if (rows 0) { throw new RuntimeException(手机不存在可能被删除); } } catch (SQLException e) { throw new RuntimeException(更新手机信息失败, e); } }delete操作建议加二次确认前端用onclickreturn confirm(确定删除?)避免演示时误删数据。如果手机已被OrderDetail引用Access会因参照完整性拒绝删除这时要先清掉订单明细这正好可以在项目报告里作为数据完整性保护的实际案例。4. 部署Tomcat的必调参数与JSPAccess常见报错4.1 内存访问冲突process exited with code 3221225477 / 0xc0000005Tomcat或JSP首次编译时如果出现process exited with code 3221225477 / 0xc0000005说明JVM进程崩溃到了非法内存地址。在JSPAccess项目里最常见原因是驱动和JDK位数不匹配或者UCanAccess依赖的原生文件映射和Tomcat冲突。排查步骤按顺序做第一确认java -version和Tomcat的JAVA_HOME指向同一个JDK32位JDK配64位Tomcat最容易触发。第二停掉Tomcat删掉work目录下整个Catalina缓存避免旧class文件和新驱动冲突。第三如果崩溃总是发生在读取.accdb时把Access数据库另存为2002-2003格式.mdbUCanAccess对旧格式的兼容更保守。处理完记得重启Tomcat并观察catalina.out最后一段日志。4.2 Access文件锁与access deniedAccess出于并发控制会在数据文件旁边生成.ldb锁文件。当JSP里连接没关闭时第二次请求会报Could not lock file。解决办法是把所有Connection、PreparedStatement、ResultSet放到try-with-resources里保证方法结束就释放。如果仍频繁报错检查E:/phonesale/db目录的写入权限进程对.accdb旁边生成的.ldb文件也要有读写权。这里说的access denied和MySQL的ERROR 1045 access denied for user rootlocalhost完全不同后者是MySQL账号密码错两个报错不要混在一起查。Access项目里出现access denied先看文件目录权限和是否被另一个进程独占。4.3 JSP编译class文件路径与页面不刷新问题JSP首次访问后Tomcat会在work/Catalina/localhost/项目名/org/apache/jsp/目录下生成对应的.java和.class文件。如果Maven或IDEA热部署失效改完JSP后页面还是旧的按下面顺序处理浏览器按CtrlF5强制刷新排除浏览器缓存。查看Tomcat的conf/web.xml里是否有developmentfalse配置开发阶段要删掉否则JSP会缓存编译结果。直接删除work目录下项目名对应的整个目录重启Tomcat重新编译。这一步能解决90%的“JSP改了不生效”问题因为.class文件就是JSP页面的最终产物。4.4 web.xml统一定义字符编码过滤器Access读取出来中文乱码最常发生在请求和响应的编码不一致时。在web.xml最前面加一个编码过滤filter filter-nameencoding/filter-name filter-classorg.apache.catalina.filters.SetCharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencoding/filter-name url-pattern/*/url-pattern /filter-mapping这是Tomcat自带的过滤器不需要额外jar包。它会把request和response的编码统一成UTF-8。注意Access默认排序规则可能是简体中文GBK存进去时如果用GBK读出后需要在DAO层转码建议建表时把Access数据库的排序规则设为Unicode能省去后面大量乱码修复时间。5. 验收加分项登录过滤器与销售统计Excel导出5.1 用Filter拦截admin目录权限校验只写一次管理后台每个页面都检查session会重复写代码直接写一个Filter拦截/admin/*public class LoginFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpSession session request.getSession(false); if (session null || session.getAttribute(user) null) { ((HttpServletResponse) resp).sendRedirect(../login.jsp); return; } User u (User) session.getAttribute(user); if (u.getIsAdmin() ! 1) { resp.getWriter().write(无权限访问); return; } chain.doFilter(request, resp); } }映射URL是/admin/*重定向时要写相对路径../login.jsp因为Filter里request的当前路径是项目根下的admin目录相对路径要上跳一级。权限校验只写这一处后续新增后台页面自动被保护项目报告里在“安全设计”小节可以明确写出这一条。5.2 按品牌统计销量并导出Excel汇总SQL用Access语法SELECT p.brand, SUM(od.quantity) AS totalQty FROM OrderDetail od INNER JOIN Phone p ON od.phoneId p.phoneId GROUP BY p.brand ORDER BY totalQty DESC;导出时把响应设为Excel MIME类型数据用制表符分隔Excel能直接打开response.setContentType(application/vnd.ms-excel;charsetUTF-8); response.setHeader(Content-Disposition, attachment;filenamesales.xls); PrintWriter out response.getWriter(); out.println(品牌\t销量); // 遍历ResultSet输出 while (rs.next()) { out.println(rs.getString(brand) \t rs.getInt(totalQty)); }演示时在管理员首页放一个“导出销售统计”按钮点击后浏览器直接下载xls。把导出的文件用Excel打开配合代码里的UCanAccess连接串就能向老师展示“数据从Access来经过JSP输出最终落地成Excel报表”这一整条链路。再加一个按日期的订单明细查询用同样方式导出整个系统的数据流就完整了。本文还有配套的精品资源点击获取
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻