ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Java银行管理系统实战:从数据库设计到事务并发处理的完整拆解

Java银行管理系统实战:从数据库设计到事务并发处理的完整拆解 简介Java银行管理系统是一份面向Java初学者、高校课设和毕业设计人群的完整项目资源基于IntelliJ IDEA与MySQL数据库实现围绕银行核心业务设计了管理员与顾客两类角色覆盖管理员登录、添加/删除顾客、金额统计以及顾客登录、存款、取款、转账、修改密码、查看交易记录、退出等完整操作流程。压缩包共28个文件内含Java源码、class编译产物、xml与yml配置、pom.xml、HTML/Markdown说明文档等项目目录按src、test、.idea、target进行了划分整体约40KB方便直接导入IDEA查看结构和二次修改。目前已有424人学习/浏览说明该案例贴近主流教学进度和课程验收要求。通过这份资源读者既能梳理银行账户管理、交易流水的数据表设计与JDBC写法也能参考登录验证、异常处理、菜单交互等常见模块的实现思路在此基础上还可继续扩展日志记录、定期存款、可视化统计等功能对提升Java桌面应用开发与数据库综合实战能力很有帮助。1. Java 银行管理系统课程设计通关的关键在于数据层设计每年这个时候总有一批计算机专业的同学对着课程设计题目发愁。Java 银行管理系统是出现频率最高的题目之一IDE 用 IntelliJ IDEA、界面用 JavaSwing、数据存 MySQL这套组合看起来常规但真正动手做的时候很多人卡在“管理员怎么算存储金额”“转账怎么保证数据一致”这类业务细节上。这个项目完整实现了管理员端和顾客端两套功能从登录鉴权到增删顾客、存取款、转账、改密、查流水都有对应代码适合拿来当课设底稿也适合想搞懂 Swing MySQL 联动的新手研究。拿到这个项目后我花了一晚上把代码结构过了一遍又花了一个下午复现了整个流程。项目本身不算复杂但有几个地方值得细说数据库表怎么设计、JDBC 连接怎么配、Swing 界面和业务逻辑怎么解耦、以及那些看起来没问题但跑起来就翻车的细节。下面按我的拆解顺序把这些内容一层层展开。2. 环境搭建与 IDEA 导入先把基础环境跑通再谈业务2.1 JDK、IDEA 与 MySQL 版本匹配这个项目是标准的 Java Swing MySQL 组合理论上 JDK 8 以上都能跑但我建议直接用 JDK 8。原因很简单Swing 在 JDK 8 里最稳定高版本 JDK 虽然也能跑 Swing但模块化之后有些类的位置变了新手遇到这类报错容易懵。IDEA 版本不限社区版就够用不需要破解。MySQL 建议用 5.7 或 8.0这两个版本在 JDBC 驱动连接方式上有差异5.7 用com.mysql.jdbc.Driver8.0 用com.mysql.cj.jdbc.Driver且 8.0 必须在连接 URL 里加serverTimezoneAsia/Shanghai否则会报时区错误。项目文件里有pom.xml说明工程本身是 Maven 项目。导入 IDEA 时选pom.xml而不是直接 Open 文件夹Maven 会帮你把依赖下载好。如果网络不好导致依赖下载失败检查一下 Maven 仓库地址是不是默认的中央仓库换成阿里云镜像会快很多。!-- pom.xml 中的关键依赖MySQL 版本根据本机安装情况调整 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency这里要注意MySQL 8.0 对应这个驱动包没问题但如果你本机装的是 5.7驱动版本用 5.1.49 更稳。驱动版本和数据库版本不匹配的表现通常是能编译通过运行时抛ClassNotFoundException或连接超时。遇到这类问题先看版本对应关系别急着改代码。2.2 IDEA 导入 Maven 项目的关键步骤导入步骤我梳理成流程照着走基本不会卡壳IDEA 启动界面选择Open定位到项目根目录有CampusBankSys.rar解压后的文件夹。pom.xml会被自动识别IDEA 弹出提示时选Open as Project。右下角进度条走完后打开Project Structure快捷键 CtrlAltShiftS确认 SDK 选的是 JDK 8。打开Settings→Maven→Runner在VM Options里填-DarchetypeCataloginternal避免每次构建拉取模板。展开src/main/java找到入口类一般是带main方法的类右键运行前先确认Edit Configuration里的Working directory指向项目根目录。很多同学卡在第四步之后项目能编译但一运行就报ClassNotFoundException: com.mysql.cj.jdbc.Driver。这不是代码问题是 Maven 依赖没进 classpath。在 IDEA 右侧 Maven 面板点刷新按钮两个循环箭头的那个等依赖下载完再运行就正常了。3. 数据库设计与 JDBC 连接这是整个系统最核心的骨架3.1 三张表搞定数据结构拆完项目里的 SQL 脚本发现它的表结构非常精简三张表就覆盖了管理员、顾客、交易记录三个核心实体。表结构设计如下表名主要字段说明adminid, username, password管理员账号密码建议存 MD5 加密值customerid, username, password, balance顾客账号、密码、账户余额transactionid, customer_id, type, amount, create_time交易记录type 区分存取转设计上有个聪明的地方顾客余额存在 customer 表里交易流水单独放 transaction 表两者通过 customer_id 关联。这样查询顾客信息时不需要 JOIN 流水表性能更好记录流水时只插入新数据不影响余额查询。这种设计思想在真实银行系统里也常见表拆分独立能减少锁竞争。创建表的 SQL 语句如下我加了注释方便理解CREATE DATABASE IF NOT EXISTS bank_system DEFAULT CHARSET utf8mb4; USE bank_system; -- 管理员表 CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL ); -- 顾客表 CREATE TABLE customer ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, balance DECIMAL(10,2) DEFAULT 0.00 ); -- 交易记录表 CREATE TABLE transaction ( id INT PRIMARY KEY AUTO_INCREMENT, customer_id INT NOT NULL, type VARCHAR(20) NOT NULL, amount DECIMAL(10,2) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (customer_id) REFERENCES customer(id) );这里做几个说明DECIMAL(10,2)是金额字段的正确打开方式前两天刚看到有人用FLOAT存金额还问为什么对账对不上——FLOAT和DOUBLE是近似值DECIMAL才是精确值银行业务里一分钱都不能差。utf8mb4字符集比utf8多了对 Emoji 表情的支持现在注册用户名起个带表情的名字也不报错了。外键约束在真实大并发场景下会拖慢写入性能但作为课设项目能保证数据完整性更重要。3.2 JDBC 工具类与配置文件分离项目里把数据库连接参数单独放在配置文件里代码中用Properties类加载这是 Java 后端开发的标准做法。好处显而易见换数据库、改密码不需要动代码只改配置文件就行。DBUtil.java的核心逻辑如下public class DBUtil { private static String url; private static String username; private static String password; private static String driver; // 静态代码块类加载时执行一次 static { try { // 读取配置文件 InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties); Properties props new Properties(); props.load(in); driver props.getProperty(jdbc.driver); url props.getProperty(jdbc.url); username props.getProperty(jdbc.username); password props.getProperty(jdbc.password); // 注册驱动8.0 版本可以省略但写上更明确 Class.forName(driver); } catch (Exception e) { e.printStackTrace(); } } // 获取连接 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } // 关闭资源先关 ResultSet再关 Statement最后关 Connection public static void close(ResultSet rs, Statement stmt, Connection conn) { if (rs ! null) { try { rs.close(); } catch (SQLException ignored) {} } if (stmt ! null) { try { stmt.close(); } catch (SQLException ignored) {} } if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }对应的db.properties配置文件内容如下jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/bank_system?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 jdbc.usernameroot jdbc.password你的数据库密码参数说明useSSLfalse是因为本地开发没必要加密连接减少握手时间serverTimezoneAsia/Shanghai是 MySQL 8.0 的必填参数不填会报 CST 时区错误的异常characterEncodingutf8保证中文数据不乱码。如果你的 MySQL 是 5.7driver 改成com.mysql.jdbc.Driverurl 里的serverTimezone可以删掉。常见做法是把配置文件放在src/main/resources目录下Maven 打包时会自动把它复制到 classpath 根目录。3.3 PreparedStatement 是防 SQL 注入的底线不是可选项项目里所有 SQL 操作都用PreparedStatement而不是Statement这是安全意识在线的表现。写登录验证时如果图省事用字符串拼接 SQLString sql SELECT * FROM customer WHERE username username AND password password ;那输入用户名admin --就能直接绕过密码登录这就是经典的 SQL 注入。用PreparedStatement占位符的方式参数会经过转义处理彻底断了这条路String sql SELECT * FROM customer WHERE username ? AND password ?; PreparedStatement pstmt conn.prepareStatement(sql); pstmt.setString(1, username); // 参数按索引从 1 开始 pstmt.setString(2, password); ResultSet rs pstmt.executeQuery();逻辑说明setString方法会自动处理字符串转义单引号、双引号等特殊字符在传参过程中都会被安全编码数据库端收到的就是普通字符串值不会当作 SQL 语法解析。这是 Java 后端开发的底线操作无论项目大小都必须这么写。维护银行系统这类涉及资金的场景代码审查时这条不过关直接打回重改。你没看错这就是课设里面最容易丢分又最容易被忽略的扣分项。4. 核心业务功能拆解登录、存取款、转账的逻辑与实现4.1 登录模块的双角色设计这个系统在登录上做了区分处理管理员走admin表顾客走customer表。两者的登录逻辑相同只是查的表和后续跳转的界面不一样。用一个登录界面接收输入然后根据用户选择角色来路由到不同的验证逻辑。这是 Swing 开发里常见的做法复用界面代码差异化处理业务。顾客登录成功后需要把当前登录用户的 ID 保存到一个全局变量或 Session 类里方便后续操作知道“当前操作的是哪个账户”。这里有个项目里处理得比较好的点用一个UserContext类保存当前登录用户信息而不是把用户 ID 存在窗口类里。这样切换窗口时不会丢状态逻辑也更清晰public class UserContext { private static int currentCustomerId; private static String currentUsername; public static void setCurrentUser(int id, String name) { currentCustomerId id; currentUsername name; } public static int getCurrentCustomerId() { return currentCustomerId; } public static String getCurrentUsername() { return currentUsername; } public static void clear() { currentCustomerId 0; currentUsername null; } }使用UserContext而不是在窗口中嵌套传参好处是任何窗口里的任何组件都能随时拿到当前用户 ID不需要层层传递构造参数。这在多窗口切换的 Swing 系统里能省下一大堆冗余代码也避免因为构造函数改动引发连锁报错。4.2 存款与取款余额更新和流水记录要同步存款取款是整个系统的高频操作代码逻辑上就两步第一步更新 customer 表的 balance 字段第二步往 transaction 表插入一条流水。这里易踩的坑是“两步操作”不是原子的——如果第一步成功、第二步失败就会出现余额变了但流水没记录的情况对账就对了不上。项目里正确的做法是用事务包裹这两步操作。MySQL 默认是自动提交模式每条 SQL 执行完立即提交。在 Java 里显式开启事务的方式是conn.setAutoCommit(false); // 关闭自动提交手动控制事务 try { // 第一步更新余额 String updateSql UPDATE customer SET balance balance ? WHERE id ?; pstmt1 conn.prepareStatement(updateSql); pstmt1.setBigDecimal(1, amount); pstmt1.setInt(2, userId); pstmt1.executeUpdate(); // 第二步插入流水 String insertSql INSERT INTO transaction (customer_id, type, amount) VALUES (?, ?, ?); pstmt2 conn.prepareStatement(insertSql); pstmt2.setInt(1, userId); pstmt2.setString(2, DEPOSIT); pstmt2.setBigDecimal(3, amount); pstmt2.executeUpdate(); conn.commit(); // 两步都成功才提交 } catch (SQLException e) { conn.rollback(); // 任一步失败就回滚余额和流水保持一致 throw e; } finally { conn.setAutoCommit(true); // 恢复自动提交避免影响连接池中的其他操作 }这个模式值得一提。之前见过有同学用余额加法和流水插入各写一个 DAO 方法然后分别在两个地方调用结果测试时模拟数据库断电余额加了但流水丢了。事务机制解决的就是这种“要么全部成功、要么全部失败”的场景。银行存款这种资金敏感操作没有事务包裹的代码在答辩现场一演示就露馅。4.3 转账功能两个账户余额同时变动的原子性转账是最能体现系统质量的业务因为它涉及两个账户的余额变更扣减转出方 增加转入方。比存取款多了一层数据一致性要求。我拆解了项目里的转账实现逻辑是先检查转出方余额是否足够不够则直接提示够的话在同一事务里执行 UPDATE 操作。特别注意查余额和扣余额之间如果隔着大量耗时操作别的并发请求可能已经把余额改掉了所以查询要用SELECT ... FOR UPDATE加锁顺手就分享了这个点。转账的 SQL 部分核心如下conn.setAutoCommit(false); // 查询转出方余额并加行锁防止并发下余额被其他事务修改 String checkSql SELECT balance FROM customer WHERE id ? FOR UPDATE; PreparedStatement checkStmt conn.prepareStatement(checkSql); checkStmt.setInt(1, fromUserId); ResultSet rs checkStmt.executeQuery(); if (!rs.next()) { conn.rollback(); throw new BusinessException(转出账户不存在); } BigDecimal currentBalance rs.getBigDecimal(balance); if (currentBalance.compareTo(amount) 0) { conn.rollback(); throw new BusinessException(余额不足当前余额 currentBalance); } // 转出方扣款 String deductSql UPDATE customer SET balance balance - ? WHERE id ?; // 转入方加款 String plusSql UPDATE customer SET balance balance ? WHERE id ?; // 两条流水记录 // 转出方记录 TRANSFER_OUT转入方记录 TRANSFER_IN conn.commit();参数说明FOR UPDATE是 MySQL 的行级锁语法它会在事务期间锁住这条记录其他事务对这个账户的读写会被阻塞直到当前事务提交或回滚。对于课设场景可能用不太上但答辩时你说了这一句老师就知道你考虑过并发安全问题。compareTo方法而不是来比较 BigDecimal是因为 BigDecimal 是对象用比较的是引用地址而非数值这是 Java 开发中非常经典的坑。4.4 管理员功能添加/删除顾客与存储金额统计管理员的三个核心操作里添加和删除顾客比较简单本质上是 customer 表的INSERT和DELETE。但删除顾客时有个注意点如果顾客表有外键约束直接删除会报外键冲突错误customer_id 被 transaction 表引用所以删除前需要先删掉该顾客的流水记录或者把外键设为ON DELETE CASCADE。计算存储金额这个功能SQL 写起来很直观SELECT SUM(balance) AS total_amount FROM customer;这一条 SQL 就能汇总出所有顾客的存款总额。项目的界面里会展示总存储金额用来做首页统计展示。这条统计 SQL 在实现层面有讲究如果业务量小全表扫描没问题如果数据量大应该考虑在customer.balance字段上建索引或者引入聚合表定时汇总。课设体量用第一版就够了但答辩时你主动提数据库性能演进方向能加不少印象分。5. 避坑指南Swing、MySQL 与版本差异的典型问题5.1 问题一Swing 窗口中文乱码现象界面上所有中文都变成了问号或乱码。原因JFrame的默认字符集和编译时的字符集不一致。IDEA 默认编译用 UTF-8但控制台运行环境的编码可能是 GBK。解决在 IDEA 的Help → Edit Custom VM Options里加上-Dfile.encodingutf-8重启 IDEA。同时检查Settings → Editor → File Encodings确保项目编码、属性文件编码、控制台编码统一为 UTF-8。如果用了 Windows 系统数据库连接 URL 里也必须有characterEncodingutf8。5.2 问题二MySQL 8.0 连接报 Public Key Retrieval 错误现象代码连接到 MySQL 8.0 时报Public Key Retrieval is not allowed。原因MySQL 8.0 默认使用 caching_sha2_password 插件做身份认证而 JDBC 驱动默认不开启公共密钥检索。解决在 JDBC URL 末尾追加allowPublicKeyRetrievaltrue。完整 URL 变成jdbc:mysql://localhost:3306/bank_system?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8allowPublicKeyRetrievaltrue另一个思路是把 MySQL 用户改成 mysql_native_password 插件认证但改认证方式需要动数据库配置不如直接改 URL 方便。5.3 问题三运行报ClassNotFoundException: com.mysql.jdbc.Driver现象代码编译通过运行时找不到驱动类。原因pom.xml 里声明的是 MySQL 8.0 驱动代码里用的却是com.mysql.jdbc.Driver5.7 的类名或者反过来。解决记住对应关系——MySQL 5.7 用com.mysql.jdbc.DriverMySQL 8.0 用com.mysql.cj.jdbc.Driver。注意是cj。这个坑特别隐蔽因为两个类名长得非常像不仔细看根本发现不了。打开 pom.xml 确认依赖版本再打开配置文件确认类名保证两者版本一致。5.4 问题四IDEA 运行 Swing 界面卡在 Build 阶段现象点击运行后Build 过程非常慢界面迟迟不出来。如果控制台出现java.lang.OutOfMemoryError: PermGen space基本就是内存分配不足。原因IDEA 默认给编译器的堆内存太小Maven 项目首次构建依赖较多时容易触发。解决Settings → Build, Execution, Deployment → Compiler → Shared build process VM options里设置-Xmx512m。如果问题依然存在检查是否在Help → Change Memory Settings里给 IDEA 本身分配了足够内存建议 1024MB 起步。5.5 问题五Swing 界面执行数据库操作时卡死现象点击登录按钮后整个窗口无响应过十几秒才恢复。原因数据库操作放在了 Swing 的事件分发线程Event Dispatch Thread中执行耗时操作阻塞了界面绘制线程。解决时间充裕的话把数据库访问逻辑放到 SwingWorker 或新线程中执行操作完成后通过invokeLater回到事件线程更新界面。项目里如果只是课设体量数据量小、查询快卡顿问题不明显但答辩时老师如果问“你的界面为什么点击后卡一下”用“这是 Swing 单线程模型的限制生产级方案应该用 SwingWorker 做异步”来回答会显得思路清晰。这个升级能让系统在多任务处理时界面保持流畅。6. 把课设做成加分项代码审查清单与三个进阶改造方向如果你只是想让课程设计通过验收按上面的步骤把功能跑通就够了。但如果你想让系统在答辩时拿高分或者想借此积累一点真实项目经验下面这几个方向值得花时间。我逐条说清楚每个方向的价值和大概实现思路。方向一密码加盐哈希存储。项目目前是明文密码走数据库这在课设里很常见但真实银行系统绝不可能这么做。最简单的改进是用 MD5 加盐或者直接用 BCrypt。加盐的意思是给密码拼接一个随机字符串再哈希这样即使两个用户密码相同哈希结果也不一样攻击者无法通过彩虹表反查。登录验证时取出数据库里的盐值拼接输入的密码再做同样的哈希运算比对结果即可。这个改造约 30 行代码但能体现你对安全的理解。方向二交易记录分页查询。现在的交易记录表是无限增长的查询全部记录时会遇到「数据量大、页面卡顿」的问题。改造方式是加一个分页参数SQL 用LIMIT offset, pageSize界面加上「上一页/下一页」按钮。一个简洁的页面顶多加上一个未来可用的下拉框选项就能让系统看起来像正经商用软件。LIMIT 的分页在数据量超过 10 万条时有性能损耗但作为课设完全够用答辩时可以提一句生产环境会用游标分页或 keyset pagination 优化。方向三多线程存款的压力测试。在我的记忆中很多同学做完系统连并发测试都不做老师一上来就点 5 个窗口同时操作直接翻车的例子不在少数。用 JUnit 写一个简单的并发测试模拟 100 个线程同时给同一个账户存钱看最终余额是否正确。如果数据不对说明你的 UPDATE 语句有并发问题这时候引入事务和锁就能解决。这个实验过程本身就很有价值。操作上给一个验证事务是否生效的测试脚本框架这是我在改完系统后固定会跑的东西Test public void testConcurrentDeposit() throws Exception { int threadCount 10; int depositAmount 100; CountDownLatch latch new CountDownLatch(threadCount); // 模拟10个线程同时给同一个账户存款 for (int i 0; i threadCount; i) { new Thread(() - { try { Connection conn DBUtil.getConnection(); String sql UPDATE customer SET balance balance ? WHERE id 1; PreparedStatement pstmt conn.prepareStatement(sql); pstmt.setInt(1, depositAmount); pstmt.executeUpdate(); conn.close(); } catch (SQLException e) { e.printStackTrace(); } finally { latch.countDown(); } }).start(); } latch.await(); // 查询余额验证结果 Connection conn DBUtil.getConnection(); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(SELECT balance FROM customer WHERE id 1); rs.next(); BigDecimal finalBalance rs.getBigDecimal(balance); System.out.println(Final balance: finalBalance); }这段代码跑完如果余额不是 10 次以上的赋值结果就说明连接到的数据库有问题或者 MySQL 事务隔离级别设置不对。通过这个测试你能直观理解并发场景下数据一致性的价值这个比背八股文里的并发概念有用的多。几十行代码能跑出真实的并发问题这是难得有性价比的练习。从那次之后我每做一个带数据库的项目都会强制自己走一遍“先写事务测试、再写业务代码”的流程。这个好习惯让我在之后的项目里避免了很多返工。希望这次的拆解能帮到正在做课设的你把 JavaSwing MySQL 这套组合真正吃透。本文还有配套的精品资源点击获取
返回列表