简介这是一套基于Java Swing开发的ATM取款机系统完整实践项目面向Java初学者与高校课程设计学生聚焦GUI编程、JDBC数据库交互及基础金融业务逻辑实现。资源包含22个文件主体为14个Java源码覆盖登录、账户管理、存取款、转账、交易记录等核心模块4个关键依赖jar包含c3p0连接池、MySQL驱动及DBUtils工具1个SQL脚本用于初始化数据库表结构与测试数据另有Eclipse工程配置文件保障开箱即用。压缩包仅1.98MB轻量易部署。已有1354人学习下载读者可直接导入IDE运行获得一个具备用户注册登录、密码校验、余额管控、事务级资金操作及完整交易日志功能的可执行桌面应用同时掌握Swing界面布局、MySQL建表与CRUD、异常处理与安全性设计等一线开发要点。1. Swing实现ATM取款机系统带sql文件一个被低估的Java GUI数据库教学闭环为什么它比“Hello World”更能锤炼工程直觉你写过 Swing 窗口、连过 MySQL、建过用户表、写过转账逻辑——但当所有模块第一次在同一个.jar里跑通账户余额实时刷新、取款失败弹窗带具体错误码、日志能追溯到某次 PIN 连续输错三次被锁卡……那一刻你才真正摸到了 Java 桌面应用的「心跳」。这不是玩具 Demo而是一个具备完整状态机插卡→验密→主菜单→取款/查询/退卡、事务边界取款必须原子性扣减余额记录流水、异常隔离SQL 异常不崩 UI、网络中断有降级提示的最小可行金融交互系统。它专为 Java 初学者设计但内核已暗含企业级开发的影子连接池管理、DAO 分层、Swing EDT 安全更新、SQL 注入防御雏形。如果你正卡在“学完 JDBC 不知怎么和界面联动”或“能写控制台 ATM 却不敢碰图形界面”这个项目就是你的第一块真实砖——它不炫技但每行代码都在回答一个工程师每天要面对的问题数据怎么安全地从数据库流到按钮上又怎么把用户的点击稳稳落回磁盘2. 从零搭建环境JDK 17 MySQL 8.0 c3p0 连接池为什么不用 HikariCP 或 Spring Boot提示本项目刻意避开 Spring 生态用最原始的 Java 技术栈还原“手搓系统”的完整链路。这不是倒退而是为了让你看清每一层的职责边界——Swing 负责渲染JDBC 负责通信c3p0 负责复用而你负责把它们拧成一股绳。2.1 JDK 与 IDE 选型为什么坚持用 JDK 17 而非 JDK 21本项目基于JDK 17LTS开发原因很实际Swing 在 JDK 17 中仍保持完整 API 兼容性而 JDK 21 已标记java.desktop模块为“待移除”JEP 437部分JXTable等第三方 Swing 扩展可能失效javac --release 17可确保编译出的字节码在大多数生产环境尤其教育机房、老旧服务器稳定运行Maven 依赖中mysql-connector-java:8.0.33与 JDK 17 的 TLS 1.2 默认配置完全匹配避免 SSL 握手失败这类玄学问题。# 验证 JDK 版本必须输出 17.x java -version # 输出应类似 # java version 17.0.1 2021-10-19 LTS # Java(TM) SE Runtime Environment (build 17.0.112-LTS-39)注意若你本地是 JDK 21请不要强行降级。改为在pom.xml中显式指定maven.compiler.release17/maven.compiler.releaseMaven 会自动启用交叉编译生成兼容 JDK 17 的 class 文件。2.2 MySQL 8.0 安装与初始化绕过caching_sha2_password认证陷阱MySQL 8.0 默认使用caching_sha2_password插件而mysql-connector-java:8.0.33需要显式启用 SSL 或切换认证方式否则报错Public Key Retrieval is not allowed或Client does not support authentication protocol requested by server。正确做法两步到位启动 MySQL 服务后用 root 登录执行-- 切换 root 用户认证方式关键 ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY your_secure_password; FLUSH PRIVILEGES;创建专用数据库与用户非 root符合最小权限原则-- 创建数据库字符集必须 utf8mb4支持 emoji 和中文 CREATE DATABASE atm_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 创建应用用户仅对 atm_system 库有读写权限 CREATE USER atm_userlocalhost IDENTIFIED BY atm_pass_2024; GRANT SELECT, INSERT, UPDATE ON atm_system.* TO atm_userlocalhost; FLUSH PRIVILEGES;提示utf8mb4是硬性要求。若用旧版utf8实际是 utf8mb3当用户姓名含生僻字如“䶮”、“堃”时插入会静默截断后续查不到记录——这是血泪经验。2.3 c3p0 连接池集成为什么不用更轻量的 HikariCP本项目选用c3p0:0.9.5.5最新稳定版而非更热门的 HikariCP理由明确c3p0 对Connection的close()方法做了智能代理即使开发者忘记关闭 ResultSet/Statement池也能自动回收资源对新手更友好其ComboPooledDataSource配置项命名直白如maxPoolSize,checkoutTimeout无需查文档猜含义与 Swing 的 EDTEvent Dispatch Thread配合更稳定HikariCP 的异步健康检查偶尔会触发 EDT 外线程更新 UI导致IllegalStateException。pom.xml关键依赖dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdcom.mchange/groupId artifactIdc3p0/artifactId version0.9.5.5/version /dependency注意mysql-connector-java:8.0.33必须与 MySQL 8.0.30 匹配。若用 8.0.28 以下版本需降级驱动至8.0.26否则Server returns invalid timezone错误频发。3. 数据库设计与 SQL 文件解析一张accounts表如何承载取款、锁卡、流水三重逻辑项目附带的atm_schema.sql不是简单建表脚本而是按金融系统规范设计的状态机模型。我们逐字段拆解其业务含义3.1 核心表结构accounts与transactions的强约束关系-- accounts 表持卡人主数据非银行卡号是系统内唯一 account_id CREATE TABLE accounts ( id BIGINT PRIMARY KEY AUTO_INCREMENT, account_number VARCHAR(19) NOT NULL UNIQUE COMMENT 银行卡号格式6228 4800 0000 0000 000, pin_hash CHAR(64) NOT NULL COMMENT PIN 密码 SHA-256 哈希值绝不存明文, balance DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 当前余额单位元, status ENUM(ACTIVE, LOCKED, CLOSED) NOT NULL DEFAULT ACTIVE COMMENT 账户状态, failed_login_count TINYINT NOT NULL DEFAULT 0 COMMENT 连续输错 PIN 次数≥3 则 LOCKED, last_login_time DATETIME NULL COMMENT 最后成功登录时间, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- transactions 表所有资金变动流水取款、存款、转账均在此记录 CREATE TABLE transactions ( id BIGINT PRIMARY KEY AUTO_INCREMENT, account_id BIGINT NOT NULL, type ENUM(WITHDRAWAL, DEPOSIT, TRANSFER_IN, TRANSFER_OUT) NOT NULL, amount DECIMAL(12,2) NOT NULL, balance_after DECIMAL(12,2) NOT NULL COMMENT 交易后账户余额, description VARCHAR(255) NULL COMMENT 如“ATM取款-北京西站”, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;关键设计点pin_hash字段强制使用 SHA-256 哈希非 MD5且无 salt—— 因本项目为教学用途salt 存储需额外字段会增加初学者理解成本生产环境必须加 salt 并用 PBKDF2。status与failed_login_count联动实现锁卡逻辑每次验密失败failed_login_count成功则清零statusLOCKED时直接拒绝所有操作。balance_after字段冗余存储避免查询余额时需聚合transactions表提升响应速度ATM 交互要求 800ms。3.2 初始化测试数据为什么预置 3 个账户而非 1 个atm_data.sql中预置了 3 个典型测试账户account_numberPIN明文初始余额用途说明62284800000000000001234¥5000.00正常用户用于取款/查询62284800000000000015678¥100.00余额不足用户测试取款失败62284800000000000029012¥0.00零余额用户测试锁卡逻辑提示导入时务必用SOURCE命令非粘贴执行确保事务完整性USE atm_system; SOURCE /path/to/atm_data.sql;3.3 一条取款 SQL 的完整生命周期从 Swing 按钮到磁盘落盘以点击“取款 ¥200”为例后端执行的原子事务如下START TRANSACTION; -- 1. 检查余额是否充足SELECT ... FOR UPDATE 锁定该行 SELECT balance, status, failed_login_count FROM accounts WHERE account_number 6228480000000000000 FOR UPDATE; -- 2. 若余额 ≥200 且 statusACTIVE执行扣减 UPDATE accounts SET balance balance - 200.00, updated_at NOW() WHERE account_number 6228480000000000000; -- 3. 记录流水balance_after 查询到的原 balance - 200 INSERT INTO transactions (account_id, type, amount, balance_after, description) VALUES (1, WITHDRAWAL, 200.00, 4800.00, ATM取款-北京西站); COMMIT;关键细节SELECT ... FOR UPDATE是灵魂。若不用此语句并发取款时可能出现超支两个线程同时读到 ¥5000各自扣 ¥200最终余额 ¥4600 而非 ¥4600。这是新手最容易翻车的点。4. Swing 界面与事件驱动如何让“取款”按钮既响应快又不阻塞 UISwing 是单线程模型所有 UI 更新必须在Event Dispatch Thread (EDT)中执行。但数据库操作是耗时 IO若直接在按钮ActionListener中执行withdraw()UI 会卡死数秒——用户看到的是“按钮按下去没反应”而非“正在处理”。4.1 SwingWorker 实现后台任务取款操作的正确打开方式// ATMFrame.java 中取款按钮的监听器 withdrawButton.addActionListener(e - { String amountStr amountField.getText().trim(); if (!amountStr.matches(\\d)) { JOptionPane.showMessageDialog(this, 请输入有效金额, 输入错误, JOptionPane.ERROR_MESSAGE); return; } BigDecimal amount new BigDecimal(amountStr); // 启动后台任务避免阻塞 EDT new SwingWorkerBoolean, String() { Override protected Boolean doInBackground() throws Exception { try { // 1. 调用 DAO 层执行取款耗时操作 boolean success accountService.withdraw(accountNumber, amount); if (success) { publish(取款成功); // 发布中间状态可选 } else { publish(取款失败请检查余额或网络); } return success; } catch (SQLException ex) { publish(数据库错误 ex.getMessage()); return false; } } Override protected void done() { try { boolean result get(); // 获取 doInBackground 返回值 if (result) { // 2. 更新 UI余额标签、按钮状态 updateBalanceLabel(); JOptionPane.showMessageDialog(ATMFrame.this, 取款成功, 操作完成, JOptionPane.INFORMATION_MESSAGE); } } catch (Exception ex) { JOptionPane.showMessageDialog(ATMFrame.this, 操作异常 ex.getMessage(), 错误, JOptionPane.ERROR_MESSAGE); } } Override protected void process(ListString chunks) { // 可在此更新进度条或状态栏本项目未实现留作扩展 } }.execute(); // 立即启动 });逻辑说明doInBackground()在后台线程执行可安全调用accountService.withdraw()done()在 EDT 中回调可直接调用updateBalanceLabel()更新 JLabelpublish()/process()用于传递中间状态如“正在验证 PIN…”本项目简化未用但框架已预留。4.2 状态机驱动界面ATM 的 5 个核心界面如何流转整个 ATM 系统由ATMState枚举驱动避免if-else堆砌public enum ATMState { WAITING_FOR_CARD, // 插卡界面显示“请插入银行卡” ENTERING_PIN, // 输入 PIN 界面密码框*号掩码 MAIN_MENU, // 主菜单取款/查询/退卡按钮 WITHDRAWAL_AMOUNT, // 取款金额输入数字键盘 TRANSACTION_COMPLETE // 交易完成显示余额、打印凭条提示 }ATMFrame中通过setState(ATMState state)统一控制组件可见性private void setState(ATMState newState) { currentState newState; // 隐藏所有面板 cardPanel.setVisible(false); pinPanel.setVisible(false); menuPanel.setVisible(false); amountPanel.setVisible(false); completePanel.setVisible(false); // 仅显示当前状态对应面板 switch (newState) { case WAITING_FOR_CARD: cardPanel.setVisible(true); break; case ENTERING_PIN: pinPanel.setVisible(true); pinField.setText(); // 清空上次输入 break; case MAIN_MENU: menuPanel.setVisible(true); break; case WITHDRAWAL_AMOUNT: amountPanel.setVisible(true); amountField.setText(); break; case TRANSACTION_COMPLETE: completePanel.setVisible(true); break; } }优势状态变更逻辑集中新增“修改 PIN”功能只需加一个CHANGE_PIN状态无需改遍所有setVisible()调用。5. 避坑指南那些让新手调试到凌晨三点的“幽灵 Bug”5.1 现象点击“取款”按钮后UI 卡死 5 秒然后弹出“取款成功”但数据库余额没变原因accountService.withdraw()方法中Connection未正确提交事务或Connection被意外关闭。常见于DAO 方法中conn.commit()被注释掉try-with-resources自动关闭了Connection但commit()写在try块外c3p0 配置autoCommittrue被误设为false且代码未手动commit()。解决检查AccountDAO.java中withdraw()方法末尾是否有conn.commit()确保Connection生命周期覆盖整个事务public boolean withdraw(String accountNumber, BigDecimal amount) throws SQLException { Connection conn null; try { conn dataSource.getConnection(); // 从 c3p0 获取 conn.setAutoCommit(false); // 关闭自动提交 // ... 执行 SELECT FOR UPDATE, UPDATE, INSERT ... conn.commit(); // 必须在此处提交 return true; } catch (SQLException e) { if (conn ! null) conn.rollback(); // 出错回滚 throw e; } finally { if (conn ! null) conn.close(); // 归还连接池 } }5.2 现象输入正确 PIN 后界面跳转到主菜单但余额显示为¥0.00实际数据库是¥5000.00原因Swing 组件更新未在 EDT 中执行或JLabel文本未刷新。常见于在doInBackground()中直接调用balanceLabel.setText(...)updateBalanceLabel()方法中balanceLabel.setText()被包裹在SwingUtilities.invokeLater()外数据库查询返回BigDecimal但setText()传入了toString()科学计数法显示5E3。解决确保所有 UI 更新在done()或SwingUtilities.invokeLater()中执行updateBalanceLabel()必须格式化数字private void updateBalanceLabel() { BigDecimal balance accountService.getBalance(accountNumber); // 使用 DecimalFormat 避免科学计数法 DecimalFormat df new DecimalFormat(¥#,##0.00); balanceLabel.setText(df.format(balance)); }5.3 现象连续输错 3 次 PIN 后账户状态变为LOCKED但再次输入正确 PIN 仍提示“账户已锁定”原因accounts表中status字段更新后accountService.getBalance()等方法仍从缓存或旧连接读取脏数据。根本原因是c3p0 连接池未配置forceUseNamedDrivertrue导致连接复用时事务隔离级别失效SELECT语句未加FOR UPDATE读取的是快照MVCC未感知到其他事务的UPDATE。解决在ComboPooledDataSource配置中强制设置事务隔离级别cpds.setTransactionIsolation(TRANSACTION_SERIALIZABLE); // 最高隔离级别所有读取账户状态的 SQL如验密、查余额必须加FOR UPDATESELECT * FROM accounts WHERE account_number ? FOR UPDATE;5.4 现象程序启动时报错java.lang.NoClassDefFoundError: com/mchange/v2/c3p0/ComboPooledDataSource原因Maven 依赖未正确打包进 JAR或CLASSPATH未包含 c3p0 JAR。常见于使用mvn compile编译但未用mvn package打包依赖IDE 运行配置中未勾选 “Include dependencies with JAR”。解决用 Maven Shade Plugin 打包可执行 JARplugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-shade-plugin/artifactId version3.4.1/version executions execution phasepackage/phase goalsgoalshade/goal/goals configuration transformers transformer implementationorg.apache.maven.plugins.shade.resource.ManifestResourceTransformer mainClasscom.example.atm.ATMApplication/mainClass /transformer /transformers /configuration /execution /executions /plugin打包后执行java -jar target/atm-system-1.0-SNAPSHOT.jar6. 进阶技巧用 SQL 日志和 Swing 调试器定位“看不见”的问题6.1 开启 c3p0 SQL 日志让每条执行的 SQL 无所遁形c3p0 内置日志功能无需 Log4j只需在c3p0-config.xml中添加c3p0-config default-config !-- 其他配置... -- property namedebugUnreturnedConnectionStackTracestrue/property property nameunreturnedConnectionTimeout30/property !-- 关键开启 SQL 执行日志 -- property namestatementCacheNumDeferredCloseThreads1/property property namemaxStatements50/property property namemaxStatementsPerConnection5/property /default-config /c3p0-config然后在 JVM 启动参数中加入-Dc3p0.debugtrue -Dcom.mchange.v2.log.MLogcom.mchange.v2.log.FallbackMLog -Dcom.mchange.v2.log.FallbackMLog.DEFAULT_CUTOFF_LEVELALL运行后控制台将输出[DEBUG] com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeQuery(NewProxyPreparedStatement.java:112) - SELECT * FROM accounts WHERE account_number ? FOR UPDATE [DEBUG] com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:107) - UPDATE accounts SET balance balance - ? WHERE account_number ?价值当“取款后余额没变”时先看日志里有没有UPDATE语句——没有则逻辑未走到 DAO有但没COMMIT则查事务配置有COMMIT但数据库没变则是WHERE条件不匹配如account_number多空格。6.2 Swing EDT 监控识别“假死”还是真卡顿Swing 提供EventQueue监控工具插入以下代码到ATMApplication.main()开头EventQueue.invokeLater(() - { // 启动 EDT 监控线程每 2 秒检测一次 new Thread(() - { while (true) { try { Thread.sleep(2000); long eventTime EventQueue.getCurrentEventTime(); System.out.println([EDT Monitor] Last event time: eventTime); // 若 eventTime 长期不变说明 EDT 被阻塞 } catch (InterruptedException e) { break; } } }).start(); });场景当按钮点击无响应但控制台仍在打印日志此时看Last event time是否变化——不变则 EDT 被某个while(true)或Thread.sleep()卡住变化则问题在后台线程如数据库连接超时。6.3 一个真实技巧用JOptionPane替代System.out.println做快速断点在复杂状态流转中如“插卡→验密→主菜单→取款→完成”System.out.println(now in MAIN_MENU)易被淹没。改用JOptionPane// 在 setState() 方法中插入 JOptionPane.showMessageDialog(null, State changed to: newState, DEBUG, JOptionPane.INFORMATION_MESSAGE);优势弹窗强制聚焦不会被日志冲刷关闭弹窗即继续执行比System.in.read()更轻量适合演示时向同学快速展示当前状态。我带过 7 届 Java 课程学生交上来的第一个“能跑通的桌面程序”80% 是这个 ATM 系统。它不追求技术前沿但把 Java 工程师每天要面对的底层契约——线程安全、事务边界、异常传播、资源回收——全摊开在你眼前。当你亲手修复第 3 个Connection closed错误你会突然明白为什么企业代码里满屏都是try-catch-finally当你第一次看到SELECT ... FOR UPDATE锁住一行数据你会懂“并发”不是概念而是行锁。这项目的价值不在 ZIP 包里的 12 个 Java 文件而在你调试时删掉的那 200 行废弃代码里。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
SemIf 原生 MLX CLI 实战:Apple Silicon 终端录制重放、复现命令与决策输出解读 【免费下载链接】SemIf Semantic ifs from open models, on a 3090 at home. Independent; not affiliated with Jev or TypeSafe. 项目地址: https://gitcode.com/gh_mirrors/op/SemIf 点击查看 免费下载 SemIf(Semantic ifs from open models… · 2026/9/23 13:17:06
基于深度强化学习的云工作流调度:PPO建模、训练与避坑指南 简介:这套基于Python的源码将深度强化学习应用于云工作流调度,面向计算机相关专业毕业设计、课程设计及智能调度算法开发者,覆盖数据预处理、模型构建、训练和评估的完整流程。代码附详细注释与项目说明,能帮助理解强化学习算法如… · 2026/9/23 13:17:05
15KW永磁同步电动机设计:电磁计算、结构设计与Simulink闭环验证 简介:这份资源是面向电机设计初学者、电气工程专业学生及毕业设计选题者的15KW永磁同步电动机设计文档,围绕电动汽车驱动电机的高效率、宽调速范围、高过载能力与结构紧凑等需求展开。内容涵盖永磁同步电机的整体结构与分类、电磁负荷计算、转子结构选型… · 2026/9/23 14:06:53
Koopman算子加速非线性MPC:从数据驱动建模到QP求解的工程实践 简介:这份资源围绕Koopman算子与模型预测控制(MPC)的结合展开,面向具备一定控制理论基础、希望深入非线性系统控制的研究生、工程师及科研人员。其核心思路是在高维提升空间中借助Koopman算子的线性特性来刻画并控制非线性动态系统… · 2026/9/23 14:06:52
Word 2007工具栏消失怎么办?从最小化到注册表重置的完整恢复指南 先问你一个情景:正用Word 2007排版,突然发现上面那排“开始、插入、页面布局”的选项卡只剩下光秃秃的名字,或者干脆连名字都找不到,整个工具栏区域凭空消失了;再严重一点,连左上角那些“保存、撤销”的小图… · 2026/9/23 14:06:46
中医五味手写实现,面试必问的5个坑点全解析 中医五味手写实现,面试必问的5个坑点全解析 复制来的中医五味算法代码,跑起来全是乱码,报错信息根本看不懂,这种“复制即崩”的绝望感,相信不少刚入行的同学都经历过。更扎心的是,当面试官甩出一句“请手写一个五味相生相克的状态机”时,你只能尴尬地… · 2026/9/23 14:06:46
gs-quant FXBinary 快速指南:从构造到定价,吃透外汇数字期权的关键做法 gs-quant FXBinary 快速指南:从构造到定价,吃透外汇数字期权的关键做法 【免费下载链接】gs-quant Python toolkit for quantitative finance 项目地址: https://gitcode.com/GitHub_Trending/gs/gs-quant
你想用 gs-quant 搭一笔 EURUSD 三个月期… · 2026/9/23 14:06:40
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29