JSP+Access手机销售系统:全流程拆解与二次开发指南
简介一份基于JSP与Access数据库的手机销售系统完整项目资料包面向Java初学者、课程设计或毕业设计人员可用于学习Web应用从需求分析、系统设计、编码实现到测试验收的全流程。压缩包内汇集了核心源代码及整套配套文档包括项目报告、详细设计说明书、需求说明书、数据库设计说明书、开题报告和测试计划能够帮助读者对照理解模块划分、界面交互、数据表设计以及代码实现逻辑。资源包约2.26MB文件类型以.jsp/.java源代码和Word文档为主目录组织清晰便于按阶段查阅与动手实践。目前已有76人学习下载。借助这份资料使用者可以系统掌握JSP动态网页开发技巧学会通过Access数据库对手机库存、销售记录、订单和客户信息进行增删改查并能够实现用户注册登录、手机型号展示、库存预警等常用功能同时熟悉需求分析、概要设计、详细设计、测试计划等项目文档的撰写方法对完成课程作业、参与竞赛或准备毕业设计都具有较高的参考价值。1. 一个JSPAccess的手机销售系统拆开后到底能学到什么临交项目前一夜某同学发现照教程抄的手机销售系统在别人电脑上跑得飞起自己笔记本一登录就报ClassNotFoundException。问了一圈没人告诉他这是JDK在作怪只觉得Access太玄学。这个压缩包能救他一整套基于JSPAccess的手机销售系统交付物开题报告、需求分析、数据库设计、详细设计、测试计划、项目报告外加全部源代码齐全。它解决的问题很朴素——让你走完一个Java Web项目从需求到验收的全流程。适合两类人拿它交课设、补毕设文档的在校生想看清请求怎么进、数据怎么存、页面怎么出的Java新手。前台管注册登录和手机下单后台管手机库存、订单与客户信息数据全放在Access文件里结构清晰适合拆开学。2. 项目全貌与技术选型六份文档加一份源码JSPAccess值不值得拆2.1 系统功能地图从用户注册到订单完成的完整业务闭环这个系统按角色天然分成两块前台给普通用户用后台给管理员用。一条完整业务链路是用户注册 → 登录 → 浏览手机列表 → 下单购买 → 管理员在后台看到新订单 → 确认订单并扣减库存 → 用户在我的订单里看到状态变化。整个闭环里的每一步操作最终都会落到Access数据库表里。手机展示、库存管理、订单处理、用户注册登录这几个核心需求对应到页面上可以整理成下面这张对照表方便拿到源码后在webapps目录里对号入座功能模块使用角色核心操作典型页面用户注册登录普通用户注册、登录、退出register.jsp、doLogin.jsp手机展示普通用户按品牌/型号浏览列表、查看详情phone_list.jsp、phone_detail.jsp下单购买普通用户选择手机、填写数量、生成订单order_add.jsp、my_order.jsp手机管理管理员手机型号增删改查、库存调整admin/phone_manage.jsp订单管理管理员查看订单、变更状态admin/order_manage.jsp客户管理管理员查看注册用户、禁用违规账户admin/user_manage.jsp提示页面文件名是我按这类JSP系统最常见的命名习惯整理的压缩包里可能略有差异拿到手后以实际文件名为准功能对照关系不变。这里有个容易被忽略的点前台和后台跑在同一个Tomcat进程里靠路径和Session做隔离。admin目录下的页面必须在逻辑上校验管理员身份否则任何用户直接输入admin/phone_manage.jsp的地址就能进后台这是初学者最喜欢漏掉的一个漏洞。源码里如果看到admin页面没有校验Session二次开发时第一件事就是把它补上。2.2 压缩包里的六份文档每一份对应开发周期的哪个阶段这份资源最值钱的不是JSP页面而是那六份文档。大部分网上下载的项目只有代码文档缺三少四而这个压缩包把项目启动到验收的全过程补齐了。每份文档在开发周期里的位置不一样拆开看的时候关注点也不一样文档对应阶段拆它看什么开题报告项目启动项目背景、研究意义、技术路线、预期成果需求说明书需求分析功能需求与非功能需求各模块的输入输出详细设计说明书概要/详细设计界面设计、功能模块划分、模块间调用关系数据库设计说明书数据库设计ER模型、表结构、字段定义、表间关联测试计划测试验收测试目标、方法策略、用例覆盖范围项目报告交付总结总体介绍、实现效果评估、后续改进方向我的建议是倒着读先读项目报告了解系统长什么样再读详细设计说明书看模块划分然后打开数据库设计说明书看表结构最后才去看代码。按照这个顺序代码里的每个类、每个SQL都能在文档里找到出处阅读效率比从头到尾硬啃高非常多。写毕设论文的时候这几份文档直接就是论文章节的素材来源自己从零憋一份出来少说要熬两个通宵。2.3 为什么JSPAccess放到今天仍然能当学习材料直接说结论JSPAccess不适合上生产但作为Java Web的入门案例它比Spring Boot更合适。JSP的请求/响应模型非常直观浏览器发一个请求Tomcat找到对应的JSP页面页面里的Java代码执行JDBC查数据库把结果拼成HTML返回给浏览器。没有框架封装没有starter依赖每一个环节都看得见摸得着。Access这边也有它的优势不需要安装数据库服务端不需要配置端口用Access打开一个.mdb文件就能建表、写数据、做查询。对于学生机器来说装一个MySQL服务端有时候比写代码还折腾而Access几乎零成本。数据库文件就是一个普通文件拷贝、备份、发给别人都方便这也解释了为什么很多课程设计项目至今还在用Access。技术选型上代码里常见的是JSP JavaBean DAO三层写法JSP负责页面展示和请求接收JavaBean后面演化为POJO负责封装数据DAO类负责所有JDBC操作。这种分层在今天的Spring生态里依然能找到影子——Controller对应JSPService对应DAO的封装思路Model对应JavaBean。把这个项目的分层看懂再去看Spring Boot会顺畅很多。注意这里说的是学习价值不是让你把这个方案搬进生产环境。生产环境数据库选型、连接池、事务控制这些Access和裸JDBC都撑不住别混为一谈。3. 数据库设计与环境搭建从ER图到Access表再到数据源配置3.1 数据库表结构手机、用户、订单三张主表如何组织数据打开数据库设计说明书核心是四张表用户表t_user、手机表t_phone、订单表t_order、订单明细表t_order_detail。用户表和手机表是基础数据订单表记录每一笔订单的概要信息明细表记录这笔订单里具体买了哪些手机、每款几台。为什么订单要拆成主表和明细两张表因为业务语义是一个订单包含多个手机型号如果不拆要么一张表里存冗余数据要么一条订单一行导致订单信息重复存储两种都是数据库设计上的坏味道。字段设计上常见做法是这样t_user用户表字段名类型说明userId自动编号主键username文本登录名唯一password文本登录密码realName文本真实姓名phone文本联系电话regTime日期/时间注册时间t_phone手机表字段名类型说明phoneId自动编号主键brand文本品牌model文本型号price货币售价stock数字库存数量description备注商品描述t_order订单主表和t_order_detail订单明细表的核心字段主表有orderId、userId、totalPrice、createTime、status明细表有detailId、orderId、phoneId、num、price。点开Access的关系视图可以看到t_order的orderId和t_user的userId、t_order_detail的orderId和主表的orderId都有连线连线就是外键约束的直观呈现。设计说明书写的是逻辑模型落到Access里就是这些物理表一一对应。3.2 环境准备JDK、Tomcat、Access驱动与数据源配置这个项目要跑起来环境上需要四样东西JDK、Tomcat、Access、JDBC驱动。前三样装好之后最关键的步骤是让Java代码能连上Access数据库。老教程里最常见的做法是走ODBC数据源流程是控制面板 → 管理工具 → 数据源(ODBC) → 添加 → 选择Access Driver → 指定.mdb文件路径 → 给数据源起个名字。这个过程配置一次就全局可用然后代码里用下面这种方式连接// 通过ODBC数据源名(DSN)连接Access数据库 Class.forName(sun.jdbc.odbc.JdbcOdbcDriver); String url jdbc:odbc:phoneDb; Connection conn DriverManager.getConnection(url);逻辑说明Class.forName加载驱动类DriverManager根据URL里的jdbc:odbc:phoneDb找到你在系统里配置的那个名为phoneDb的数据源建立连接。参数说明phoneDb必须和ODBC面板里配置的数据源名完全一致大小写不敏感但拼写必须对这是最常见的翻车点。如果你用的JDK比较新上面这段代码会直接报ClassNotFoundException因为新版JDK把sun.jdbc.odbc.JdbcOdbcDriver这个桥接类从标准库里移除了。这时候需要换用UCanAccess驱动它不依赖ODBC数据源直接在URL里写文件路径// 使用UCanAccess驱动直接连接.mdb文件不需要配置数据源 Class.forName(net.ucanaccess.jdbc.UcanaccessDriver); String url jdbc:ucanaccess://D:/workspace/phone_sale/db/phone_sale.mdb; Connection conn DriverManager.getConnection(url);逻辑说明UCanAccess把Access文件当普通数据库文件直接打开URL的格式是jdbc:ucanaccess://加绝对路径。参数说明路径里不能有中文和空格否则连接容易失败如果.mdb设置了密码还需要额外传递连接属性。两种方式二选一即可我个人的习惯是直接UCanAccess省掉ODBC配置这一层不确定因素。项目里如果用的是老式ODBC桥代码里搜JdbcOdbcDriver就能确认如果是UCanAccess搜ucanaccess就能定位连接串写在哪里。3.3 目录结构JSP页面、Java类和数据库文件分别放在哪里拿到压缩包解压后整个Web应用的目录结构一般是这样的webapps/phone_sale/ ├── index.jsp ├── login.jsp ├── register.jsp ├── phone_list.jsp ├── phone_detail.jsp ├── order_add.jsp ├── my_order.jsp └── admin/ │ ├── phone_manage.jsp │ ├── order_manage.jsp │ └── user_manage.jsp ├── WEB-INF/ │ ├── web.xml │ └── classes/ │ ├── com/phone/sale/bean/ │ │ ├── UserBean.java │ │ ├── PhoneBean.java │ │ └── OrderBean.java │ ├── com/phone/sale/dao/ │ │ ├── UserDao.java │ │ ├── PhoneDao.java │ │ └── OrderDao.java │ └── com/phone/sale/util/ │ └── DBUtil.java └── db/ └── phone_sale.mdb几个关键点说一下。第一JSP页面直接放在webapps根目录Tomcat访问的时候不需要额外配置路由文件名就是URL路径。第二Java类编译后的.class文件必须放在WEB-INF/classes目录下Tomcat的类加载器才会找到它们放错位置会出现类找不到的诡异报错。第三.mdb数据库文件推荐放在项目根目录或外部目录不要塞进WEB-INF里因为Tomcat对WEB-INF有访问保护某些情况下文件会被锁定导致数据库只读。包结构上bean包放数据实体对应数据库表结构dao包放JDBC访问代码负责所有增删改查util包里的DBUtil是所有DAO公用的连接工具类。这种划分的好处是页面JSP只调DAO不直接写JDBC换数据库、改连接串只需要动DBUtil一个地方。项目里如果看到每个JSP页面都自己写Class.forName和DriverManager二次开发时的第一优先级就是把连接逻辑抽出来。4. 核心模块代码拆解一个手机从入库到卖出的完整链路4.1 登录校验与Session会话用户状态的建立与销毁登录功能是所有业务的前置门槛。用户把用户名和密码通过表单POST到后台JSP页面后台从请求参数里取值拿这两个值和t_user表做比对匹配成功就把用户信息存进Session匹配失败就弹提示并留在原页。下面这段是doLogin.jsp的核心逻辑% page importjava.sql.* % % page importcom.phone.sale.util.DBUtil % % request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DBUtil.getConnection(); String sql SELECT userId, username, realName FROM t_user WHERE username? AND password?; ps conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); rs ps.executeQuery(); if (rs.next()) { session.setAttribute(userId, rs.getInt(userId)); session.setAttribute(username, rs.getString(username)); response.sendRedirect(phone_list.jsp); } else { out.println(scriptalert(用户名或密码错误);history.back();/script); } } finally { DBUtil.close(rs, ps, conn); } %逻辑说明先设置请求编码解决中文问题再从request里取username和password参数用PreparedStatement的?占位符拼接SQL查到了就写Session并重定向到手机列表页查不到就输出一段JavaScript提示错误。finally里关掉rs、ps、conn三个资源顺序一定是先ResultSet再Statement最后Connection这个习惯能避免连接泄漏。参数说明SQL里的两个?从左到右对应setString的两个参数顺序不要颠倒session.setAttribute的第一个参数是key第二个是value页面里用session.getAttribute(username)取回来。这里有个新手经常踩的坑密码存的是明文数据库里能直接看到。合格的改造做法是对密码做MD5加盐哈希再落库校验的时候拿同样的算法对输入值做哈希再比对这是所有Web项目的基线要求。源码如果已经是明文存储二次开发时优先处理。4.2 手机库存管理列表查询与新增入库手机列表页是前台的核心展示页面它的本质是把t_phone表的数据用ResultSet一行一行遍历出来拼成HTML表格。下面这段从phone_list.jsp里截取的代码展示了这种经典写法% Connection conn DBUtil.getConnection(); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery( SELECT phoneId, brand, model, price, stock FROM t_phone ORDER BY phoneId DESC); while (rs.next()) { % tr td% rs.getInt(phoneId) %/td td% rs.getString(brand) %/td td% rs.getString(model) %/td td% rs.getBigDecimal(price) %/td td% rs.getInt(stock) %/td td a hreforder_add.jsp?phoneId% rs.getInt(phoneId) %购买/a /td /tr % } rs.close(); stmt.close(); conn.close(); %逻辑说明Statement直接执行一条固定的SELECT语句ResultSet通过next()逐行移动游标每移动一次就输出一个 行。这种写法在JSP里最基础但要注意查询结束后必须手动关闭资源否则Tomcat的连接池会被占满。参数说明getString(brand)里的brand是表的字段名不是页面元素名URL拼接时把phoneId传给了order_add.jsp作为下单接口的参数。后台新增手机的逻辑在admin目录下的新增页面里核心是一条INSERT语句% request.setCharacterEncoding(UTF-8); String brand request.getParameter(brand); String model request.getParameter(model); double price Double.parseDouble(request.getParameter(price)); int stock Integer.parseInt(request.getParameter(stock)); String description request.getParameter(description); Connection conn DBUtil.getConnection(); String sql INSERT INTO t_phone(brand, model, price, stock, description) VALUES(?, ?, ?, ?, ?); PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, brand); ps.setString(2, model); ps.setDouble(3, price); ps.setInt(4, stock); ps.setString(5, description); ps.executeUpdate(); ps.close(); conn.close(); response.sendRedirect(admin/phone_manage.jsp); %逻辑说明从表单获取参数字符串类型直接用数值类型需要手动转换。setDouble对应Access里的货币字段setInt对应数字字段类型不匹配会在执行时报错。参数说明executeUpdate的执行结果是受影响的行数这里没接收返回值习惯上可以写成int rows ps.executeUpdate()来判断是否成功。这段代码暴露了一个问题如果表单里填的不是数字Double.parseDouble会直接抛NumberFormatException页面报黄页。标准做法是前端用JavaScript先校验后端再try-catch兜底两层防护。拿到源码后可以检查一下管理表单有没有这层校验没有的话改起来很简单。4.3 下单逻辑库存扣减与订单写入的事务问题下单是整条链路里最值得细看的模块它同时操作t_order和t_phone两张表还涉及库存校验。这里给一个简化版但结构完整的下单核心逻辑展示了先校验、再写单、后扣库存的顺序% int phoneId Integer.parseInt(request.getParameter(phoneId)); int userId (Integer) session.getAttribute(userId); int buyNum 1; // 默认购买1台 Connection conn DBUtil.getConnection(); conn.setAutoCommit(false); // 第一步查库存和价格 PreparedStatement psCheck conn.prepareStatement( SELECT stock, price FROM t_phone WHERE phoneId?); psCheck.setInt(1, phoneId); ResultSet rs psCheck.executeQuery(); if (!rs.next() || rs.getInt(stock) buyNum) { conn.rollback(); out.print(scriptalert(库存不足);history.back();/script); return; } double totalPrice rs.getDouble(price) * buyNum; rs.close(); psCheck.close(); // 第二步插入订单主表 PreparedStatement psOrder conn.prepareStatement( INSERT INTO t_order(userId, totalPrice, createTime, status) VALUES(?, ?, Now(), 待发货)); psOrder.setInt(1, userId); psOrder.setDouble(2, totalPrice); psOrder.executeUpdate(); psOrder.close(); // 第三步扣减库存 PreparedStatement psStock conn.prepareStatement( UPDATE t_phone SET stockstock-? WHERE phoneId? AND stock?); psStock.setInt(1, buyNum); psStock.setInt(2, phoneId); psStock.setInt(3, buyNum); int affected psStock.executeUpdate(); if (affected 0) { conn.rollback(); out.print(scriptalert(下单失败请重试);history.back();/script); return; } conn.commit(); %逻辑说明整个下单过程包在一个事务里setAutoCommit(false)之后的所有操作要么全成功要么全回滚。第一步先查库存和单价库存不够直接rollback并返回第二步插入订单主表createTime用数据库的Now()函数生成第三步用UPDATE语句扣库存WHERE条件里带上stock?保证扣减不会把库存扣成负数。如果第三步影响行数为0说明库存已经被别的请求扣光了事务回滚。参数说明conn.rollback()回滚本次事务里所有未提交的修改conn.commit()把修改落库两步顺序不能反。注意如果你从MySQL迁移过Access的SQLAPI里习惯写NOW()Access只认Now()函数名大小写差异会导致SQL执行失败。看到报错未定义的函数先检查数据库函数名。这段代码有个并发细节先SELECT查库存再UPDATE扣库存中间存在时间窗口两个用户同时下单时理论上都可能通过库存校验。真正的解决方案是用原子UPDATE直接扣减或者给t_phone表加锁。教学项目里不需要过度设计但理解查和扣不是一回事这件事比代码本身值钱。事务的rollback和commit成对出现、缺一不可这也是面试里常问的事务边界问题。4.4 请求转发与重定向为什么下单成功要用sendRedirectJSP里跳转页面有两种方式看起来差不多行为差异却很大。一种叫转发代码是request.getRequestDispatcher(my_order.jsp).forward(request, response)浏览器地址栏不变整个过程发生在服务器内部相当于一次请求内部换了个页面继续处理。另一种叫重定向代码是response.sendRedirect(my_order.jsp)服务器返回一个302响应浏览器重新发起一次新的GET请求地址栏会变成目标地址。下单这个场景必须用重定向原因是刷新问题。假设下单成功后用转发跳到订单列表页用户此时按F5浏览器会把上一次的请求重新执行一次插入订单的SQL又跑了一遍订单表里多出一条一模一样的记录这就是经典的表单重复提交。改成sendRedirect之后浏览器刷新的是my_order.jsp这个GET请求只是重新查一遍订单表不会触发插入逻辑。这个模式叫PRGPost-Redirect-Get。// 错误写法转发会让刷新页面重复提交订单 request.getRequestDispatcher(my_order.jsp).forward(request, response); // 正确写法重定向让刷新变成只读查询 response.sendRedirect(my_order.jsp);逻辑说明两行代码实现同样的跳到订单列表页效果但请求语义完全不同。转发是一次请求内的页面切换重定向是两次请求。参数说明sendRedirect的参数是相对当前应用的URL路径不要写成绝对路径带域名部署环境换了会失效。这个坑在作业项目里出现频率极高检查方式是搜代码里所有的forward调用凡是下单、删除、修改这类写操作后面跟了forward的基本都有重复提交隐患。资深的检查习惯是写操作跳转一律用sendRedirect读操作转发没问题。5. 避坑与常见问题JSPAccess现场五个翻车实录5.1 从jdbc:odbc桥到UCanAccessJDK版本带来的第一击现象代码里的Class.forName(sun.jdbc.odbc.JdbcOdbcDriver)在有的电脑上正常有的电脑上直接报ClassNotFoundException登录页白屏。原因新版JDK把JDBC-ODBC桥从标准库中移除了这是JDK官方多年前的既定决策。跑不动的机器都装了较新的JDK而老教程都是在旧版本环境下写的代码本身没变运行环境变了。解决改用UCanAccess驱动连接Access数据库Class.forName换成net.ucanaccess.jdbc.UcanaccessDriverURL从jdbc:odbc:phoneDb换成jdbc:ucanaccess://物理路径.mdb。改完连接串之后不需要配置ODBC数据源少一层配置就少一个坑。如果项目代码里有多处重复写连接逻辑顺手抽到DBUtil里统一管理后面再换驱动只改一个文件。5.2 Access文件被占用数据库打不开或变只读现象第一次运行正常第二次执行增删改操作报错数据库已被锁定或者页面提示数据库或对象为只读重启Tomcat才好一阵跑一会儿又出现。原因某个连接没有正确关闭文件句柄一直被Tomcat进程占用。Access和MySQL这类数据库服务器不一样它直接操作物理文件文件一旦被进程锁住其他连接就无法写入。还有一种情况是.mdb文件放在桌面或网盘同步目录文件被外部工具锁定。解决所有JDBC操作必须在finally里关闭ResultSet、Statement、Connection顺序相反。自检方法搜代码里所有new Connection的地方看close是不是写在finally里如果写在try块末尾当中间代码抛异常时close根本执行不到。数据库文件挪到项目根目录或专门的数据目录不要放桌面、网盘这类会被外部程序占用的位置。5.3 页面中文乱码三处编码不一致同时翻车现象手机名称在页面上显示成鎵嬫満或者一堆问号从管理后台新增的手机信息存进Access后列表页读出来还是乱码。更诡异的是有些页面正常有些页面乱码没有规律。原因编码问题有三个环节任何一个不一致都会乱码。第一个是JSP文件本身保存的编码第二个是HTTP请求参数传输编码第三个是数据库读写编码。很多教程只设置一处页面正常了参数又乱参数正常了数据库又乱。解决统一UTF-8三步都做。JSP页面头部加pageEncodingUTF-8和contentTypetext/html; charsetUTF-8所有接收参数的JSP第一行写request.setCharacterEncoding(UTF-8)连接串里显式指定编码属性。Access这边老式ODBC桥对中文支持差如果已经切到UCanAccess编码问题会少很多。排查思路是用浏览器开发者工具看响应头里的charset再逐层排查。5.4 数据库路径的玄学换个位置整个系统找不到库现象项目在同事电脑上跑得好好的拷到自己电脑上登录页还能打开一登录就报找不到数据库或者提示路径错误。改一下路径又好了隔几天换台机器又复现。原因连接串里写的是绝对路径比如jdbc:ucanaccess://D:/workspace/phone_sale/db/phone_sale.mdb。这个路径在原来那台机器上存在换台电脑D盘目录结构不一样整个连接就断了。所谓路径玄学本质是硬编码路径导致的必然结果。解决把数据库路径抽到DBUtil里的一个常量部署时只改一处。更稳的做法是写一个配置文件项目启动时从classpath读取路径不存在时抛异常并给出明确提示。不要想着用相对路径一劳永逸Tomcat启动时的工作目录在不同环境不一致相对路径本身就是一个不稳定因素。验收清单里加一条换机器部署只改一个配置项。5.5 刷新页面导致订单重复提交现象用户下单成功后手一滑按了F5订单页面刷新数据库里出现两条完全相同的订单。用户很困惑管理员更困惑最后把锅扣在系统不稳定上。原因下单逻辑所在的JSP用的是请求转发或者直接在输出页面中执行了插入SQL。用户刷新时浏览器重复执行了上一次的请求插入订单和扣减库存的代码又跑了一遍。尤其在下单这种写操作页面刷新的破坏力是双倍的——多一条订单多扣一次库存。解决所有写操作完成后一律使用response.sendRedirect跳转到只读页面把提交请求和展示结果拆成两次请求。这才是PRG模式Post后重定向到Get页面。更严格的做法是引入一次性令牌页面生成订单时带一个随机token存Session提交时校验消费过的token拒绝重复处理。检查现有代码时搜下单页面里有没有sendRedirect没有就优先补上。6. 二次开发给手机销售系统加一个销售统计报表模块改造目标是在后台加一个销售统计页面按手机型号汇总销售数量和销售金额这个功能在需求说明书里通常没写但答辩时老师问的概率很高。第一步在后台菜单栏加一个入口链接指向admin/sale_report.jsp。第二步新建这个JSP页面核心是一段GROUP BY聚合SQL% Connection conn DBUtil.getConnection(); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery( SELECT p.brand, p.model, SUM(d.num) AS saleCount, SUM(d.num * d.price) AS saleAmount FROM t_order_detail d JOIN t_phone p ON d.phoneId p.phoneId GROUP BY p.brand, p.model ORDER BY saleAmount DESC); while (rs.next()) { % tr td% rs.getString(brand) %/td td% rs.getString(model) %/td td% rs.getInt(saleCount) %/td td% rs.getBigDecimal(saleAmount) %/td /tr % } %逻辑说明t_order_detail表里记录了订单中每款手机的购买数量和成交价格JOIN t_phone拿品牌和型号GROUP BY按型号分组SUM汇总销售数量和销售金额。参数说明d.num是购买数量d.price是下单时刻的成交价这两列都在订单明细表里不需要动态查手机表。页面写好之后验证方法很直接在手机表里准备三个型号的数据各下单两笔不同数量然后打开统计页核对汇总结果是否和订单明细求和一致。再把库存数量减去订单明细的购买总量看是否和t_phone表当前库存一致这个对账动作能顺手查出一堆问题——重复提交、库存扣错、明细缺失全都会暴露在对不齐的数字里。改造时注意一个数据一致性问题如果统计用的SUM(d.num * d.price)里的价格直接取手机表的当前售价历史上改过价的订单统计就会不准。正确的做法是下单时把当时的价格冗余到明细表里统计永远基于明细表。这也是我拿到项目管理端时第一眼会确认的事情。我从那以后跑这种老项目第一件事就是先造几笔订单做对账而不是急着改代码。一条链路下来登录、下单、刷新、对库存、对订单五个动作全跑通了才敢动二次开发。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

