简介这是一套面向计算机专业学生与JavaWeb初学者、课程设计或毕业设计开发者的超市收银系统源码基于JavaWeb技术栈实现可帮助读者快速搭建一套完整的收银管理解决方案解决商品、订单、用户与统计等业务模块的开发需求。压缩包共147个文件约1.61MB以60个java源文件、29个jsp页面、22个map映射文件为主辅以16个css与7个js前端资源、3个xml配置、2个properties配置及1个sql脚本另含mvnw构建脚本整体结构清晰便于按模块阅读与二次开发。系统涵盖商品增删改查与库存预警、订单生成查询与多支付方式统计、多角色用户登录与权限管理以及销售数据统计分析等功能前端引入Bootstrap样式适合作为课程设计参考或JavaWeb综合练习项目。目前已有97人学习可帮助读者理解分层架构与前后端交互流程快速掌握收银系统的核心实现思路。1. 从一份 JavaWeb 超市收银系统源码说起它到底能跑出什么很多人第一次拿到「基于 JavaWeb 的超市收银系统」这类源码包第一反应是解压、找 main、点运行然后被一堆web.xml、WEB-INF、lib目录劝退。它不是一个双击就能跑的桌面程序而是一套典型的 JavaWeb 三层结构应用JSP 负责页面渲染Servlet 接请求JDBC 或 MyBatis 落库MySQL 存商品、订单、会员和库存。收银场景本身不复杂但它是练手 JavaWeb 全链路最合适的题材之一——扫码加购、结算、库存扣减、小票打印、日结报表每一环都能对应到真实业务。这份源码适合三类人正在做课程设计、需要一套能讲清楚分层架构的参考实现刚学完 Servlet/JSP、想找一个完整案例把知识点串起来以及想用 SpringBoot 重构老项目、需要一份可对照的原始版本。下面按「环境怎么搭 → 核心链路怎么跑 → 坑在哪 → 怎么验证」的顺序拆开讲能照着复现。2. 环境搭建与项目导入IDEA 里跑通 JavaWeb 的最小配置2.1 JDK、Tomcat、MySQL 的版本对齐JavaWeb 老项目最怕版本错配。常见做法是 JDK 8 Tomcat 8.5/9.0 MySQL 5.7/8.0这套组合兼容性最好。JDK 17 配 Tomcat 9 也能跑但javax.servlet包名在 Tomcat 10 之后变成了jakarta.servlet源码里所有import javax.servlet.*会集体报红这是新手最容易翻车的地方。组件推荐版本说明JDK8 或 11源码若用javax.*别上 17Tomcat8.5 或 9.010 以上包名变更需改代码MySQL5.7 或 8.08.0 需注意驱动类名和时区IDEIDEA 2021社区版需手动配 Tomcat构建Maven 或手动导 jar看源码有没有pom.xml先确认源码结构有pom.xml就是 Maven 项目直接File → Open选pom.xml没有就是传统 Web 项目需要File → Project Structure → Modules手动加 Web Facet并把lib目录下的 jar 全部 Add as Library。2.2 数据库导入与连接配置找到源码里的.sql文件用命令行或 Navicat 导入。导入前先建库字符集用utf8mb4否则商品名里的生僻字或 emoji 会变问号。# 建库并导入注意字符集 mysql -u root -p -e CREATE DATABASE supermarket DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; mysql -u root -p supermarket supermarket.sql # 验证表是否导入成功 mysql -u root -p -e USE supermarket; SHOW TABLES;导入后检查db.properties或jdbc.properties把用户名密码改成你本地的。MySQL 8.0 的驱动类名是com.mysql.cj.jdbc.DriverURL 要带serverTimezoneAsia/Shanghai否则启动就报时区错误。# db.properties 典型配置 jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/supermarket?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.password你的密码参数说明useUnicode和characterEncoding保证中文不乱码serverTimezone解决 8.0 时区异常useSSLfalse避免本地连接时的 SSL 警告。改完这些数据库层基本就通了。2.3 IDEA 配置 Tomcat 并部署Run → Edit Configurations → → Tomcat Server → Local在 Deployment 标签页点选Artifact通常选xxx:war exploded这样改 JSP 不用重启。Application context 设成/或/supermarket访问路径就是http://localhost:8080/或/supermarket。启动前确认web.xml里的welcome-file指向哪个页面常见是login.jsp或index.jsp。如果启动报ClassNotFoundException八成是 jar 没进WEB-INF/lib或 Maven 依赖没刷新mvn clean package一下再部署。3. 收银核心链路的代码拆解从扫码到落库3.1 商品查询与购物车Servlet 接参到 JSP 渲染收银的第一步是扫码或输入条码查商品。典型实现是前端 AJAX 请求GoodsServlet后端查库返回 JSON前端把商品塞进购物车表格。看这段 Servlet 的doGet// GoodsServlet.java 按条码查商品 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String barcode request.getParameter(barcode); response.setContentType(application/json;charsetutf-8); PrintWriter out response.getWriter(); GoodsDao dao new GoodsDao(); Goods goods dao.findByBarcode(barcode); // 按条码精确查询 if (goods null) { out.print({\code\:404,\msg\:\商品不存在\}); } else { // 手动拼 JSON老项目常见做法也可用 Gson/Jackson out.print({\code\:200,\name\:\ goods.getName() \,\price\: goods.getPrice() ,\stock\: goods.getStock() }); } out.flush(); }逻辑说明request.getParameter拿条码GoodsDao走 JDBC 查goods表返回结果手动拼 JSON。参数上要注意response.setContentType必须带charsetutf-8否则前端拿到中文商品名是乱码。GoodsDao.findByBarcode里用PreparedStatement防注入别用字符串拼接 SQL。购物车一般存在HttpSession里结构是MapString, CartItemkey 用条码value 含商品信息和数量。加购时判断库存数量超过库存要拦下来这是收银系统的基本校验。3.2 结算与库存扣减事务边界在哪里结算是整个系统最不能出错的地方。用户点「结算」后端要做三件事生成订单主表记录、生成订单明细、扣减商品库存。这三步必须在一个事务里否则会出现订单生成了但库存没扣或者库存扣了订单没落库。// OrderService.java 结算核心手动管理事务 public boolean checkout(ListCartItem items, int memberId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 OrderDao orderDao new OrderDao(conn); GoodsDao goodsDao new GoodsDao(conn); int orderId orderDao.insertOrder(memberId, totalAmount(items)); for (CartItem item : items) { orderDao.insertOrderItem(orderId, item); // 扣库存带库存充足校验 int affected goodsDao.reduceStock(item.getBarcode(), item.getQty()); if (affected 0) { conn.rollback(); // 库存不足整体回滚 return false; } } conn.commit(); return true; } catch (Exception e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { DBUtil.close(conn); } }逻辑说明setAutoCommit(false)开启事务所有 DAO 共用同一个Connection这是关键——如果每个 DAO 自己拿连接事务就失效了。reduceStock的 SQL 要写成UPDATE goods SET stock stock - ? WHERE barcode ? AND stock ?用affected 0判断库存不足比先查再扣更安全能防并发超卖。参数说明memberId为 0 表示非会员totalAmount计算时注意用BigDecimal别用double金额精度问题在收银系统里是硬伤。3.3 小票与日结报表JSP 输出与 SQL 聚合小票打印常见两种做法JSP 页面套打印样式或者生成 PDF。老项目多用前者print.jsp里用window.print()触发浏览器打印CSS 用media print控制只打印小票区域。日结报表靠 SQL 聚合按日期分组统计销售额和订单数-- 日结按天统计销售额、订单数、客单价 SELECT DATE(create_time) AS sale_date, COUNT(*) AS order_count, SUM(total_amount) AS total_sales, ROUND(SUM(total_amount) / COUNT(*), 2) AS avg_order FROM orders WHERE create_time ? AND create_time ? GROUP BY DATE(create_time) ORDER BY sale_date DESC;参数说明两个?分别是起始和结束时间用和而不是BETWEEN避免边界重复统计。ROUND保留两位小数avg_order就是客单价。这个查询在订单量上万后要加create_time索引否则日结会越来越慢。4. 避坑与排查JavaWeb 老项目最常见的 5 个翻车点4.1 中文乱码POST 和 GET 要分开治现象商品名、会员姓名存进库变成???或者页面显示乱码。原因POST 请求体默认 ISO-8859-1 解码GET 请求参数在 Tomcat 8 之前也默认 ISO-8859-1。解决加一个CharacterEncodingFilter在web.xml里配/*拦截doFilter里调request.setCharacterEncoding(UTF-8)和response.setCharacterEncoding(UTF-8)。GET 乱码还要在 Tomcat 的server.xml里给 Connector 加URIEncodingUTF-8。数据库连接串的characterEncodingutf8也别漏。4.2 空指针DAO 返回 null 没判现象查一个不存在的条码页面直接 500日志里NullPointerException。原因GoodsDao.findByBarcode查不到返回 null上层直接goods.getName()。解决所有 DAO 查询结果在使用前判空Servlet 里返回友好提示而不是让异常冒到页面。更稳的做法是 DAO 返回OptionalGoods强制调用方处理空值。4.3 事务失效DAO 各自拿连接现象结算时订单插入了但库存没扣或者扣了库存订单没生成。原因OrderDao和GoodsDao各自DBUtil.getConnection()两个连接不在同一事务里rollback只回滚了其中一个。解决把Connection作为参数传进 DAO 构造函数Service 层统一管理连接和事务边界这是 3.2 节代码里new OrderDao(conn)的原因。4.4 驱动类找不到MySQL 8.0 的类名变了现象启动报ClassNotFoundException: com.mysql.jdbc.Driver。原因MySQL 8.0 的驱动类名是com.mysql.cj.jdbc.Driver老配置没改。解决改db.properties里的jdbc.driver同时确认mysql-connector-java的版本是 8.0.x。如果还报错检查 jar 有没有真的进WEB-INF/libIDEA 里 Maven 依赖和 Web 部署的 lib 是两回事。4.5 静态资源 404路径没带 context现象JSP 能打开但 CSS、JS、图片全 404。原因页面里写的是绝对路径/css/style.css但应用部署在/supermarket下实际路径应该是/supermarket/css/style.css。解决JSP 里用${pageContext.request.contextPath}/css/style.css或者用c:url标签。别硬编码 context 名换部署路径就全废。5. 从能跑到好用用 SpringBoot 重构与接口验证的实操技巧老 JavaWeb 项目跑通只是起点真正让它「好用」得做两件事一是把 Servlet 那套换成 SpringBoot二是给核心接口加自动化验证。我一般会先挑结算接口做重构试点因为它逻辑最重、最值得保护。重构时OrderService的checkout方法直接搬进 Spring 的Service事务用Transactional(rollbackFor Exception.class)替代手动commit/rollbackDAO 换成 MyBatis 的 Mapper 或 JPA Repository。注意Transactional默认只对RuntimeException回滚收银场景里SQLException是受检异常必须显式写rollbackFor这个坑我踩过不止一次。验证接口用 Postman 或写 JUnit 都行关键是覆盖三个边界库存刚好等于购买量、库存差一个、并发两个请求抢同一件商品。并发那个用CountDownLatch模拟// 并发扣库存测试验证不超卖 Test public void testConcurrentCheckout() throws Exception { int threads 10; CountDownLatch latch new CountDownLatch(threads); ExecutorService pool Executors.newFixedThreadPool(threads); AtomicInteger success new AtomicInteger(); for (int i 0; i threads; i) { pool.submit(() - { try { latch.countDown(); latch.await(); // 所有线程同时起跑 if (orderService.checkout(buildItems(), 0)) { success.incrementAndGet(); } } catch (Exception e) { e.printStackTrace(); } }); } pool.shutdown(); pool.awaitTermination(10, TimeUnit.SECONDS); // 库存 5 件10 个线程抢成功数必须 5 assertTrue(success.get() 5); }逻辑说明CountDownLatch让 10 个线程同时发起结算success统计成功数。如果reduceStock的 SQL 没写AND stock ?这里会超卖success可能大于 5。参数上buildItems()构造购买数量库存预置成 5断言成功数不超过库存。这个测试跑通结算链路才算真正可靠。重构完别急着删老代码两套并行跑一段时间用同样的输入对比输出确认一致再切换。收银系统不怕慢怕的是账对不上。我自己的习惯是每次改完结算逻辑先跑一遍并发测试再看一眼日结报表的数字有没有跳变这两个动作比任何代码审查都管用。希望帮到你。本文还有配套的精品资源点击获取