2026/10/24 20:58:15

Java课程设计实战:基于Swing+MyBatis+MySQL的ATM柜员机模拟系统

Java课程设计实战:基于Swing+MyBatis+MySQL的ATM柜员机模拟系统 简介一份适合Java课程设计参考的ATM柜员机模拟系统项目报告面向正在完成Java课程设计、或希望系统练习面向对象编程与Java Swing界面开发的学生。报告围绕用户登录、账户查询、存款、取款、修改密码等核心功能从需求分析、功能图、界面设计到功能实现逐步展开详细说明了窗口切换与友好界面设计并重点讲解了取款/存款代码、逻辑校验和SQL数据库操作同时涉及数据管理、安全机制、异常处理等重难点可帮助读者快速掌握ATM系统的模块划分与编码要点。整个资料为1个docx格式的课程设计报告文档压缩包仅1个文件、大小约1.21MB文档结构清晰包含完整设计过程、关键代码、测试思路与项目总结适合作为课程设计报告模板或开发蓝本。目前已有387人学习/浏览值得推荐给需要完成Java课设或入门GUI编程的同学。1. 需求立住ATM柜员机模拟系统到底要解决什么问题JAVA课程设计年年都有新题目但ATM柜员机模拟系统始终是那个看着简单、做起来全是细节的经典选题。很多同学拿到这个题目第一反应是不就是做个界面加几个按钮吗等真正动手才发现登录验证、余额查询、存取款、改密、转账每一块都要跟数据库打交道还要处理各种输入异常和业务规则。如果你正在为这门课设发愁或者想找一份能直接参考的完整方案这套基于Java Swing MyBatis MySQL的ATM柜员机模拟系统就是一个很合适的样板。它的定位很明确用桌面GUI模拟真实ATM的操作流程让学生把Java面向对象、GUI事件处理、JDBC/数据库操作这些知识点串起来同时把需求分析→界面设计→功能实现→测试验收的完整开发流程走一遍。本文会从需求、设计、实现、避坑、验收五个维度把这份方案的每个细节拆开讲清楚你可以直接照着搭也可以在自己的项目里改。2. 总体设计界面层、服务层与数据层的三层结构2.1 需求边界哪些功能必须做哪些可以不做ATM柜员机模拟系统最怕的就是功能边界没划清楚做着做着发现工作量失控。参考这份课设的方案核心功能锁定在六个模块用户登录、账户查询、存款、取款、转账、修改密码。登录是入口查询和存取款是主业务改密属于账户安全操作退出系统做状态收尾。这六个模块对应真实ATM的基本操作既覆盖了教学要求的知识点又不至于把项目撑得太大。有些功能是可以有取舍的。比如原文的界面窗口只有1个主窗口加5个功能窗口转账窗口独立存在但并没有把转账做成单独的复杂流程而是复用输入对方卡号金额的简单模式。这种设计的好处是每个窗口职责单一代码结构清晰答辩的时候也能把模块化设计讲明白。至于真实ATM会有的凭条打印、吞卡处理、超时退出等属于加分项课设阶段不做完全合理——把核心六个功能做扎实比堆一堆花哨但没实现的按钮更有说服力。另外一个容易被忽略的点是角色定位。这个系统是单角色系统只有普通用户视角没有管理员后台。所以不需要做用户管理界面、交易流水导出这类后台功能。数据层面只需要一张用户表存卡号、密码、余额再加一张可选的交易记录表就能支撑所有业务。把范围控制在这个程度一个小组分工协作刚好合适。2.2 分层结构Swing界面、业务校验、数据操作各管各的这套方案虽然是一个课设规模的程序但代码组织并没有写成所有逻辑堆在一个类里的败笔。从源码结构能看出清晰的职责划分界面层在Panel和View类里负责窗口、按钮、输入框、事件监听服务层由HandleServiceImpl承担负责空值校验、非法字符检查、金额规则校验、余额比对这些业务逻辑数据层借助MyBatis的Mapper接口和XML映射文件操作MySQL数据库。这种分层的直接好处是改界面不会碰数据库代码换数据库也不用改界面逻辑。比如存取款界面里的按钮点击事件做的事情只是把金额字符串传给HandleServiceImpl然后弹窗显示返回的Result对象里的提示信息至于金额是不是数字、是不是整百、余额够不够全部交给服务层判断。Result这个自定义返回类型值得留意它封装了data和massage两个字段前者表示操作成功与否后者表示给用户看的提示文字这样界面层不需要自己拼错误信息所有业务反馈都统一走这一个出口。对于答辩来说这个结构非常好讲界面层负责交互服务层负责规则数据层负责持久化每一层都有明确的职责边界。如果导师追问为什么用Result而不是直接返回boolean你可以说boolean只能表达成功失败但用户需要知道失败原因比如余额不足和含有非法字符是两种完全不同的失败Result把成功状态和提示消息打包返回界面层拿到后直接弹窗展示就行。2.3 窗口切换方案CardLayout与按钮事件的配合界面切换是ATM模拟系统的一个隐藏考点。真实ATM的流程是登录→主菜单→选择功能→返回主菜单每一步都是全屏切换。这个方案用的是CardLayout布局管理器把所有Panel放进同一个容器里通过cardLayout.show(contentPane,菜单)这样的语句在面板之间切换。主菜单界面放多个JButton按钮每个按钮绑定一个ActionListener点击后切换到对应功能面板。比如点存款按钮就调用cardLayout.show(contentPane,存款)窗口标题同步改成存款点返回按钮就切回菜单面板并且把输入框清空避免下次进来还残留上一次的输入。这个清空操作看着小实际很容易漏如果取款失败后直接返回金额输入框里还留着刚才输入的数字下次进来用户会以为金额被保留了容易产生误解。所以backButton的监听器里除了切面板一定记得moneyText.setText()。面板切换的时候还涉及当前登录用户信息的传递。mainView.currentId就是用来记录当前登录用户ID的字段登录成功后赋值后续存钱取钱都靠它定位数据库里的记录。这种用单窗口共享状态的方式简单直接课设阶段够用不用引入全局变量管理器之类的复杂方案。3. 功能实现存款、取款的业务逻辑与界面事件绑定3.1 存取款界面ChangeMoneyPanel的复用设计存取款界面最值得借鉴的是它的复用思路。方案里没有分别写存款Panel和取款Panel而是在ChangeMoneyPanel里通过type参数区分——type为存入时进入存款逻辑type为取钱时进入取款逻辑。界面标题、确认按钮文字、提示语都用这个type动态拼接一套界面代码同时服务两个功能减少了大量重复代码。public class ChangeMoneyPanel extends JPanel { private final Image backgroundImage; public ChangeMoneyPanel(String type, Container contentPane, CardLayout cardLayout, MainView mainView) { setLayout(null); String substring type.substring(0, 1); JLabel label new JLabel(请输入 substring 款金额, ATM机 substring 款必须是整百的); JTextField moneyText new JTextField(); JButton enterButton new JButton(type); JButton backButton new JButton(返回); enterButton.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { HandleServiceImpl handleService new HandleServiceImpl(); ResultBoolean result; if (type.equals(存入)) { result handleService.addMoney(mainView.currentId, moneyText.getText().trim()); } else { result handleService.minusMoney(mainView.currentId, moneyText.getText().trim()); } JOptionPane.showMessageDialog(mainView, result.massage, 提示, JOptionPane.INFORMATION_MESSAGE); if (result.data) { backButton.doClick(); } } }); backButton.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { mainView.setTitle(菜单); cardLayout.show(contentPane, 菜单); moneyText.setText(); } }); } }这段代码里有几个细节值得说明。第一个是moneyText.getText().trim()用户输入金额时很可能带前后空格不trim的话 100 这样的字符串会被判定为非法字符这个踩坑点很隐蔽但很常见。第二个是操作成功后才触发backButton.doClick()失败则停留在当前界面用户可以修改金额再试这个交互逻辑跟真实ATM一致。第三个是所有提示都用JOptionPane.showMessageDialog弹出参数里的提示是窗口标题INFORMATION_MESSAGE是消息类型这些细节不用特别记忆但答辩时被问到你如何处理操作反馈可以顺口回答。3.2 服务层业务校验加钱与扣钱的对称逻辑存款和取款的服务层方法是对称设计的但两者有一个关键差异存款只需保证金额合法取款还要额外验证余额充足。这段逻辑校验代码是整个项目的核心知识点之一课程设计报告里单独辟了一节写它。Override public ResultBoolean addMoney(Long id, String money) { if (money.equals()) { return new ResultBoolean(不能为空, false); } if (!StringUtils.isStrictlyNumeric(money)) { return new ResultBoolean(含有非法字符, false); } if (Double.parseDouble(money) % 100 ! 0) { return new ResultBoolean(ATM中只支持百元大钞零钱请前往柜台领取, false); } SqlSession sqlSession sqlSessionFactory.openSession(); MysqlOperateMapper userMapper sqlSession.getMapper(MysqlOperateMapper.class); userMapper.addMoneyById(id, new BigDecimal(money)); sqlSession.commit(); sqlSession.close(); return new ResultBoolean(存款成功, true); } Override public ResultBoolean minusMoney(Long id, String money) { if (money.equals()) { return new ResultBoolean(不能为空, false); } if (!StringUtils.isStrictlyNumeric(money)) { return new ResultBoolean(含有非法字符, false); } if (Double.parseDouble(money) % 100 ! 0) { return new ResultBoolean(ATM中只支持百元大钞零钱请前往柜台领取, false); } SqlSession sqlSession sqlSessionFactory.openSession(); MysqlOperateMapper userMapper sqlSession.getMapper(MysqlOperateMapper.class); User user userMapper.selectById(id); if (user.getMoney().compareTo(new BigDecimal(money)) 0) { return new ResultBoolean(余额不足, false); } userMapper.minusMoneyById(id, new BigDecimal(money)); sqlSession.commit(); sqlSession.close(); return new ResultBoolean(取款成功, true); }校验顺序是一条一条叠上去的先查空再查非法字符再查整百规则最后才碰数据库。这个顺序有讲究——非法输入根本不值得打开数据库连接先把能拦的拦在门外。StringUtils.isStrictlyNumeric这个工具方法检查字符串是否全由数字组成比手动正则更省事效果等同。金额运算全程用BigDecimal而不是double这是为了避免浮点数精度问题——0.1 0.2在double里不等于0.3银行系统里这种误差是致命的。余额判断那里compareTo返回负数说明账户余额小于取款金额直接返回余额不足且不执行扣款SQL。这里有个容易被忽略的细节提前return避免了无效的数据库写操作也保证了对用户余额的安全校验。扣款SQL只有在前面的校验全部通过后才会执行并且执行后必须commit()否则事务不会真正落库。3.3 其他功能模块登录验证、改密与查询登录界面的验证逻辑走的是输入卡号和密码→查数据库→比对密文的流程。课设阶段通常不会做真正的密码加密存储直接明文或简单比对即可但如果你的报告里写了安全性需求答辩时导师可能会追问密码存储方式。我一般会建议在方案里加上MD5加盐或SHA-256的说明哪怕代码里没实现至少表达你意识到了这个问题。改密界面包含原密码、新密码、确认新密码三个输入框点击确认修改后先校验原密码是否正确再校验新密码和确认密码是否一致最后执行update语句。这里同样有一条隐藏规则新密码不能和原密码相同这条校验加不加都不影响跑分但加了以后答辩时能多讲一层业务思考。查询界面最简单查询当前账户余额和最近的交易记录直接select出来显示在界面上即可不需要复杂的格式化。转账功能在原文里描述为包含需要给转账对象的卡号和金额输入对应的数字即可转账成功实现思路是输入对方卡号→查对方账户是否存在→扣减自己余额→增加对方余额。这个操作涉及两个账户的余额变更必须放在同一个事务里否则可能出现自己扣了钱、对方没收到的数据不一致问题。4. 数据持久化tb_user表结构、Mapper映射与事务管理4.1 数据库设计一张用户表如何支撑全部业务整套系统的数据存储核心是tb_user表。字段设计遵循最小化原则id作为主键login_id或卡号字段做登录标识password存密码money存余额。课设阶段不需要设计单独的交易记录表因为核心功能只要把余额改对就算完成如果想把交易记录查询做成完整功能再增加一张tb_transaction表记录时间、类型、金额、操作前后余额即可。建表语句通常长这样CREATE TABLE tb_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, card_no VARCHAR(20) NOT NULL UNIQUE COMMENT 卡号, password VARCHAR(32) NOT NULL COMMENT 密码, money DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 账户余额 ); CREATE TABLE tb_transaction ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, type VARCHAR(10) NOT NULL COMMENT 存取/取款/转账, amount DECIMAL(10,2) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES tb_user(id) );money字段用DECIMAL(10,2)而不是FLOAT或DOUBLE这是银行系统的标准做法理由跟上一章说的一样浮点数有精度损失。DECIMAL在MySQL内部以字符串形式存储计算时按十进制精确处理不会出现0.10.2不等于0.3的问题。车到山前必有路但做金融相关模拟系统精度问题从一开始就别走弯路。4.2 MyBatis映射update语句的简洁写法数据操作层用了MyBatis的Mapper接口加XML映射文件方式。接口里定义addMoneyById和minusMoneyById两个方法XML里对应两条update语句。这种写法的核心技巧是直接在SQL表达式里做加减运算而不是先把余额查出来在Java里算好再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回去在高并发场景下会丢更新——两个线程同时读到余额1000都存100最后余额变成1100而不是1200。直接在update语句里写money #{money}数据库的行锁会保证同一时刻只有一个事务能改这一行的余额从机制上避免了并发覆盖问题。虽然ATM课设没有真实并发但这个知识点写进报告里会让导师眼前一亮。4.3 事务边界与资源回收MyBatis的SqlSession默认不会自动提交事务必须手动commit()才能让改动落库。原文的代码里每次操作都是openSession()→getMapper()→执行SQL→commit()→close()。这里有两个关键点。第一个是commit的时机必须在所有SQL执行成功后调用如果中途抛出异常应该调用rollback()回滚否则会出现部分更新第二个是close的时机无论成功还是失败SqlSession都要关闭否则数据库连接池会被耗尽。课设项目里最常见的资源泄漏就是忘了close()。系统跑起来先正常测了几十次之后突然报连接超时十有八九是SqlSession没关。规范做法是放在finally块里关闭但考虑到课设代码的简洁性要求至少要在成功路径上close。如果你想让方案更完整可以用try-with-resources或者ThreadLocal绑定SqlSession这些进阶做法不需要全部实现但可以在报告的改进方向里提一句。异常处理也是这一层该考虑的事。比如数据库连接失败、SQL执行异常如果不在服务层catch住异常会直接抛到界面层弹出一个红色的错误堆栈给用户看非常不友好。我一般会在服务层包一层try-catch捕获Exception后返回一个Result(false,系统繁忙请稍后再试)既保证用户看到的是友好提示又避免了堆栈信息直接暴露SQL细节。5. 避坑指南ATM课设里最容易翻车的六个问题5.1 空值校验顺序错误导致用户看到诡异报错现象用户在取款界面不输入金额直接点确定弹窗提示的不是请输入金额而是含有非法字符。 原因校验代码先执行了StringUtils.isStrictlyNumeric(money)空串.isStrictlyNumeric返回false直接走了非法字符分支根本没走到空值判断。 解决调整校验顺序先判断money.equals()并返回不能为空再做字符类型校验。这个问题看代码非常不起眼但实测必现答辩演示的时候很影响观感。5.2 余额不足判断放在扣款SQL之后现象取款金额大于余额时数据库余额变成负数用户还收到了取款成功的提示。 原因代码直接执行minusMoneyById没有先查询余额做比较或者查询了余额但没有拦截。 解决必须在执行扣款SQL之前先selectById查出当前余额用compareTo和取款金额比较余额不足直接return不执行update。这一条是金融逻辑的安全底线测试用例里必须覆盖。5.3 SqlSession未关闭导致数据库连接耗尽现象系统连续操作二三十次后界面卡死或报Too many connections。 原因每次操作new出SqlSession执行完就扔了没有close。MyBatis默认从连接池取连接不close就还不上连接池满了后续操作全部阻塞。 解决在完成数据库操作后立即调用sqlSession.close()。更稳妥的做法是放在finally块里或者用try-with-resources让JVM帮你关。5.4 金额用double类型导致精度偏差现象存款100次0.1元账户余额显示不是10.00而是9.9999999。 原因代码里用Double.parseDouble处理金额double在计算机中是二进制浮点数无法精确表示0.1。 解决金额字符串解析用new BigDecimal(money)数据库字段类型用DECIMAL运算全部交给BigDecimal的compareTo和加减方法。ATM里的整百校验用BigDecimal也一样支持可以用remainder方法取模判断。5.5 转账不做事务控制导致两边余额不一致现象A向B转账时A余额扣了但B余额没到账。 原因扣A和加B是两条update SQL中间没有事务包裹或者两条SQL在一个方法里执行了但其中一个失败后没有回滚。 解决把转账的扣自己加对方两步放进同一个事务要么都成功要么都回滚。实现上可以在SqlSession里手动控制commit和rollback。5.6 CardLayout切换后输入框残留数据现象用户查询失败返回菜单页再进入取款界面金额输入框还留着上一次输入的200。 原因切换面板时只执行了cardLayout.show()没有把输入框内容清空。 解决在返回按钮的事件里除了show面板还要对界面上的JTextField执行setText()清空操作。这个细节不难但很多同学的方案都翻过车。6. 验收与答辩怎么测、怎么讲、怎么拿高分6.1 功能测试用例表课程设计的验收看的是流程能不能完整走通、边界情况处理得干不干净。下面这组测试用例基本覆盖了ATM模拟系统的日常场景和异常场景答辩前用这组用例过一遍能兜住绝大多数提问。编号操作步骤预期结果是否通过TC01正确卡号密码登录进入主菜单必测TC02错误密码登录提示密码错误不进入系统必测TC03卡号不存在登录提示卡号不存在可测TC04查询余额显示数据库中的当前余额必测TC05存入100元余额增加100提示存款成功必测TC06存入空值提示不能为空必测TC07存入abc提示含有非法字符必测TC08存入150元提示只支持整百必测TC09取款200元且余额充足余额扣减200提示取款成功必测TC10取款2000元且余额不足提示余额不足必测TC11修改密码原密码正确密码更新成功下次用新密码登录必测TC12修改密码原密码错误提示原密码错误可测测试时注意一点先查询余额做基准再执行存取款再查询验证结果是否准确每一个用例比对修改前后的数据库记录。不要只看界面弹窗。6.2 答辩讲法从点讲到面把踩坑变成亮点答辩最忌讳的是只讲我能干什么不讲我为什么这么设计。走进答辩教室前把这四句话在脑子里过一遍第一句讲分层界面、服务、数据各管各的改一个模块不影响另外两个第二句讲校验所有输入先过空值和非法字符再过业务规则最后才碰数据库第三句讲安全金额用BigDecimal保证精确余额判断在更新之前转账操作纳入事务第四句讲教训哪几个问题是在测试中发现的、怎么修好的。这个结构的好处是每一个点都能对应到课程要求的知识点。分层对应面向对象设计和模块化校验对应异常处理和健壮性事务对应数据库知识测试对应软件工程流程。哪怕代码有一些小瑕疵只要你能把设计思路和踩坑教训讲清楚导师不会太为难你。面试官式的追问比如你的系统怎么防止并发下余额出错也可以用数据库行锁保证原子性、金额计算用BigDecimal直接接住。6.3 答辩前的强制流程答辩前那个晚上我习惯强制自己走一遍完整流程登录→查余额→存100→查余额→取200→查余额→转账→查对方余额→改密→退出→用新密码重新登录。这一套场景走完功能层面的问题基本都暴露了。如果时间宽裕再把异常场景过一遍空输入、非法字符、非整百、余额不足、错误原密码、重复新密码每条确认弹窗提示文字和预期一致。从那以后每次交课设前我都强制自己把这条主流程和异常分支各走一遍宁可多花十分钟测试也不要在验收现场翻车。希望这套方案的内容能帮到正在为课设头大的你。本文还有配套的精品资源点击获取