函数调用关系图生成方法:静态分析、动态追踪与IDE工具全解析

函数调用关系图生成方法:静态分析、动态追踪与IDE工具全解析

接手一个没人维护的老模块时,我的习惯不是直接从第一行开始读,而是先把函数调用关系图画出来,再决定从哪里切入。道理很简单:代码的阅读顺序不应该是代码的书写顺序,而应该是调用链的传播顺序。过去几年我在 C/C、Pyth…

2026/10/19 5:15:15 阅读更多 →
降ai率指令到底有没有用?让AI自己去AI味实测失败,换这个方法朱雀AIGC检测直接0%!

降ai率指令到底有没有用?让AI自己去AI味实测失败,换这个方法朱雀AIGC检测直接0%!

网上流传的降AI方法很多:换同义词、调句式、翻译成英文再翻回来、让AI自己去AI味、往论文里加自己的调研内容。这些方法到底能把AI率降到多少?我们用同一段AI生成的论文文本,把6种方法各做了一遍,全部送腾讯朱雀AI检测&#xff0c…

2026/10/19 5:15:15 阅读更多 →
Python数据分析完整流程:从数据清洗到特征工程实战指南

Python数据分析完整流程:从数据清洗到特征工程实战指南

真正上手跑过一次完整的python数据分析流程之后,你会发现大部分教程讲的都是零散的技巧:怎么画图、怎么清洗某个脏列、怎么调一个模型。但现实里,老板要的是从原始数据到最终结论的一整套链路,也就是数据采集、清洗、加工、分析、…

