资讯详情

资讯详情

Java课设实战:ATM柜员机模拟系统三层架构与JDBC持久化方案

简介这份资源是面向高校Java课程设计场景的ATM柜员机模拟系统完整项目文档适合正在完成课程设计或希望以真实项目巩固面向对象编程的学生参考。文档围绕用户登录、账户查询、存款、取款、更改密码等核心功能展开涵盖需求分析、功能图梳理、界面设计、取款与存款代码实现、逻辑校验及SQL语句操作等内容并附有项目总结与参考文献可帮助读者理清从需求到编码的完整开发脉络。压缩包共1个docx文件约1.21MB结构完整、便于直接查阅与借鉴。目前已有387人学习适合需要参考课程设计框架、补充功能实现思路或对照完善自身项目的学习者使用。1. 从一份“能跑但不敢交”的课设说起每年做 Java 课程设计ATM 柜员机模拟系统几乎是出现频率最高的题目之一。原因很直接需求边界清楚、功能点不多不少、既能练 Swing 界面又能练 JDBC 持久化答辩时老师也容易提问。但真正动手做的人很快会发现一个尴尬局面——代码能跑登录、存取款、改密都点得通可一旦要交报告、要答辩、要给同组同学讲清楚“你这套东西是怎么组织的”就说不明白了。问题不在功能而在结构界面和业务逻辑糊在一个类里、金额校验散落在各个按钮的监听器里、数据库连接随手 open 随手 close最后连自己都不敢改。这套 ATM 柜员机模拟系统的课程设计方案解决的就是这个“能跑但不敢交”的问题。它面向的是要交 Java 课设的学生也适合需要一份可交付小系统原型的从业者参考。下面按需求、架构、界面、业务、数据、验收的顺序把一份能直接照着搭的方案拆开讲。2. 需求与选型六个功能点背后的真实约束2.1 功能清单不是越多越好而是要能闭环ATM 柜员机模拟系统的功能清单原文列了六项用户登录、账户查询、存款、取款、修改密码、退出系统正文里还补了转账。这七项放在一起恰好构成一个完整的业务闭环身份进入 → 信息读取 → 资金流入 → 资金流出 → 资金转移 → 凭证变更 → 安全退出。做课设最容易犯的错是贪多加什么理财、积分、账单分期结果每一项都只做了一半。我的建议是严格守住这七项把每一项的边界条件做扎实比堆十个半成品功能更能拿分。具体到每一项的验收口径通常的做法是这样定的登录必须区分“卡号不存在”和“密码错误”两种提示不能统一报“登录失败”否则调试时是黑匣子查询要同时返回余额和最近若干条交易记录记录条数可以固定为 5 条够展示又不撑爆界面存款只接受整百金额这是 ATM 的真实约束也是逻辑校验的天然考点取款在整百约束之外还要做余额比较改密要校验原密码、新密码两次输入一致、新密码不等于原密码退出要能回到登录界面而不是直接杀进程方便演示时反复操作。2.2 技术选型Swing JDBC MySQL 是课设的稳妥组合界面层用 Java Swing这是原文明确的方向也是课程设计场景下最合理的选择。Swing 是 JDK 自带不需要额外装前端框架老师机器上双击就能跑答辩现场不会因为环境问题翻车。布局上用绝对定位setLayout(null)配合setLocation/setSize虽然不够优雅但对课设这种固定分辨率的场景完全够用而且所见即所得调界面时不用跟布局管理器较劲。持久层用 JDBC 直连 MySQL配合 MyBatis 的 Mapper 接口做 SQL 映射。原文的代码里出现了SqlSession、MysqlOperateMapper说明用的是 MyBatis。这个组合的好处是 SQL 写在 XML 里答辩时老师问“你的 update 语句在哪”你能直接翻出来给他看比 JPA 自动生成 SQL 更容易讲清楚。数据库选 MySQL 而不是文件存储是因为转账、余额比较这类操作天然适合用 SQL 的原子更新来表达money money - #{money}一条语句就完成了读改写避免了先查后改的并发窗口。2.3 分层把“按钮里写业务”这个坑提前填掉原文的代码结构已经透出了分层的意图ChangeMoneyPanel是界面层HandleServiceImpl是业务层MysqlOperateMapper是数据访问层中间用ResultBoolean做统一返回。这个结构值得照搬因为它解决了一个课设里极常见的问题——业务逻辑和界面耦合。如果直接在按钮的actionPerformed里写 JDBC那么存款和取款两个界面的校验代码会重复两遍改一处漏一处最后自己都记不清哪个界面校验了整百、哪个没校验。分层的职责边界我一般这样划界面层只负责收集输入、展示提示、控制窗口切换不碰任何 SQL业务层负责参数校验、余额比较、事务提交返回带消息的Result对象数据访问层只做单条 SQL 的映射。这样界面层拿到Result后只需要判断data是 true 还是 false把massage弹出来即可界面代码会薄很多。3. 总体架构设计三层结构与窗口容器的职责划分3.1 三层结构的调用链整个系统的调用链是单向的用户点击按钮 → 界面层收集输入 → 调用业务层方法 → 业务层校验后调用 Mapper → Mapper 执行 SQL → 结果逐层回传。这条链上不允许反向调用也就是业务层不能去操作界面组件界面层不能直接 new 一个SqlSession。原文的ChangeMoneyPanel里按钮监听器只做了三件事取输入框文本、调handleService.addMoney、根据返回结果弹窗并决定是否返回菜单这就是正确的写法。用一张表把三层的职责和产出物列清楚搭代码时照着填即可层次典型类职责不该做的事界面层LoginPanel、ChangeMoneyPanel、MainView输入收集、提示弹窗、CardLayout切窗写 SQL、做余额比较业务层HandleServiceImpl参数校验、余额判断、事务提交操作 Swing 组件数据层MysqlOperateMapper XML单条增删改查映射做业务规则判断3.2 窗口容器一个 CardLayout 管住所有面板界面切换是这类课设的第二大难点。原文提到“1 个主窗口和 5 个窗口”用多个JFrame互相setVisible也能跑但窗口一多就会出现关不掉、叠层、焦点乱跳的问题。更稳的做法是单JFrameCardLayout主窗口只创建一次登录、菜单、查询、存取款、转账、改密各做成一个JPanel全部塞进同一个contentPane用卡片名切换。// 主窗口初始化所有功能面板一次性注册进 CardLayout JFrame frame new JFrame(ATM 柜员机模拟系统); CardLayout cardLayout new CardLayout(); JPanel contentPane new JPanel(cardLayout); MainView mainView new MainView(); // 持有 currentId 等会话状态 contentPane.add(new LoginPanel(contentPane, cardLayout, mainView), 登录); contentPane.add(new MenuPanel(contentPane, cardLayout, mainView), 菜单); contentPane.add(new ChangeMoneyPanel(存入, contentPane, cardLayout, mainView), 存款); contentPane.add(new ChangeMoneyPanel(取出, contentPane, cardLayout, mainView), 取款); // 查询、转账、改密面板同理注册 frame.setContentPane(contentPane); frame.setSize(700, 500); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true);这段代码的关键点有三个。第一MainView作为会话状态的载体登录成功后把当前卡号写进mainView.currentId后续所有业务操作都从这里取用户标识避免每个面板各自维护一份用户信息导致不一致。第二ChangeMoneyPanel用构造参数type区分存款和取款同一个类复用两套界面这是原文已经采用的技巧省掉一个重复类。第三CardLayout的show方法按卡片名切换返回按钮只需要cardLayout.show(contentPane, 菜单)一行不用管窗口的创建销毁。3.3 会话状态与登录态传递登录成功后系统需要记住“当前是谁在操作”。最直接的做法是在MainView里放一个Long currentId字段登录面板验证通过后赋值。这里有个容易忽略的细节退出系统时要把它清空否则下一个用户登录前如果误触了某个功能面板会拿着上一个用户的 id 去查余额。通常的做法是在退出按钮里把currentId置 null 并切回登录卡片登录面板每次显示时清空输入框。这个细节不写进报告没关系但答辩演示时如果被老师连续切换两个账号测试没清空就会露馅。4. 界面与交互实现登录、菜单与功能面板的落地细节4.1 登录界面两种失败提示要分开登录界面的组件很少卡号输入框、密码输入框用JPasswordField、登录按钮、退出按钮。真正的设计点在验证逻辑的返回上。业务层的登录方法应该返回一个能区分失败原因的结果而不是简单的布尔值。原文的Result类带massage字段正好可以承载这个信息。// 登录业务先查卡号是否存在再比对密码两种失败给不同提示 public ResultUser login(Long cardId, String password) { SqlSession session sqlSessionFactory.openSession(); try { MysqlOperateMapper mapper session.getMapper(MysqlOperateMapper.class); User user mapper.selectById(cardId); if (user null) { return new Result(卡号不存在请核对后重试, null); } if (!user.getPassword().equals(password)) { return new Result(密码错误, null); } return new Result(登录成功, user); } finally { session.close(); } }参数说明cardId是卡号对应数据库主键password是明文比对课设场景下可以接受但如果报告里要写安全性可以提一句“实际系统应存储加盐哈希”。finally里关 session 是必须的原文代码里多处手动close一旦中间抛异常就会泄漏连接用 try-finally 包住是最低成本的兜底。界面层拿到结果后data非空就写mainView.currentId并切到菜单卡片否则弹提示并保留输入框内容方便用户改。4.2 主菜单按钮布局与返回路径主菜单是六个功能按钮加一个退出按钮。布局上用绝对定位排成两列三行即可按钮字号放大到 30 左右答辩投影时后排也能看清。这里的设计要点是“返回路径统一”所有功能面板的返回按钮都切回“菜单”卡片菜单的退出按钮切回“登录”卡片登录卡片的退出按钮才真正System.exit(0)。这样形成一条清晰的层级演示时从登录进、从登录出中间怎么点都不会迷路。4.3 存取款面板一个类复用两套界面原文的ChangeMoneyPanel用type参数区分“存入”和“取出”通过type.substring(0, 1)取出“存”或“取”拼进提示文案。这个技巧值得保留但要注意两个细节。一是提示文案里的“整百”约束要写清楚原文写的是“ATM 机存/取款必须是整百的”这句话直接告诉用户规则比操作失败后再报错体验好。二是输入框在返回菜单时要清空原文的返回监听器里做了moneyText.setText()这个不能省否则下次进来还留着上次的金额演示时容易误操作。背景图的处理原文用了paintComponent重绘这是 Swing 自定义背景的标准做法。需要注意的是图片资源要放在src/main/resources/images/下并用getClass().getResource加载打成 jar 后路径才不会丢。如果图片加载失败返回 nullpaintComponent里要判空否则整个面板白屏这个坑我在早期项目里踩过。5. 业务逻辑与数据持久化校验、事务与 SQL 映射5.1 金额校验的顺序有讲究原文的addMoney和minusMoney里校验顺序是非空 → 纯数字 → 整百 → 余额比较。这个顺序不是随便排的它遵循“从便宜到贵”的原则字符串判空和正则匹配不碰数据库先做余额比较要查库放最后。如果顺序反了用户输入一个空字符串系统先去查了一遍数据库再报“不能为空”纯属浪费。// 取款校验先做无副作用的格式校验再查库比余额 public ResultBoolean minusMoney(Long id, String money) { if (money null || money.trim().isEmpty()) { return new Result(金额不能为空, false); } if (!StringUtils.isStrictlyNumeric(money)) { return new Result(金额含有非法字符, false); } if (Double.parseDouble(money) % 100 ! 0) { return new Result(ATM 仅支持百元面额零钱请前往柜台, false); } SqlSession session sqlSessionFactory.openSession(); try { MysqlOperateMapper mapper session.getMapper(MysqlOperateMapper.class); User user mapper.selectById(id); if (user.getMoney().compareTo(new BigDecimal(money)) 0) { return new Result(余额不足, false); } mapper.minusMoneyById(id, new BigDecimal(money)); session.commit(); return new Result(取款成功, true); } finally { session.close(); } }这里有两个值得在报告里展开讲的点。第一金额用BigDecimal而不是double因为double做减法会出现0.1 0.2 ! 0.3这类精度问题余额算着算着就多出个尾数答辩时老师很可能会问。第二compareTo的返回值语义要记牢小于 0 表示前者小于后者原文注释里写得很清楚这个注释建议保留团队协作时省得别人再查一遍文档。5.2 事务提交与连接关闭MyBatis 默认不开自动提交所以每次写操作后必须显式session.commit()否则数据只在会话内可见关掉程序就没了。原文的代码在addMoneyById和minusMoneyById之后都调了commit这是对的。但close放在 commit 之后、没有 try-finally 保护一旦 commit 抛异常连接就泄漏了。改成上面的 try-finally 结构把close放进 finally是更稳妥的写法。课设数据量小泄漏几次看不出来但报告里写上“使用 try-finally 确保连接释放”是个加分项。5.3 SQL 映射原子更新比先查后改更安全原文的 XML 里两条 update 语句写得很干净update idaddMoneyById update tb_user set money money #{money} where id #{id} /update update idminusMoneyById update tb_user set money money - #{money} where id #{id} /update这种写法把“读余额、算新值、写回”三步压缩成一条 SQL数据库层面保证原子性。如果改成先select出余额、在 Java 里加减、再update绝对值两个请求并发时就会互相覆盖。课设单人演示看不出差别但报告的需求分析里既然写了“数据一致性”实现上就该对得上。#{money}是 MyBatis 的预编译占位符能防 SQL 注入不要图省事用${money}字符串拼接。转账功能需要两条 update 同时成功或同时失败这时才需要真正的事务边界在业务层开 session 后连续调minusMoneyById和addMoneyById两次都成功再commit任何一步失败就rollback。这是整个系统里唯一需要显式回滚的地方报告里单独写一段“转账的事务处理”比泛泛谈“保证数据安全”有说服力得多。6. 避坑与排查课设里最容易翻车的五个点6.1 现象登录后点任何功能都报空指针原因通常是mainView.currentId没赋值就切了卡片或者登录面板和功能面板持有的不是同一个MainView实例。解决方法是把MainView作为构造参数逐层传递全系统只 new 一次登录成功时赋值、退出时置空并在每个功能面板的入口处加一句if (mainView.currentId null) { 切回登录; return; }兜底。6.2 现象存取款成功但重启程序后余额回到原值原因是忘了session.commit()或者 commit 之后又开了新 session 查旧数据。排查时先在业务方法里打印 commit 前后的余额确认写库动作真的发生了再检查 Mapper XML 的namespace和接口全限定名是否一致不一致时 MyBatis 会静默不报错SQL 根本没执行。6.3 现象输入“100.0”被判为非法字符isStrictlyNumeric这类工具方法通常只认纯数字串小数点会被拦下。如果产品上允许带小数校验逻辑要改成先BigDecimal解析再判整百如果不允许提示文案要写“请输入整数金额”。这个问题的根源是校验规则和提示文案没对齐用户按提示操作反而失败体验最差。6.4 现象背景图在 IDE 里正常打成 jar 后白屏原因是图片用了文件系统路径而不是 classpath 路径。解决方法是资源放resources目录用getClass().getResource(/images/xxx.gif)加载并在paintComponent里对backgroundImage null做判空降级至少保证界面能用。6.5 现象连续快速点击“取款”按钮余额被扣了两次原因是按钮监听器里没有防重入用户手快或鼠标连点就会触发多次。简单做法是在业务方法入口加一个boolean submitting标志提交前置 true、finally里置 falsetrue 时直接 return。更彻底的做法是把按钮在提交期间setEnabled(false)完成后再恢复。这个细节在答辩演示时如果被老师连点测试能明显看出功底。7. 验收与答辩用一张检查表把方案讲圆验收这件事很多人的做法是“功能点一遍能跑就行”但答辩时老师问的往往不是功能有没有而是“你怎么证明它是对的”。我的习惯是准备一张验收检查表按功能、异常、数据、界面四类各列几条演示时按表走讲的时候按表说逻辑自然就顺了。验收类别检查项预期结果功能正确卡号密码登录进入主菜单显示当前卡号功能存款 500提示成功查询余额增加 500功能取款超过余额提示余额不足余额不变异常金额输入字母提示非法字符不写库异常金额输入 150提示仅支持百元面额数据操作后重启程序余额与操作后一致界面各功能面板返回均回到主菜单输入框已清空演示顺序建议按“登录 → 查询 → 存款 → 查询 → 取款 → 查询 → 转账 → 改密 → 退出 → 重新登录验证新密码”走一遍这条路径覆盖了所有正向流程和状态变化老师一眼能看出数据是连贯的。异常用例穿插在中间演示比如取款时故意输 150 和超额金额展示校验生效。整个过程控制在三到五分钟留时间回答提问。答辩提问高频集中在三个方向一是“余额比较为什么用 BigDecimal”答精度问题并举0.10.2的例子二是“转账如果中途失败怎么办”答事务回滚指出哪两行 update 在同一个 session 里三是“密码明文存储安全吗”坦诚说课设场景做了简化实际应加盐哈希同时说明你已经意识到这个问题。第三个问题最忌讳硬撑承认简化并给出改进方向比编一个不存在的加密实现更能得分。我带的项目里有个习惯答辩前一定自己按检查表完整走三遍第三遍专门用“手快连点”和“乱输字符”的方式去破坏它。第一次做 ATM 课设时我没做这一步演示时被老师连续切换两个账号结果第二个账号看到了第一个账号的余额当场卡住。从那以后我每次交课程设计都强制走一遍“换账号 连点 乱输入”的破坏性验收把能想到的异常路径先自己踩一遍。希望这套结构和检查表能帮到你照着搭一遍交上去的东西至少是自己敢讲清楚的。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →