ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java银行账目管理系统毕业设计:从源码到生产级实现

Java银行账目管理系统毕业设计:从源码到生产级实现 简介这份资源是面向高校计算机专业学生与Java初学者的一套银行账户管理系统毕业设计完整方案围绕Java企业级开发技术栈展开可用于课程设计、毕设参考或Spring Boot入门实战。压缩包共28个文件约270KB以10个java源码与10个class编译文件为核心另含3个db数据库脚本、project工程配置、classpath依赖描述及doc项目报告覆盖从代码到文档的完整交付内容。系统采用Spring Boot结合Spring Data JPA与MyBatis实现数据访问遵循MVC分层架构基于MySQL设计用户、账户与交易记录等数据表并引入Spring Security完成认证授权前端使用Thymeleaf配合Ajax提升交互体验同时包含统一异常处理、Log4j日志及单元与集成测试。目前已有80人学习适合需要完整赛题方案、可运行源码与设计文档的读者参考借鉴。1. 从一份银行账目管理系统源码说起毕业设计怎么做出生产味每年到了毕设季后台总有人问我同一个问题基于 Java 的银行帐目管理系统到底怎么才能不写成“玩具”我见过太多版本功能列表里写着开户、存款、取款、转账跑起来却连并发扣款都对不上数据库里余额能变成负数。这个标题背后其实是一套很典型的技术组合Java SE 或 Java Web 做业务层MySQL 存账目流水JDBC 或 MyBatis 做持久化Swing 或 Spring Boot 做界面和接口。它解决的核心问题不是“做个界面”而是把账户、交易、余额三者之间的数据一致性讲清楚。适合谁适合正在选毕业设计题目、手里只有 Java 基础和数据库概念、想拿一份能讲清楚设计思路的源代码和项目报告的人。下面我按自己带学生做这类系统的顺序把选型、建表、核心代码、避坑和进阶验证一次讲透。2. 银行账目管理系统的技术选型Swing 还是 Spring Boot2.1 两种主流架构的适用边界毕业设计里最常见的两条路一条是 Java Swing JDBC MySQL纯桌面端另一条是 Spring Boot MyBatis MySQL 前端页面B/S 结构。选哪条不取决于哪个“高级”而取决于你的项目报告想突出什么。Swing 方案的优势是启动快、依赖少、不需要配 Tomcat答辩时双击就能跑。缺点是界面代码和业务代码容易混在一起如果不在 Service 层做切分项目报告里的“分层设计”会显得很虚。我一般建议 Swing 方案至少分出 entity、dao、service、ui 四层这样报告里画架构图才有东西可写。Spring Boot 方案的优势是接口清晰、方便用 Postman 演示、能自然引入事务管理和连接池。缺点是环境配置对新手不友好JDK 版本、Maven 依赖、MySQL 驱动版本任何一个对不上启动就报错。如果你选这条项目报告里可以重点写 RESTful 接口设计和事务传播行为比单纯写“实现了转账”有深度得多。提示不要为了显得技术栈丰富而硬上微服务。银行账目管理系统的业务边界很清晰单体应用足够强行拆服务只会让答辩时被问得下不来台。2.2 数据库选 MySQL 的四个理由第一MySQL 免费且资料多毕设环境不会因为授权问题卡住。第二InnoDB 引擎支持事务这是账目系统的命门。第三自增主键和唯一索引能帮你挡住一部分重复提交。第四项目报告里写“基于 InnoDB 的行级锁实现并发控制”比写“用了数据库”要具体。建库时字符集用 utf8mb4排序规则用 utf8mb4_general_ci避免中文乱码。连接串里必须加useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue这三个参数是新手翻车重灾区后面避坑章节会细说。2.3 项目报告里必须出现的三张图很多人的项目报告读起来像流水账就是因为缺图。我建议至少放三张第一张是系统功能结构图把开户、存款、取款、转账、查询流水、销户六个模块画清楚第二张是数据库 E-R 图账户表和交易表的关系要标出一对多第三张是转账业务的时序图体现“校验余额→扣款→入账→写流水→提交事务”的顺序。这三张图一放报告的技术密度立刻不一样。3. 账户与流水表怎么建字段、索引和事务边界3.1 账户表 account 的字段设计账户表不要只存一个余额字段就完事。我一般会加版本号字段用于乐观锁加状态字段区分正常、冻结、销户加创建时间和更新时间方便排查。下面是我常用的建表语句CREATE TABLE account ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 账户主键, account_no VARCHAR(32) NOT NULL COMMENT 账号业务唯一, owner_name VARCHAR(64) NOT NULL COMMENT 户名, balance DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT 余额精确到分, status TINYINT NOT NULL DEFAULT 1 COMMENT 1正常 2冻结 3销户, version INT NOT NULL DEFAULT 0 COMMENT 乐观锁版本号, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_account_no (account_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT账户表;金额字段用 DECIMAL 而不是 DOUBLE这是血泪经验。DOUBLE 在做多次加减后会出现 0.30000000000000004 这种结果对账时能把你逼疯。version 字段配合UPDATE ... WHERE version ?实现乐观锁防止两个请求同时扣同一笔钱。3.2 交易流水表 transaction_log 的设计流水表只增不改每条记录对应一次资金变动。关键字段包括交易类型、金额、交易前余额、交易后余额、关联账号、交易时间。CREATE TABLE transaction_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, trade_no VARCHAR(40) NOT NULL COMMENT 交易流水号, account_no VARCHAR(32) NOT NULL COMMENT 本方账号, target_account_no VARCHAR(32) DEFAULT NULL COMMENT 对方账号转账时使用, trade_type TINYINT NOT NULL COMMENT 1存款 2取款 3转入 4转出, amount DECIMAL(18,2) NOT NULL COMMENT 交易金额, balance_before DECIMAL(18,2) NOT NULL COMMENT 交易前余额, balance_after DECIMAL(18,2) NOT NULL COMMENT 交易后余额, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_trade_no (trade_no), KEY idx_account_time (account_no, created_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT交易流水表;idx_account_time这个联合索引是给“查询某账号最近流水”用的没有它数据量一上来查询就慢。trade_no 用唯一索引防止重复提交产生两条流水。3.3 事务边界划在 Service 层事务不要开在 DAO 层也不要开在 Controller 层。DAO 层只管单条 SQLController 层只管参数校验和返回事务边界放在 Service 的转账方法上。用 Spring 的话就是Transactional(rollbackFor Exception.class)注意默认只回滚 RuntimeException所以必须显式写 rollbackFor。用原生 JDBC 的话把conn.setAutoCommit(false)放在方法开头commit 和 rollback 放在 try-catch-finally 里连接关闭前一定要恢复自动提交。4. 核心业务代码转账、并发扣款和流水记录4.1 转账 Service 的完整实现下面这段代码是 Spring Boot JdbcTemplate 的写法逻辑清晰适合放进项目报告。核心思路是先查转出账户校验余额和状态再查转入账户然后扣款、入账、写两条流水最后提交事务。Service public class TransferService { Autowired private JdbcTemplate jdbcTemplate; Transactional(rollbackFor Exception.class) public void transfer(String fromNo, String toNo, BigDecimal amount) { // 1. 参数基础校验 if (amount null || amount.compareTo(BigDecimal.ZERO) 0) { throw new BizException(转账金额必须大于0); } if (fromNo.equals(toNo)) { throw new BizException(不能给自己转账); } // 2. 查询转出账户加行锁防止并发扣款 Account from jdbcTemplate.queryForObject( SELECT * FROM account WHERE account_no ? FOR UPDATE, new BeanPropertyRowMapper(Account.class), fromNo); if (from null || from.getStatus() ! 1) { throw new BizException(转出账户不存在或状态异常); } if (from.getBalance().compareTo(amount) 0) { throw new BizException(余额不足); } // 3. 查询转入账户 Account to jdbcTemplate.queryForObject( SELECT * FROM account WHERE account_no ? FOR UPDATE, new BeanPropertyRowMapper(Account.class), toNo); if (to null || to.getStatus() ! 1) { throw new BizException(转入账户不存在或状态异常); } // 4. 扣款与入账 BigDecimal fromAfter from.getBalance().subtract(amount); BigDecimal toAfter to.getBalance().add(amount); jdbcTemplate.update( UPDATE account SET balance ?, version version 1 WHERE account_no ? AND version ?, fromAfter, fromNo, from.getVersion()); jdbcTemplate.update( UPDATE account SET balance ?, version version 1 WHERE account_no ? AND version ?, toAfter, toNo, to.getVersion()); // 5. 写两条流水 String tradeNo UUID.randomUUID().toString().replace(-, ); jdbcTemplate.update( INSERT INTO transaction_log(trade_no, account_no, target_account_no, trade_type, amount, balance_before, balance_after) VALUES (?,?,?,?,?,?,?), tradeNo 01, fromNo, toNo, 4, amount, from.getBalance(), fromAfter); jdbcTemplate.update( INSERT INTO transaction_log(trade_no, account_no, target_account_no, trade_type, amount, balance_before, balance_after) VALUES (?,?,?,?,?,?,?), tradeNo 02, toNo, fromNo, 3, amount, to.getBalance(), toAfter); } }FOR UPDATE是 InnoDB 的行级锁两个并发转账请求会在这里排队避免同时读到相同余额。version 字段的乐观锁是第二道保险即使行锁失效UPDATE 的 WHERE 条件也会让其中一个失败。流水号用 UUID 加后缀区分转出和转入保证唯一。4.2 并发扣款测试怎么写光写代码不够项目报告里最好有并发测试结果。用 Java 的 CountDownLatch 模拟 10 个线程同时扣款public class ConcurrentTest { public static void main(String[] args) throws Exception { int threads 10; CountDownLatch latch new CountDownLatch(threads); ExecutorService pool Executors.newFixedThreadPool(threads); for (int i 0; i threads; i) { pool.submit(() - { try { transferService.transfer(6222020200112233445, 6222020200112233446, new BigDecimal(100.00)); } catch (Exception e) { System.out.println(失败: e.getMessage()); } finally { latch.countDown(); } }); } latch.await(); pool.shutdown(); } }跑之前账户余额设成 1000 元10 个线程各转 100 元。正确结果是 10 笔全部成功余额归零如果出现余额负数或者只成功 9 笔说明锁没生效。这个测试结果截图放进项目报告比写一百句“保证了并发安全”都有说服力。4.3 流水查询的分页写法流水表数据量大了必须分页。MySQL 用 LIMIT offset, size但 offset 很大时性能差。我一般建议按时间倒序加账号过滤走 idx_account_time 索引SELECT * FROM transaction_log WHERE account_no ? ORDER BY created_at DESC LIMIT ?, ?;参数说明第一个 ? 是账号第二个 ? 是偏移量第三个 ? 是每页条数。如果项目报告里想体现优化意识可以提一句“深分页场景可改用游标方式记录上一页最后一条的 created_at 作为下一页起点”。5. 避坑与排查银行账目系统最常见的五个翻车点5.1 现象转账后两边余额对不上流水却写了两条原因事务没生效。常见情况是Transactional注解加在了 private 方法上或者同类内部方法直接调用导致代理失效。另一个可能是 MySQL 表引擎是 MyISAM根本不支持事务。解决确认注解加在 public 方法上且通过 Spring 容器获取代理对象调用。建表时检查ENGINEInnoDB。用SHOW TABLE STATUS LIKE account看引擎类型。5.2 现象启动报“Public Key Retrieval is not allowed”原因MySQL 8 默认认证插件是 caching_sha2_passwordJDBC 连接串没允许公钥检索。解决连接串加allowPublicKeyRetrievaltrueuseSSLfalse。如果还不行把用户认证插件改成 mysql_native_passwordALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码;。5.3 现象金额出现 0.30000000000000004原因用了 DOUBLE 或 FLOAT 存金额。解决建表时金额字段一律 DECIMAL(18,2)Java 里用 BigDecimal且用new BigDecimal(0.1)而不是new BigDecimal(0.1)。后者会带入浮点误差。5.4 现象并发测试时部分请求报“Deadlock found”原因两个转账请求互相锁住了对方要用的行。比如 A 转 B 和 B 转 A 同时发生一个先锁 A 再锁 B另一个先锁 B 再锁 A。解决在 Service 层对账号排序始终按账号字符串升序加锁。这样所有请求的加锁顺序一致不会形成环路等待。5.5 现象项目报告里的 E-R 图和实际表结构对不上原因先写了报告后改了表或者建表时随手加字段没同步文档。解决用 MySQL 的SHOW CREATE TABLE导出最终建表语句照着画 E-R 图。报告里的字段名、类型、注释必须和数据库完全一致答辩老师真的会对着看。6. 进阶验证用对账脚本检查账目平衡最后一章说一个我每次带学生都会让他们做的验证写一个对账脚本检查“账户余额变动”和“流水记录”是否一致。这个脚本不长但能暴露很多隐藏 bug。public class ReconcileChecker { public static void main(String[] args) { // 查出所有账户 ListAccount accounts jdbcTemplate.query( SELECT * FROM account, new BeanPropertyRowMapper(Account.class)); for (Account acc : accounts) { // 该账户所有流水的净变动 BigDecimal netChange jdbcTemplate.queryForObject( SELECT COALESCE(SUM(CASE WHEN trade_type IN (1,3) THEN amount ELSE -amount END),0) FROM transaction_log WHERE account_no ?, BigDecimal.class, acc.getAccountNo()); // 初始余额假设为0则当前余额应等于净变动 if (acc.getBalance().compareTo(netChange) ! 0) { System.out.println(对账不平: acc.getAccountNo() 余额 acc.getBalance() 流水净额 netChange); } } System.out.println(对账检查完成); } }这段脚本的逻辑是存款和转入记正取款和转出记负所有流水加总应该等于当前余额。如果不等说明有交易没写流水或者流水写错了方向。跑一遍全绿项目报告里的“数据一致性”才算有证据。我自己的习惯是每次改完转账逻辑先跑并发测试再跑对账脚本两个都过才提交代码。这个习惯帮我省掉了无数次答辩前夜改 bug 的崩溃。希望帮到你。本文还有配套的精品资源点击获取
返回列表