2026/10/19 5:15:15 阅读更多 →

最新新闻

STM32通过IIC驱动MPU6050六轴传感器:从时序到姿态解算实战指南

STM32通过IIC驱动MPU6050六轴传感器:从时序到姿态解算实战指南

做嵌入式这几年,被问到最多的传感器大概就是 MPU6050。这颗六轴芯片配合 STM32 的 IIC 接口,几乎是所有姿态相关项目的标准开局:两轮自平衡、云台增稳、手势遥控、跌倒检测……不管你是刚入门的新手,还是在做毕业设计的同学&#…

2026/10/19 5:55:34 阅读更多 →
Oracle数据泵单实例迁移全流程详解:从expdp到impdp

Oracle数据泵单实例迁移全流程详解:从expdp到impdp

搞Oracle数据库搬迁,绕不开的一个词就是数据泵(Data Pump)。单实例环境来回同步数据,expdp导出、impdp导入这套组合我这些年用了不下几十次,说它是最皮实的逻辑迁移方案不为过。别看这个标题“单实例环境使用数据泵&am…

2026/10/19 5:55:34 阅读更多 →
物联网设备WiFi配网实战:ESP32/ESP8266 SoftAP与SmartConfig详解

物联网设备WiFi配网实战:ESP32/ESP8266 SoftAP与SmartConfig详解

