Java课程设计实战:Swing+MyBatis实现ATM模拟系统 简介这份资源是面向高校Java课程设计场景的ATM柜员机模拟系统完整项目文档适合正在完成课程设计或希望以真实项目巩固面向对象编程的学生参考。文档围绕用户登录、账户查询、存款、取款、更改密码等核心功能展开涵盖需求分析、功能图设计、窗口切换与登录主菜单界面布局并给出存取款功能代码、逻辑校验思路及SQL语句示例还涉及数据管理、安全机制与异常处理等重难点分析。压缩包共1个docx文件约1.21MB内容按需求分析、界面设计、功能实现、项目总结与附录组织目录清晰便于按模块查阅和对照撰写报告。目前已有387人学习适合需要完整设计思路、代码片段与文档框架的读者参考借鉴。1. 拆开这份 JAVA 课程设计ATM 柜员机模拟系统到底能跑通什么课程设计季一到后台总有人问同一个问题有没有一份能直接跑起来、结构还算干净的 Java 小系统拿来改改就能交差顺便把 Swing 和 JDBC 这两块补一补。这份 ATM 柜员机模拟系统就是冲着这个场景来的——登录、查询、存款、取款、转账、改密、退出七个动作全走图形界面数据落在 MySQL 里不是那种内存里塞个 HashMap 糊弄人的玩具。它适合两类人一是刚学完 Java 语法、需要一次完整 GUI 加数据库串联练手的学生二是想找个现成骨架把业务逻辑换成自己题目的开发者。下面按我实际拆包的顺序讲从工程结构到关键代码再到几个必踩的坑能抄的地方直接给代码。2. 工程结构与技术选型为什么是 Swing 加 MyBatis 而不是纯 JDBC拿到一个课程设计包我第一件事不是看界面是看目录和依赖。这份项目的分层比多数同类作业清楚视图层用 Swing 的CardLayout做窗口切换业务层有HandleServiceImpl统一收口持久层走 MyBatis 的 Mapper 接口实体是User返回值统一包了一个ResultT。这个结构决定了它可改、可测、可扩展而不是所有逻辑堆在一个main方法里。2.1 目录分层与各层职责拆包后大致是这么几块理解了这个后面改代码才不会乱层级典型类/文件职责视图层MainView、ChangeMoneyPanel、登录面板组件布局、事件绑定、窗口切换业务层HandleServiceImpl参数校验、余额判断、事务提交持久层MysqlOperateMapper接口 XMLSQL 映射、增删改查实体/工具User、ResultT、StringUtils数据载体、统一返回、字符串校验资源images/背景图、mybatis-config.xml界面素材、数据库连接配置视图层不直接碰 SQL业务层不直接碰JButton这条线守住了后面加一个「交易流水查询」功能只需要动 Mapper 和业务层界面加个面板就行。很多课程设计翻车就翻在视图里直接new SqlSession()改一个字段要翻五个文件。2.2 选 Swing 加 MyBatis 的理由与边界Swing 是 JDK 自带的不用装额外 UI 框架课程设计环境里最稳CardLayout天然适合 ATM 这种「一个主窗口、多个功能页来回切」的交互模型比开一堆JFrame干净得多。持久层选 MyBatis 而不是纯 JDBC核心原因是 SQL 和 Java 代码分离——存款取款的 SQL 写在 XML 里改语句不用重新编译业务类而且#{}预编译天然挡掉一部分注入。但边界也要说清楚这套东西是单机模拟没有并发控制没有真正的密码加密原文提到加密存储是需求实现里是明文比对也没有事务隔离级别的考量。拿它当学习骨架没问题直接往生产环境搬就是给自己挖坑。我一般会跟人说把它当「能跑通全链路的教学样本」别当「金融系统雏形」。2.3 环境准备与首次运行跑起来之前环境对齐是第一步版本错位是新手最常见的卡点# 1. 确认 JDK 版本项目用 SwingJDK 8 及以上均可 java -version # 2. 建库建表字段名要和 Mapper XML 对齐 mysql -u root -p -e CREATE DATABASE atm_db DEFAULT CHARSET utf8mb4; # 3. 导入依赖mybatis、mysql-connector-java、junit测试用 # Maven 项目直接在 pom.xml 里加非 Maven 项目把 jar 丢进 lib 并 Add to Build Path建表语句按实体字段来核心就一张用户表CREATE TABLE tb_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, card_no VARCHAR(20) NOT NULL UNIQUE, -- 卡号登录用 password VARCHAR(50) NOT NULL, -- 密码教学项目明文存储 money DECIMAL(12,2) DEFAULT 0.00 -- 余额用 DECIMAL 不用 DOUBLE );这里有个细节值得单独说余额字段用DECIMAL(12,2)而不是DOUBLE。浮点数做金额运算会出现0.1 0.2 ! 0.3这类玄学问题取款几次之后余额对不上账排查半天以为是 SQL 写错了。金额一律用BigDecimal对应DECIMAL这是血泪经验不是洁癖。数据库连接配置在mybatis-config.xml里改url、username、password三处即可url记得带上useSSLfalseserverTimezoneAsia/Shanghai否则高版本驱动连不上会报时区错误。3. 核心功能实现存取款代码逐段拆解与参数说明界面能点开只是第一步真正决定这份代码能不能改的是业务层。原文给的ChangeMoneyPanel和HandleServiceImpl是全文最有价值的部分我按「界面事件 → 业务校验 → SQL 执行」这条链路拆开讲每段代码后面说清楚它在干什么、参数怎么传、哪里容易改错。3.1 存取款面板的事件绑定逻辑ChangeMoneyPanel用了一个很讨巧的设计存款和取款共用一个面板类靠构造参数type区分。传「存入」就是存款页传「取出」就是取款页标题、按钮文案、调用的业务方法全由这个字符串分流。public ChangeMoneyPanel(String type, Container contentPane, CardLayout cardLayout, MainView mainView) { setLayout(null); // 取 type 首字存入-存取出-取用于拼接提示文案 final String substring type.substring(0, 1); JLabel label new JLabel(请输入 substring 款金额,ATM 机 substring 款必须是整百的); final JTextField moneyText new JTextField(); JButton enterButton new JButton(type); final JButton backButton new JButton(返回); enterButton.addActionListener(e - { HandleServiceImpl service new HandleServiceImpl(); ResultBoolean r; if (type.equals(存入)) { // currentId 是登录成功后挂在 MainView 上的当前用户主键 r service.addMoney(mainView.currentId, moneyText.getText().trim()); } else { r service.minusMoney(mainView.currentId, moneyText.getText().trim()); } JOptionPane.showMessageDialog(mainView, r.massage, 提示, JOptionPane.INFORMATION_MESSAGE); if (r.data) { // 业务成功才返回菜单失败留在原页让用户改 backButton.doClick(); } }); backButton.addActionListener(e - { mainView.setTitle(菜单); cardLayout.show(contentPane, 菜单); moneyText.setText(); // 清空输入避免下次进来残留上次金额 }); // 组件 add 与坐标设置略见原文 }几个参数要盯住mainView.currentId是当前登录用户的主键登录成功时必须赋值否则存取款会往null上操作moneyText.getText().trim()一定要trim用户复制粘贴金额时常带空格不 trim 会在数字校验那步报「含有非法字符」让人一头雾水r.data为true才跳回菜单这个「成功才返回」的约定贯穿所有功能页改代码时别破坏它。3.2 业务层的四道校验与 BigDecimal 比较HandleServiceImpl里的addMoney和minusMoney是整份代码的「黑匣子」所有规则都在这。校验顺序有讲究先判空、再判非法字符、再判整百、最后判余额顺序错了提示信息就会误导用户。Override public ResultBoolean minusMoney(Long id, String money) { if (money.equals()) { return new Result(false, 不能为空); } if (!StringUtils.isStrictlyNumeric(money)) { return new Result(false, 含有非法字符); } if (Double.parseDouble(money) % 100 ! 0) { return new Result(false, ATM 中只支持百元大钞零钱请前往柜台领取); } SqlSession session sqlSessionFactory.openSession(); try { MysqlOperateMapper mapper session.getMapper(MysqlOperateMapper.class); User user mapper.selectById(id); // BigDecimal 比较必须用 compareTo不能用 equals 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(); // 无论成功失败都关会话防止连接泄漏 } }参数说明id是用户主键money是界面传进来的字符串全程不转double做金额运算只在整百判断时临时用一下。compareTo返回负数表示余额小于取款额这个比较方式必须记住——BigDecimal的equals会连精度一起比100.0和100.00判不相等用equals判余额是经典翻车点。存款方法结构一样少了余额判断多了一步addMoneyById。两个方法都遵循「开会话 → 拿 Mapper → 执行 → commit → close」的固定套路commit漏了数据不落库close漏了连接池很快耗尽这两个是新手改代码时最容易删掉的两行。3.3 Mapper 与 SQL两条 update 语句的写法差异持久层就两条核心 SQL看着简单但写法直接决定并发下会不会算错账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这里用的是「在数据库端做加减」而不是「查出来、Java 里算、再写回去」。前者一条语句原子完成后者在单机教学项目里看不出问题一旦两个人同时取款就可能出现覆盖写。#{money}是预编译占位符传BigDecimal会自动映射到DECIMAL列别改成${money}字符串拼接那是注入漏洞的入口。selectById的查询语句对应User实体字段名和列名不一致时要么起别名要么配resultMap课程设计里字段少直接select id, card_no as cardNo, password, money from tb_user where id #{id}起别名最省事。4. 避坑与排查五个真实会卡住你的问题这份代码我照着跑过一遍也在别人的机器上远程排过几次问题高度集中。下面五条按「现象 → 原因 → 解决」写遇到对得上的直接照方抓药。4.1 登录后存取款报空指针现象登录界面能进主菜单点存款输入金额确认控制台抛NullPointerException定位到addMoney里的id为null。原因是登录成功时只做了密码比对忘了把查出来的用户主键赋给mainView.currentId。解决登录校验通过后立刻mainView.currentId user.getId();并在主菜单加载时断言它非空早失败早发现。4.2 存款成功但重启后余额没变现象界面提示「存款成功」退出重进余额还是原值。原因基本是session.commit()被漏掉或写在close()之后。MyBatis 默认不自动提交openSession()不带true参数就必须手动 commit。解决把commit放在 SQL 执行之后、close之前用try-finally保证close一定执行别把commit塞进finally失败时也提交就麻烦了。4.3 输入 100.00 提示「含有非法字符」现象用户输入100.00或带空格的100被isStrictlyNumeric判为非法。原因是校验工具只认纯数字串小数点、负号、空格都不放行。解决界面层先trim()校验工具按需扩展成允许一位小数点的正则或者干脆在输入框加DocumentFilter只允许数字从源头堵住。改校验工具时注意别把整百判断的顺序打乱。4.4 余额比较结果反了现象余额 500取 300 提示「余额不足」余额 100取 500 反而成功。原因是用了equals或把compareTo的返回值方向记反。a.compareTo(b) 0表示 a 小于 b取款判断应该是「余额小于取款额就拒绝」。解决统一封装一个boolean enough(BigDecimal balance, BigDecimal amount)方法全项目只调它别在每个方法里手写比较。4.5 背景图加载报 NPE 或图片不显示现象new ImageIcon(getClass().getResource(/images/xxx.gif))抛空指针或界面一片灰。原因是资源路径以/开头表示从 classpath 根找图片没被打进输出目录或者文件名大小写和代码不一致。解决确认images目录在源码根下且被标记为资源目录打包后检查target/classes/images里有没有文件getResource返回null时先打印路径排查别直接Objects.requireNonNull把异常吞成一行看不懂的堆栈。5. 从能跑到能改把这份骨架扩成自己的课程设计跑通只是及格线课程设计要拿得出手得在骨架上加东西。我给一个改动成本最低、又能明显拉开差距的方向加一张交易流水表把每次存取款记下来查询页顺带展示最近五条。改动只涉及三处不动现有界面结构。先建流水表字段和用户表通过user_id关联CREATE TABLE tb_trade ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, type VARCHAR(10) NOT NULL, -- DEPOSIT / WITHDRAW / TRANSFER amount DECIMAL(12,2) NOT NULL, trade_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_user_time (user_id, trade_time) );然后在HandleServiceImpl的存取款方法里SQL 执行成功后补一条插入注意和余额更新放在同一个SqlSession里保证要么都成功要么都回滚mapper.minusMoneyById(id, new BigDecimal(money)); mapper.insertTrade(id, WITHDRAW, new BigDecimal(money)); // 同一会话内 session.commit();最后在查询面板加一个JTable数据源调selectRecentTrades(userId, 5)SQL 用order by trade_time desc limit 5。这一步做完你的系统就从「功能演示」变成了「有审计痕迹的业务闭环」答辩时能讲的东西立刻多一层。验证方法也简单连续做三笔操作存 500、取 200、转 100重启程序查询页应显示三条流水且余额为 200再手动把流水插入语句注释掉重跑确认余额更新和流水记录是同一事务——如果余额变了流水没记说明你的事务边界没圈对。这个反向验证比正向点一遍更能暴露问题。从那以后我每次拿到这类课程设计包都强制先跑一遍「存—取—重启—查流水」这条最小闭环再动任何一行代码。骨架跑不通就改业务等于在流沙上盖楼。希望这份拆解能帮你少走几个晚上。本文还有配套的精品资源点击获取