资讯详情

Java超市积分管理系统:Servlet+JSP+JDBC实战解析

📅 2026/9/28 11:54:48 | 华诺云谱 👁 阅读
Java超市积分管理系统:Servlet+JSP+JDBC实战解析
简介一份基于 Java 的超市积分管理系统完整项目资料包覆盖项目报告、答辩 PPT、源代码与数据库脚本适合 Java Web 初学、课程设计及毕业设计对照参考。压缩包共 5 个文件、约 18.21MB含 2 个 zip、1 个 sql、1 个 doc、1 个 txt源代码与项目截图分别放于 zipsql 为数据库初始化脚本doc 为项目报告txt 为使用说明整体目录清楚可按需查阅对应模块。系统采用 MVC 分层思想由 Servlet 处理请求、JSP 渲染页面、JDBC 连接数据库、DAO 封装数据访问会员、商品、积分等核心业务围绕数据库表设计与业务逻辑展开能够帮助读者理解 Java Web 常用技术栈从请求到响应的完整链路。项目报告对需求分析、系统设计、实现过程和测试结果有较完整梳理答辩 PPT 与项目截图可用于汇报演示和界面效果确认。目前已有 360 人学习下载源代码与数据库脚本可直接用于二次开发、功能扩展或作为同类课设项目的落地参考。1. 一个 Java 超市积分管理系统为什么值得拆开看这套基于 Java 的超市积分管理系统压缩包里装着项目报告、答辩 PPT、完整源代码、数据库脚本和运行截图是一个典型的 Java Web 课程设计完整闭环。我拆这类资源有个习惯先看数据库脚本和核心业务代码再回过去读报告——因为报告可能写得很漂亮但代码和表结构才是项目的真实面貌。这套资源的表设计、积分增减流程、会员等级判断逻辑都做得比较完整适合正在做 Java 课程设计、毕业设计或者想系统梳理 Servlet JSP JDBC 这套老技术栈的人。带 Java 关键词的类似资源很多但多数是半成品或者代码和文档对不上。这个项目最大的价值在于数据库脚本、源代码、项目报告、答辩 PPT 四样东西齐全而且它们之间是能互相印证的。也就是说你可以照着报告里的设计思路去读代码也可以直接跑起来看效果再回过头改功能。对新手来说这是一条完整的「需求 → 设计 → 编码 → 测试 → 答辩」链路对熟手来说这是一个可以快速改造成会员管理、积分商城、客户关系管理系统的现成底座。2. 技术选型与系统架构先搞清楚这项目是怎么搭起来的2.1 为什么是 Servlet JSP JDBC而不是 Spring Boot现在的 Java 课程设计很多一上来就用 Spring Boot MyBatis Plus但这套项目用的是更传统的一套组合Servlet 处理 HTTP 请求JSP 生成动态页面JDBC 直接操作数据库配合 DAO 模式封装数据访问。这套技术栈在今天看确实有些年头但作为课程设计恰恰合适——因为每个环节都是透明的没有框架帮你屏蔽细节。用 Servlet JSP JDBC 的好处有三个第一请求从浏览器到数据库的完整链路清晰可见答辩时老师问「一个登录请求是怎么处理的」你能从头到尾讲清楚第二不依赖框架的自动配置数据库连接、事务管理、参数传递都是手写代码这本身就是很好的面试素材第三项目体积小部署简单一个 Tomcat 加一个 MySQL 就能跑起来。我一般建议如果你时间充裕可以在这套代码的基础上加一层 Spring 容器体验一下从原生 JDBC 到框架的演进过程但如果你是赶着交作业那就保持原样把精力花在把现有功能跑通、讲明白上。2.2 数据库表结构设计积分系统的核心是「流水账」而不是「余额」打开 jf.sql 这个脚本你会发现这个项目的表设计遵循了一个重要原则积分余额不直接存储而是通过积分流水表实时汇总计算。这是积分系统设计中一个非常关键的决定。为什么这么说如果你设计一张 member 表里面放一个 points 字段直接存当前积分余额那每次积分变动除了要 INSERT 一条流水记录还要 UPDATE 这个字段。一旦出现并发操作比如用户同时下单和退货两个请求同时读写这个字段就可能出现数据不一致。而通过流水表汇总每次只要往里追加记录查询时用 SUM 聚合就从机制上避免了这个坑。这套项目的表结构大致包含这几类表类型作用关键字段会员表存储会员基本信息id、会员卡号、姓名、手机号、注册日期商品表商品信息id、商品名称、单价、库存积分流水表每次积分变动的记录id、会员id、变动值、变动类型、时间消费记录表会员购买记录id、会员id、商品id、金额、时间提示看数据库脚本时重点看积分流水表是否有「变动类型」字段。如果只有变动值没有类型说明这项目没法区分「消费获得」「手动调整」「过期扣除」这三种积分变动后面写报表时会很吃力。2.3 JDBC 连接与 DAO 封装把数据库操作从业务代码里拆出去打开源代码包 jf.zip你会看到典型的包结构。项目的核心分层是这样servlet 包放控制器dao 包放数据访问对象entity 包放实体类util 包放工具类。这种分层虽然朴素但比把所有 JDBC 代码都塞进 Servlet 要规范得多。DAO 层的代码通常长这样我直接贴一段最常见的查询方法public Member findByCardNo(String cardNo) { Connection conn null; PreparedStatement ps null; ResultSet rs null; Member member null; try { conn DBUtil.getConnection(); String sql SELECT id, card_no, name, phone, points FROM member WHERE card_no ?; ps conn.prepareStatement(sql); ps.setString(1, cardNo); rs ps.executeQuery(); if (rs.next()) { member new Member(); member.setId(rs.getInt(id)); member.setCardNo(rs.getString(card_no)); member.setName(rs.getString(name)); member.setPhone(rs.getString(phone)); member.setPoints(rs.getInt(points)); } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(rs, ps, conn); } return member; }这段代码有几个刻意为之的细节第一所有查询都用的 PreparedStatement 而不是 Statement因为 PreparedStatement 有预编译机制能防 SQL 注入而且对同一 SQL 的多次执行性能更好第二资源释放放在 finally 块里而且是先关 ResultSet、再关 PreparedStatement、最后关 Connection 的顺序如果顺序反了可能会出现资源泄漏第三数据库连接和关闭操作抽到了 DBUtil 这个工具类里业务代码不需要关心驱动的加载和连接参数。参数说明cardNo是会员卡号查询时通过?占位符传参避免字符串拼接DBUtil.getConnection()从工具类获取连接实际开发中这里可以换成 C3P0 或 Druid 连接池查询结果通过if (rs.next())判断是否存在记录避免返回空对象导致 NPE2.4 MVC 流程串讲一个积分查询请求从浏览器到数据库的完整路径搞懂了这个项目的 MVC 流转你就掌握了整个系统的骨架。我以一个「会员积分查询」请求为例串一遍完整流程浏览器访问http://localhost:8080/jf/queryPoint?cardNo10001web.xml 中配置的 URL 映射把请求交给QueryPointServletServlet 调用memberDao.findByCardNo(10001)获取会员信息DAO 层通过 JDBC 查询 member 表注意这里查出来的 points 字段Servlet 把 Member 对象存到 request 作用域转发到queryResult.jspJSP 页面通过 EL 表达式或 JSTL 标签把数据显示到页面在这个流程里Servlet 扮演的是控制器的角色它不做 SQL 查询、不写 HTML只做两件事从请求里取参数、调 DAO、把结果塞进 request、转发到视图。JSP 做视图只负责展示数据不应该出现业务逻辑。注意如果你在 JSP 里看到大片%Java 脚本片段那属于早期写法虽然能跑但不推荐。答辩时老师问「什么是 MVC」你要说清楚模型、视图、控制器的职责边界如果能指出来「这个项目的 JSP 里只有 EL 表达式没有业务逻辑」那会加分不少。3. 核心功能拆解与实现细节积分增减、会员等级、消费折上折3.1 积分产生逻辑消费金额如何映射成积分积分系统的第一个核心问题是一笔消费该给会员加多少分这个项目里最常见的规则是「每消费 1 元积 1 分」但为了演示效果很多课程设计会做成「消费金额 / 10」或者其他比例。具体的换算规则可以在项目报告里找到通常写在需求分析章节。实现这个逻辑的代码一般长这样public int calculatePoints(BigDecimal amount) { // 每消费满10元积1分不足10元的部分不积分 return amount.intValue() / 10; }这里有个很容易翻车的细节如果你用amount.intValue() / 10那么 19.9 元的消费会被截断成 19 再除以 10结果是 1 分。但如果你用amount.intValue() * 10再去做其他运算就可能出现精度问题。积分计算最好用整数运算避免浮点数误差。我见过不少项目在积分换算上直接用 double 计算结果然后出现 0.999999 这种值最后四舍五入到 0 分用户白消费了。参数说明amount是 BigDecimal 类型比 double 更适合表示金额intValue()会截断小数部分所以 19.9 元只算 19 元如果需要四舍五入策略可以用setScale(0, RoundingMode.HALF_UP)但要注意规则要和报告里的描述一致3.2 积分扣减与事务边界退货时怎么保证不会把积分扣成负数积分扣减比增加要麻烦得多。因为积分可以买东西兑换礼品或抵扣现金也可以因为退货被扣回还可能因为过期被清零。这个项目里的扣减逻辑核心是解决一个问题扣积分和记流水这两个操作要么都成功要么都失败。下面这段代码是一个典型的积分扣减事务处理public boolean deductPoints(int memberId, int points) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交开启事务 // 第一步锁定会员行防止并发扣减 String lockSql SELECT points FROM member WHERE id ? FOR UPDATE; PreparedStatement psLock conn.prepareStatement(lockSql); psLock.setInt(1, memberId); ResultSet rs psLock.executeQuery(); int currentPoints 0; if (rs.next()) { currentPoints rs.getInt(points); } if (currentPoints points) { conn.rollback(); return false; // 积分不足 } // 第二步扣减积分 String updateSql UPDATE member SET points points - ? WHERE id ?; PreparedStatement psUpdate conn.prepareStatement(updateSql); psUpdate.setInt(1, points); psUpdate.setInt(2, memberId); psUpdate.executeUpdate(); // 第三步记录流水 String insertSql INSERT INTO points_log (member_id, change_value, change_type, create_time) VALUES (?, ?, deduct, NOW()); PreparedStatement psInsert conn.prepareStatement(insertSql); psInsert.setInt(1, memberId); psInsert.setInt(2, -points); psInsert.executeUpdate(); conn.commit(); // 提交事务 return true; } catch (SQLException e) { conn.rollback(); e.printStackTrace(); return false; } finally { DBUtil.closeConnection(conn); } }这段代码有三个关键点要说透。第一是SELECT ... FOR UPDATE这一步把会员行锁住了两个用户同时扣积分时后到的那个会等前面的事务提交后才能操作这就避免了超扣。第二是事务边界的控制Java 里的setAutoCommit(false)意味着接下来的所有 SQL 都在同一个事务里任何一个环节抛异常都会回滚不会出现「钱扣了但积分没扣」或者反过来。第三是积分的校验和扣减放在同一个事务里避免了「校验时够用、扣的时候不够用」的竞态窗口。参数说明points建议用正数传入扣减时在 SQL 里做减法流水里记负数FOR UPDATE是 InnoDB 的行级锁MyISAM 引擎不支持所以建表时默认引擎必须是 InnoDB事务的回滚要放在 catch 块里因为只有出现异常才需要回滚3.3 会员等级设计与积分过期让系统看起来像一个真实产品纯粹做增删改查的课程设计很容易被老师问「你的系统的创新点和使用价值在哪里」。这个项目在会员等级和积分过期上做了一些设计让系统的完整度明显高于平均水平。会员等级通常按累计积分划分比如0 到 1000 分是普通会员1001 到 5000 分是银卡会员5001 分以上是金卡会员。不同等级的会员在消费时可以享受不同的折扣率这需要在订单计算时动态判断。public String getMemberLevel(int totalPoints) { if (totalPoints 5000) { return GOLD; } else if (totalPoints 1000) { return SILVER; } else { return NORMAL; } }这个等级判断逻辑虽然简单但要注意一个问题如果用「当前剩余积分」来判断等级那用户把积分花掉之后就会降级这在真实场景里不合理。正确做法是用「累计获得积分」来判断等级也就是从流水表里查SUM(change_value)且只统计增加的部分。模拟积分过期一般有两种做法一种是用定时任务在每天凌晨扫描过期记录另一种是在查询积分余额时动态过滤已过期的流水。课程设计用第二种更稳妥因为不需要额外配置定时任务环境。具体的过期周期通常设为一年从流水产生时间算起。注意如果你要在答辩时讲积分过期一定要想清楚「过期积分的计算口径」。是按先进先出FIFO先扣最早获得的积分还是直接按过期时间批量清零两种口径算出来的余额可能不一样建议在报告里写清楚。3.4 前端页面交互JSP JavaScript 实现会员注册与积分查询这套项目的前端没有使用特别重的框架JSP 页面配合少量 JavaScript 完成表单验证和动态交互。比如在会员注册页面通常会有一个「检测卡号是否重复」的异步请求这部分用 AJAX 就能实现代码关键部分长这样function checkCardNo(cardNo) { var xhr new XMLHttpRequest(); xhr.open(GET, checkCardNo?cardNo encodeURIComponent(cardNo), true); xhr.onreadystatechange function() { if (xhr.readyState 4 xhr.status 200) { var result JSON.parse(xhr.responseText); if (result.exist) { document.getElementById(cardNoTip).innerHTML 该卡号已存在; } else { document.getElementById(cardNoTip).innerHTML 卡号可用; } } }; xhr.send(); }这段代码值得注意的细节是encodeURIComponent如果你直接拼接中文参数比如手机号或姓名浏览器默认编码可能是 GBK 或 UTF-8服务器端解析时会乱码。显式编码之后再传给后端能避免这类典型的编码问题。服务器端对应的是一个返回 JSON 的 Servlet它会调用 DAO 查询 member 表然后通过response.getWriter().write(jsonString)把结果返回给页面。参数说明readyState 4表示请求完全结束status 200表示 HTTP 响应正常JSON.parse把服务器返回的字符串转成 JavaScript 对象后端需要设置response.setContentType(application/json;charsetUTF-8)如果不用 AJAX也可以让表单提交后回到同一个页面时显示错误提示但用户体验会差一些4. 部署与运行从零开始把这套系统跑起来的完整流程4.1 环境准备JDK、Tomcat、MySQL 的版本搭配与配置拿到源代码后第一步不是急着导入 IDE而是先把环境准备好。这个项目基于 Java 技术栈运行环境需要 JDK 8、Tomcat 8 或 9、MySQL 5.7 以上版本。这三个组件的版本搭配很关键JDK 8 是兼容性最好的选择Tomcat 8.5 对应 Servlet 3.1 规范MySQL 5.7 的默认字符集是 utf8mb4能正常存中文。环境变量配置是新手最容易卡住的地方。在 Windows 上需要配置JAVA_HOME和CATALINA_HOME然后在 PATH 里加上%JAVA_HOME%\bin。配置完成后在命令行输入java -version如果能输出版本号和 Java HotSpot(TM) 字样说明 JDK 环境没问题。接着启动 MySQL 服务用 Navicat 或命令行工具导入 jf.sql 脚本mysql -u root -p jf.sql提示导入数据库前先确认 root 用户的密码和密码认证方式。MySQL 8.0 默认用 caching_sha2_password老版本的 JDBC 驱动可能连不上这时候要么换 mysql-connector-java 8.0 以上的驱动要么把用户改成 mysql_native_password。4.2 数据库连接配置找对 db.properties 或 DBUtil 里的连接参数这个项目把数据库连接信息写在工具类或配置文件中你需要修改的核心就三个参数URL、用户名、密码。以 DBUtil.java 为例private static final String URL jdbc:mysql://localhost:3306/jf?useSSLfalsecharacterEncodingutf8serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456;这三个配置有几个容易踩的坑。useSSLfalse是必须的否则 MySQL 5.7 或 8.0 的 SSL 连接会报警告甚至连接失败characterEncodingutf8确保数据库读写中文不乱码serverTimezoneAsia/Shanghai是 JDBC 8.0 驱动的要求不设置会报 CST 时区错误。如果你的 MySQL 密码包含特殊字符比如或#需要确认文件读取时没有做转义处理。参数说明jf是数据库名必须和 jf.sql 里创建的库名一致3306是 MySQL 默认端口如果你本机改过端口这里也要跟着改root/123456只是示例实际改成你自己的账号密码4.3 导入 IDE 与部署到 Tomcat用 Eclipse 还是 IDEA这个项目可以直接导入 Eclipse 或 IntelliJ IDEA。用 IDEA 的话选择 File - New - Project from Existing Sources然后选 Tomcat 作为应用服务器。导入后需要检查项目的 JDK 版本设置Project Structure 里的 SDK 要和编译级别一致否则可能报 invalid source release 错误。部署到 Tomcat 有两种方式直接把target/项目名拷贝到 Tomcat 的webapps目录或者在 IDEA 里配置 Tomcat 运行。我建议在 IDEA 里配 Tomcat因为这样可以断点调试。运行前要在 Edit Configurations 里选好本地 Tomcat 路径然后点运行浏览器会自动打开http://localhost:8080/jf。如果看到首页出现但样式错乱、图片不显示十有八九是静态资源路径用了绝对路径或者在 web.xml 里的欢迎页面配置不正确。正常情况下的访问路径要根据项目实际部署名来比如项目部署名是jf那访问地址就是上面那个如果部署名不对页面会 404。4.4 跑通核心流程注册会员、录入消费、查看积分变动系统能正常启动之后建议按下面的顺序跑一遍核心流程这一步同时是验证数据库连接是否OK、代码是否有隐藏 bug 的过程打开会员注册页面填写姓名、手机号、卡号完成注册在数据库里手动插入一条商品记录或者通过后台管理页面添加商品模拟一笔消费输入商品 ID 和会员卡号提交订单查看该会员的积分流水确认增加了对应积分再做一个积分扣减操作比如用积分兑换商品确认流水里出现一条负数记录整个流程走完你基本可以确定这套系统的主要功能都是通的。如果某一步报错先从控制台看异常栈——最常见的错误是ClassNotFoundExceptionJDBC 驱动没进 WEB-INF/lib、SQLSyntaxErrorException表名或字段名写错、NullPointerException从 request 取不到参数。把这三类错误优先排查掉项目基本就能跑顺了。5. 避坑与常见问题这五个坑我替你先踩了5.1 中文乱码问题前端 GBK 和后端 UTF-8 打架现象注册的会员姓名是中文刷新页面后显示成乱码或者提交到数据库后变成???。原因Tomcat 8 之前默认请求编码是 ISO-8859-1而项目代码里用的是 UTF-8。两个编码不一致中文就乱了。解决在 Servlet 里最前面加request.setCharacterEncoding(UTF-8)同时确认 JSP 页面第一行有pageEncodingUTF-8。数据库连接 URL 里也要带characterEncodingutf8。三处都统一成 UTF-8乱码基本消失。5.2 数据库连接超时连接一直不释放导致连接池被占满现象系统运行一段时间后第一次操作正常第二次就报connection refused或too many connections。原因DAO 方法里的finally块没有正确关闭连接每个请求都打开一个新连接用完不关MySQL 的连接数上限到了。解决检查每个 DAO 方法的 finally 块里是否有DBUtil.close(rs, ps, conn)。只看新增的方法还不够要全文搜索有没有conn.createStatement()之后没有关闭的代码。更稳妥的方法是改造 DBUtil用 ThreadLocal 管理连接确保一次请求只有一个连接且最终一定关闭。5.3 积分重复累加刷新页面导致同一个订单被多次积分现象点击「提交订单」按钮后连续刷新页面会员积分增加了不止一次。原因表单被重复提交。浏览器刷新时重新发送了上一次的 POST 请求Servlet 再次执行积分累加逻辑。解决前端在提交成功后location.replace到结果页而不是用location.href直接跳转后端在 Servlet 里判断 session 中是否有同名标记处理完成后移除。这个坑在答辩演示时最容易暴露因为评委很可能让你多点几次按钮。5.4 删除会员时外键冲突会员有积分流水记录时删不掉现象删除一个消费过的会员报Cannot delete or update a parent row: a foreign key constraint fails。原因积分流水表里有外键引用会员表的主键不先删流水就删会员数据库约束不放行。解决删除会员前先删除该会员的积分流水和消费记录。写 SQL 时要注意顺序先删明细再删主表。代码实现上把这段逻辑放进事务里避免删除一半时出错留下一堆孤儿数据。5.5 部署路径硬编码项目换个目录就找不到跳转页面现象本地跑得好好的换一台电脑部署点击登录跳转时 404。原因Servlet 里用了绝对路径/jf/login.jsp部署名一变路径就失效或者 JSP 里的资源引用用了以/开头的绝对路径。解决把所有的response.sendRedirect(/jf/xxx.jsp)改成相对路径或者用request.getContextPath()拼接。JSP 页面里所有 JS、CSS、图片引用统一改成${pageContext.request.contextPath}/static/xxx这种写法。这个问题虽然不致命但能直接体现代码的规范性答辩时是加分项。6. 最后的整合技巧用 SQL 脚本快速再造一份答辩演示数据项目跑通之后最容易被忽略但最影响答辩效果的事情是演示数据太假。如果会员表只有几条记录积分流水都是同一时间产生的评委一眼就能看出来这是刚初始化的系统。我总结一个整合技巧写一个 SQL 脚本批量生成一年内的消费和积分数据让它看起来像一个真实运营了半年的系统。-- 生成一年内的随机积分流水记录 DELIMITER $$ CREATE PROCEDURE generate_test_data() BEGIN DECLARE i INT DEFAULT 1; DECLARE member_count INT DEFAULT 0; DECLARE max_member_id INT DEFAULT 0; SELECT COUNT(*) INTO member_count FROM member; SELECT MAX(id) INTO max_member_id FROM member; SET i 1; WHILE i 500 DO INSERT INTO points_log (member_id, change_value, change_type, create_time) VALUES ( FLOOR(1 RAND() * max_member_id), FLOOR(10 RAND() * 190), consume, DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 365) DAY) ); SET i i 1; END WHILE; END$$ DELIMITER ; CALL generate_test_data();这个脚本的核心思路用到了RAND()和DATE_SUB()两个函数。RAND()生成 0 到 1 之间的随机数乘上 190 再加 10就能得到 10 到 200 之间的随机积分值DATE_SUB(NOW(), INTERVAL ... DAY)让流水时间分布在过去一年内这样查询月度报表时可以看到不同月份的积分累积趋势而不是所有数据集中在同一天。执行完这个存储过程之后再随手执行一条更新 member 表已累计积分的语句UPDATE member m SET m.points ( SELECT COALESCE(SUM(change_value), 0) FROM points_log pl WHERE pl.member_id m.id );这样会员表的积分余额和流水表就对上了演示时你从会员列表点进详情看到的积分变动历史是真实合理的。从那以后我每次拿到新的课程设计项目资源都会先按「数据脚本 → 核心业务表 → 事务边界」这个顺序绕一遍再动手改代码省掉了大量排查低级 bug 的时间。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