上个月帮朋友调一块 ESP32 开发板,设备本身跑得好好的,最后却卡在“怎么让设备连上他家的 WiFi”这一步。我说你直接把 SSID 和密码写在代码里不就行了?他说你自己调试当然没问题,可这个设备是要交给一个完全不会用串口、不会看代…

2026/10/19 5:55:34 阅读更多 →
artcraft:基于WebGL的轻量级创意合成工具实战

artcraft:基于WebGL的轻量级创意合成工具实战

1. 项目缘起与整体设计思路1.1 为什么会有 artcraft 这个想法做创意工具这些年,我最大的感受就是:灵感来得快,工具跟不上。脑子里冒出一个画面,想快速拼贴、调色、加纹理,结果打开专业软件要等半分钟加载,图…

2026/10/19 5:55:34 阅读更多 →
VS2017 64位下OSGQT整合实战:从编译到渲染的避坑指南

VS2017 64位下OSGQT整合实战:从编译到渲染的避坑指南

简介:这是一份面向Qt与OpenSceneGraph(OSG)三维渲染开发者的整合示例工程,基于Github上口碑较好的OSGtree项目整理而来,已在VS2017搭配64位OSG环境下完成调试,可直接运行,适合正在学习OSG与Qt混…

2026/10/19 5:55:34 阅读更多 →
CSerialPort串口类2017修正版:稳定嵌入工业控制的Win32封装

CSerialPort串口类2017修正版:稳定嵌入工业控制的Win32封装

简介:这是一份面向C开发者,特别是Windows平台串口通信初学者与中级工程师的轻量级串口类封装资源。CSerialPort类经2017年最新修正,显著降低MFC依赖性,新增_AFX宏适配MFC环境,并提供纯Win32验证程序,使非MF…

2026/10/19 5:54:34 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →