基于Spring Boot的银行账户转账系统:事务、锁与幂等性实战
简介这是一份面向Java初学者与GUI编程练习者的模拟银行账户转账系统源码围绕A、B两个初始余额均为1000元的账户展开实现双方随机互相转账、金额不得超过账户余额、余额为零即无法交易等核心业务规则适合用于理解多线程协作与图形界面事件驱动的综合小项目。压缩包内共2个文件均为java源码整体约2KB分别承担窗口界面搭建与转账线程逻辑结构精简、便于直接阅读与二次修改。资源已有4107人学习下载说明其作为课堂练习或自学案例具备一定参考价值。读者可从中获得完整的双账户随机转账实现思路、按钮状态切换交易开始与结束交易的交互设计、交易记录写入ab.txt的持久化处理方式以及余额校验与线程调度的排错参考适合作为Java课程设计或GUI入门练手素材。1. 从一次转账失败说起为什么需要一套完整的银行账户转账系统转账这件事看起来只是「A 扣钱、B 加钱」两步操作但真正动手写过的开发者都知道这里面的坑远比想象中多。某高校的课程设计答辩现场A 同学演示转账功能时第一次点击「确认转账」页面卡住第二次点击后账户余额直接扣了两次——这就是典型的并发问题和幂等性缺失。模拟银行账户转账系统要解决的核心问题就是在单机或局域网环境下完整复现真实银行转账的业务闭环账户管理、身份验证、转账事务、流水记录、并发控制。这套方案适合正在做 Java Web 课程设计、数据库课设或分布式入门项目的学生也适合想理解事务与锁机制的初中级开发者。它不追求高并发压测指标而是把「一笔转账从发起到落账」的每个环节都拆开讲透让你能照着搭出一套能演示、能答辩、能经得起追问的系统。2. 需求分析与技术选型六个核心模块的职责边界2.1 功能性需求拆解一套能拿得出手的转账系统功能需求通常要覆盖六个模块。用户模块负责注册、登录、密码修改登录态用 Session 或 Token 维持账户模块管理每个用户的银行卡号、余额、状态正常/冻结转账模块是核心处理转出、转入、金额校验、余额扣减与增加流水模块记录每一笔交易的完整信息包括交易号、双方账号、金额、时间戳、状态并发控制模块保证同一账户在并发转账时不会出现余额错乱异常处理模块负责转账失败后的回滚和补偿。我一般会把需求写成一张表方便后续对照验收模块核心功能关键约束用户模块注册、登录、登出密码加密存储登录态超时账户模块开户、查询余额、冻结账号唯一余额非负转账模块发起转账、确认到账金额0转出方余额充足流水模块记录、查询交易明细每笔交易有唯一流水号并发控制锁账户、事务隔离同一账户串行扣款异常处理回滚、重试、告警失败交易不产生脏数据这张表看起来简单但每一条约束背后都对应着具体的实现决策。比如「余额非负」这条如果只在应用层用 if 判断并发场景下必然翻车必须落到数据库层面用约束或行锁兜底。2.2 技术选型与理由技术栈的选择直接决定后续开发效率和答辩时的可解释性。对于课程设计级别的转账系统我推荐 Spring Boot MyBatis MySQL Redis 的组合。Spring Boot 负责快速搭建 Web 层和依赖注入MyBatis 做数据持久化SQL 可控性强方便在答辩时展示事务和锁的细节MySQL 用 InnoDB 引擎天然支持行级锁和事务Redis 用来做分布式锁或缓存账户热点数据虽然单机场景下不是必须但加上它能体现你对并发控制的思考。前端不用太复杂Thymeleaf 或 Vue 都行重点是后端逻辑要清晰。如果你时间紧直接用 Postman 调接口演示也完全可以答辩老师更关心的是事务和并发怎么处理的而不是页面好不好看。提示不要为了炫技引入消息队列或微服务课程设计的核心是把转账事务讲清楚引入过多中间件反而增加调试成本。2.3 数据库表设计表结构是转账系统的地基设计不好后面全是坑。核心表就三张用户表、账户表、交易流水表。-- 用户表存登录信息 CREATE TABLE t_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(128) NOT NULL COMMENT 加密后的密码, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 账户表存余额和状态一个用户可对应多个账户 CREATE TABLE t_account ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT 所属用户, account_no VARCHAR(32) NOT NULL UNIQUE COMMENT 银行卡号, balance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT 余额精确到分, status TINYINT NOT NULL DEFAULT 1 COMMENT 1正常 0冻结, version INT NOT NULL DEFAULT 0 COMMENT 乐观锁版本号, INDEX idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 交易流水表每笔转账一条记录 CREATE TABLE t_transaction ( id BIGINT PRIMARY KEY AUTO_INCREMENT, trade_no VARCHAR(64) NOT NULL UNIQUE COMMENT 交易流水号, from_account_no VARCHAR(32) NOT NULL, to_account_no VARCHAR(32) NOT NULL, amount DECIMAL(18,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0处理中 1成功 2失败, remark VARCHAR(255) DEFAULT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_from_account (from_account_no), INDEX idx_to_account (to_account_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;余额字段用 DECIMAL 而不是 FLOAT这是血泪经验——浮点数在金额计算时会出现精度丢失0.1 0.2 不等于 0.3 的问题在转账场景里是致命的。version 字段是为乐观锁准备的后面讲并发控制时会用到。交易流水号 trade_no 必须唯一通常用「时间戳 随机数 用户 ID」生成保证可追溯。3. 转账核心逻辑实现事务、锁与幂等性的三重保障3.1 基于 Spring 声明式事务的转账服务转账的本质是一个数据库事务扣转出方余额、加转入方余额、写流水记录三步要么全成功要么全失败。Spring 的 Transactional 注解能帮我们管理事务边界但有几个细节必须注意。Service public class TransferService { Autowired private AccountMapper accountMapper; Autowired private TransactionMapper transactionMapper; /** * 转账核心方法 * param fromAccountNo 转出账号 * param toAccountNo 转入账号 * param amount 转账金额单位元 * param tradeNo 外部传入的交易号用于幂等 */ Transactional(rollbackFor Exception.class) public void transfer(String fromAccountNo, String toAccountNo, BigDecimal amount, String tradeNo) { // 1. 幂等校验同一 tradeNo 只处理一次 Transaction exist transactionMapper.selectByTradeNo(tradeNo); if (exist ! null) { throw new BizException(重复交易请勿重复提交); } // 2. 校验金额 if (amount null || amount.compareTo(BigDecimal.ZERO) 0) { throw new BizException(转账金额必须大于0); } // 3. 锁定转出账户悲观锁防止并发扣款 Account from accountMapper.selectForUpdate(fromAccountNo); if (from null || from.getStatus() ! 1) { throw new BizException(转出账户不存在或已冻结); } if (from.getBalance().compareTo(amount) 0) { throw new BizException(余额不足); } // 4. 锁定转入账户 Account to accountMapper.selectForUpdate(toAccountNo); if (to null || to.getStatus() ! 1) { throw new BizException(转入账户不存在或已冻结); } // 5. 扣减与增加余额 accountMapper.updateBalance(fromAccountNo, amount.negate()); accountMapper.updateBalance(toAccountNo, amount); // 6. 写入流水 Transaction record new Transaction(); record.setTradeNo(tradeNo); record.setFromAccountNo(fromAccountNo); record.setToAccountNo(toAccountNo); record.setAmount(amount); record.setStatus(1); transactionMapper.insert(record); } }这段代码有几个关键点值得展开。第一Transactional(rollbackFor Exception.class)里的 rollbackFor 必须显式指定因为 Spring 默认只对 RuntimeException 回滚如果抛出的是受检异常事务不会回滚这是很多人踩过的坑。第二selectForUpdate对应 SQL 里的SELECT ... FOR UPDATE它会锁定行记录其他事务在锁释放前无法修改该行从而保证同一账户的扣款是串行的。第三幂等校验放在最前面用 tradeNo 查流水表如果已存在就直接拒绝防止用户重复点击导致重复扣款。对应的 Mapper XML 关键 SQL!-- 悲观锁查询账户 -- select idselectForUpdate resultTypeAccount SELECT * FROM t_account WHERE account_no #{accountNo} FOR UPDATE /select !-- 更新余额正数为加负数为减 -- update idupdateBalance UPDATE t_account SET balance balance #{delta} WHERE account_no #{accountNo} /updateupdateBalance用balance balance delta而不是先查再算再写这样即使有并发数据库层面的原子性也能保证不会算错。delta 为负数就是扣款正数就是入账。3.2 乐观锁与悲观锁的选择上面用的是悲观锁适合并发冲突较多的场景。如果你的系统读多写少可以考虑乐观锁用 version 字段实现。-- 乐观锁更新每次更新检查 version 是否变化 UPDATE t_account SET balance balance - #{amount}, version version 1 WHERE account_no #{accountNo} AND version #{version} AND balance #{amount}执行后检查受影响行数如果为 0 说明 version 被其他事务改过需要重试或报错。乐观锁的好处是不加锁、性能好坏处是冲突时需要重试实现复杂度略高。我一般建议课程设计用悲观锁逻辑直观答辩时也好解释如果老师追问高并发优化再补充乐观锁方案作为对比。3.3 幂等性设计的三种思路幂等性是转账系统的后悔药。用户网络卡顿重复提交、前端按钮没置灰、接口被恶意重放都会导致同一笔转账执行多次。常见的幂等方案有三种一是用唯一交易号做数据库唯一索引插入冲突就拒绝二是用 Redis 记录 tradeNo处理前先查缓存三是状态机控制交易状态从「处理中」到「成功」只允许流转一次。我通常会把第一种和第三种结合tradeNo 在流水表建唯一索引同时交易状态严格按 0→1 或 0→2 流转不允许从 1 再回到 0。这样即使有并发请求数据库唯一约束会兜底业务层状态机再挡一道。4. 并发场景下的避坑与排查五条最容易翻车的点4.1 余额扣成负数现象两个线程同时转账都查到余额 100各自扣 80最终余额变成 -60。原因没有加锁两个事务读到了相同的初始余额。解决用SELECT ... FOR UPDATE锁定账户行或者在 UPDATE 语句里加AND balance amount条件让数据库层面保证不会扣成负数。4.2 事务不回滚现象转账过程中抛出异常但转出方余额已经扣了转入方没收到。原因异常类型是受检异常Spring 默认不回滚或者方法内部 try-catch 把异常吞了。解决Transactional 显式指定 rollbackFor Exception.class并且不要在事务方法内部捕获异常后不抛出。4.3 死锁现象A 向 B 转账的同时 B 向 A 转账两个事务互相等待对方释放锁数据库报 Deadlock found。原因两个事务加锁顺序相反。解决统一按账户 ID 或账号字符串排序后依次加锁保证所有事务的加锁顺序一致。4.4 重复扣款现象用户点击两次提交扣了两次钱。原因接口没有幂等控制。解决前端按钮点击后置灰后端用 tradeNo 唯一索引 状态机双重校验重复请求直接返回「交易已处理」。4.5 流水与余额不一致现象流水表记录转账成功但账户余额没变。原因写流水和更新余额不在同一个事务里或者事务传播行为配置错误。解决确保扣款、入账、写流水在同一个 Transactional 方法内传播行为用默认的 REQUIRED。注意排查这类问题时先看数据库的隔离级别和锁等待超时日志再看应用日志里事务的提交回滚记录最后用单元测试模拟并发复现。不要一上来就改代码先定位清楚是锁的问题还是事务边界的问题。5. 验收与答辩怎么证明你的转账系统真的可靠验收不是把功能点一遍就完事而是要拿出证据证明系统在异常和并发场景下依然正确。我一般会准备三组测试功能测试、并发测试、异常测试。功能测试用 Postman 或 JUnit 跑通正常转账、余额不足、账户冻结、重复提交四个用例每个用例记录请求参数、预期结果、实际结果。并发测试用 JMeter 或自己写多线程脚本模拟 50 个线程同时对同一账户转账跑完后检查最终余额是否等于初始余额减去所有成功交易金额之和。异常测试故意在转账中途抛异常验证事务是否回滚、流水是否一致。答辩时老师最爱问的三个问题你的转账怎么保证不超卖事务边界在哪里幂等怎么做的提前把答案准备好最好能现场打开数据库表展示数据和流水。如果老师追问「如果数据库宕机了怎么办」可以回答单机场景下依赖数据库的 redo log 保证持久性如果要做高可用需要主从复制和故障转移但这超出课程设计范围可以作为扩展方向提一句。验收项测试方法通过标准正常转账接口调用双方余额正确变化流水生成余额不足构造余额小于转账额返回错误余额不变并发扣款50 线程同时转账最终余额 初始 - 成功总额重复提交同一 tradeNo 调两次第二次返回重复交易异常回滚中途抛异常双方余额和流水均无变化从那以后我每次做涉及金额或状态流转的项目都会强制走一遍「并发 异常 幂等」这三板斧哪怕需求再简单也不跳过。希望这套结构能帮到你把课程设计或方案交付稳稳拿下来。本文还有配套的精品资源点击获取

相关新闻

银河麒麟V10网卡驱动编译签名三重校验指南

银河麒麟V10网卡驱动编译签名三重校验指南

简介:本资源是专为银河麒麟V10操作系统适配优化的e1000e与RTL8125网卡驱动源码包,面向国产化信创环境下的Linux内核开发者、系统集成工程师及政企IT运维人员,解决在麒麟V10上因内核版本差异导致的Intel和Realtek主流网卡无法编译加载的核心兼…

2026/10/17 16:15:22 阅读更多 →
ActiveMQ C#入门实战:Windows下STOMP协议连接与消息持久化

ActiveMQ C#入门实战:Windows下STOMP协议连接与消息持久化

简介:本资源是一套面向C#初学者与中间件开发者的ActiveMQ消息队列实战Demo,聚焦WinForm桌面应用中消息的同步发送与异步接收场景,帮助开发者快速理解JMS规范在.NET环境下的落地实现。压缩包共36个文件,含19个核心C#源码&#xff0…

2026/10/16 6:42:56 阅读更多 →
大话2.0.78版LUA脚本引擎:游戏内嵌式自动化接口解析

大话2.0.78版LUA脚本引擎:游戏内嵌式自动化接口解析

简介:本资源是专为《大话西游2》2.0.78版本定制的LUA 4脚本引擎源码实现,面向游戏MOD开发者、客户端逆向研究者及C/Lua交叉编程学习者,解决旧版客户端脚本扩展能力弱、缺乏可编译工程支持等实际开发痛点。压缩包共123个文件,涵盖3…

2026/10/16 6:42:56 阅读更多 →

最新新闻

MySQL学生成绩管理系统数据库设计:需求分析、建表、视图与存储过程完整方案

MySQL学生成绩管理系统数据库设计:需求分析、建表、视图与存储过程完整方案

简介:这份资源是北邮研一数据库课程大作业的完整详解文档,面向正在修读数据库系统课程、需要完成课程设计的研究生及高年级本科生。内容围绕学生成绩管理系统展开,覆盖需求分析、数据库设计、ER图绘制、逻辑结构设计与建表程序等完整流程&…

2026/10/17 16:15:27 阅读更多 →
Unity运行时导入3D模型:基于TriLib源码的模型加载与替换实践

Unity运行时导入3D模型:基于TriLib源码的模型加载与替换实践

简介:这套基于 Unity3D C# 与 TriLib 插件实现的运行时 3D 模型导入加载源码工程,面向需要为游戏或应用添加实时模型替换、关卡/场景编辑器以及 AR/VR 可视化等功能的 Unity 开发者。压缩包共 879 个文件,约 26.37MB,以 C# 脚本、…

2026/10/17 16:15:27 阅读更多 →
数据库关系代数与SQL实战入门:从课本习题到可执行查询

数据库关系代数与SQL实战入门:从课本习题到可执行查询

简介:本资源是《数据库系统概论(第5版)》王珊、萨师煊主编教材的配套课后习题详细答案,面向高校计算机、软件工程、信息管理等专业本科生及数据库初学者,用于巩固关系模型、关系代数、SQL语言等核心理论知识&#xff0…

2026/10/17 16:15:27 阅读更多 →
Unity3D数字孪生案例:从模型导入到数据驱动的完整落地路径

Unity3D数字孪生案例:从模型导入到数据驱动的完整落地路径

简介:面向数字孪生初学者与全栈开发爱好者的一套 Unity3d(WebGL)小案例代码包,聚焦硬件终端、webGL、Web端与服务端之间的数据交互链路,帮助解决 Unity3d 建模在网页端运行及 IoT 数据回流三维场景的问题。压缩包共3个…

2026/10/17 16:15:27 阅读更多 →
WordPress故障排查实战:从白屏、数据库到迁移陷阱一次说清

WordPress故障排查实战:从白屏、数据库到迁移陷阱一次说清

说起WordPress问题,我这几年遇到最多的不是某个单一故障,而是一堆"莫名其妙"的现象:前台白屏、后台登录死循环、文章保存按钮转圈、图片传不上去、网站被搜索引擎收录了一堆垃圾页面、搬家之后样式全丢。很多朋友的第一反应是"…

2026/10/17 16:15:27 阅读更多 →
Vim+GCC+库文件:打通Linux开发工具链的完整闭环

Vim+GCC+库文件:打通Linux开发工具链的完整闭环

我见过太多刚切到 Linux 的开发者,第一周就被劝退。倒不是 C 语言难,而是卡在了工作流上:在终端里不知道怎么快速改代码,写完程序不知道怎么编译,编译完了遇到各种链接报错又一头雾水。说白了,就是 Vim 操作…

2026/10/17 16:14:27 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →