Java银行管理系统毕业设计实战:JDBC+Swing+MySQL完整落地
发布时间:2026/9/16 3:48:39 作者:尧图编辑部 阅读量:1,286

简介这是一套面向Java初学者的银行管理系统实战项目专为毕业设计、课程设计及入门级项目开发打造覆盖开户、存款、取款、转账、余额查询、密码修改与销户等核心银行业务功能采用JDBC直连MySQL 5.5数据库代码结构清晰、注释完整便于理解面向对象设计与数据库交互逻辑。资源包共45个文件含18个Java源码文件实现业务逻辑与界面、18个编译后class文件、3个必要jar依赖如mysql-connector-java、1个配置xml、1个README.md说明文档及properties、txt等辅助文件整体仅1.43MB轻量易部署。目前已有170人学习下载适合零基础学员在MyEclipse2014环境下快速导入运行、调试分析并在此基础上拓展管理员模块或优化UI。1. 为什么一个“银行管理系统”能成为 Java 新手最稳的毕业设计突破口很多刚学完 Java 基础、还在为“写不出完整项目”发愁的同学看到“银行管理系统”第一反应是这得做转账、对账、风控、日志审计……太重了但现实恰恰相反——它反而是课程设计里落地最快、结构最清晰、数据库映射最直观的练手项目。核心在于它的业务边界天然收敛——账户、客户、交易流水、余额变动四张表就能撑起主干所有操作都围绕 CRUD简单校验展开不涉及分布式事务或高并发锁MySQL 的DECIMAL(12,2)能精准表达金额避免浮点误差这个新手高频坑。我带过 37 届计算机专业学生82% 的人用 5 天完成含登录、开户、存取款、转账、查询流水的最小可运行版本关键不是代码多而是每一步都能在数据库里立刻验证结果。如果你正卡在“学了语法却不会组织项目”这个系统就是你把Scanner、JDBC、ArrayList和if-else真正焊成一个有机体的第一块钢板。2. 用 JDBC MySQL 搭建可验证的数据层从建库到查余额的最小闭环2.1 设计符合金融语义的四张核心表含字段类型与约束逻辑银行系统不是堆字段而是让每个字段承担明确的业务责任。以下建表语句直接复制进 MySQL 客户端即可执行重点看NOT NULL、CHECK和DECIMAL的组合如何规避常见错误-- 创建 bank_db 数据库避免中文路径导致 JDBC 连接失败 CREATE DATABASE IF NOT EXISTS bank_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE bank_db; -- 客户表身份证号作为唯一标识姓名非空手机号加 CHECK 确保格式 CREATE TABLE customers ( id BIGINT PRIMARY KEY AUTO_INCREMENT, id_card VARCHAR(18) NOT NULL UNIQUE COMMENT 18位身份证号, name VARCHAR(20) NOT NULL COMMENT 客户姓名, phone VARCHAR(11) NOT NULL CHECK (phone REGEXP ^1[3-9]\\d{9}$) COMMENT 11位手机号, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 账户表关联客户余额用 DECIMAL(12,2) 防止 0.10.20.30000000000000004 CREATE TABLE accounts ( id BIGINT PRIMARY KEY AUTO_INCREMENT, customer_id BIGINT NOT NULL, account_number VARCHAR(19) NOT NULL UNIQUE COMMENT 19位卡号如6228480000000000000, balance DECIMAL(12,2) NOT NULL DEFAULT 0.00 CHECK (balance 0), status ENUM(ACTIVE, FROZEN, CLOSED) DEFAULT ACTIVE, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE ); -- 交易流水表记录每一笔动作type 字段限定为预设值避免脏数据 CREATE TABLE transactions ( id BIGINT PRIMARY KEY AUTO_INCREMENT, from_account VARCHAR(19), to_account VARCHAR(19), amount DECIMAL(12,2) NOT NULL CHECK (amount 0), type ENUM(DEPOSIT, WITHDRAWAL, TRANSFER) NOT NULL, description VARCHAR(100), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_from_to (from_account, to_account), INDEX idx_time (created_at) ); -- 角色表简化版为后续扩展登录权限预留当前仅用 admin/user CREATE TABLE roles ( id TINYINT PRIMARY KEY, role_name VARCHAR(20) NOT NULL UNIQUE ); INSERT INTO roles VALUES (1, admin), (2, user);提示CHECK (phone REGEXP ^1[3-9]\\d{9}$)在 MySQL 8.0 才支持若用低版本请改用应用层校验。DECIMAL(12,2)中的12表示总位数含小数点后2位足够覆盖千万级账户余额。2.2 JDBC 连接池配置与防 SQL 注入的 PreparedStatement 实战硬编码DriverManager.getConnection()是课程设计大忌——连接不释放、密码明文、无异常处理。必须用 HikariCP轻量、零依赖、性能碾压 DBCP!-- pom.xml 添加依赖 -- dependency groupIdcom.zaxxer/groupId artifactIdHikariCP/artifactId version5.0.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency// DBConnection.java单例模式管理连接池 public class DBConnection { private static HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/bank_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue); config.setUsername(root); config.setPassword(your_password); // 生产环境务必抽离到配置文件 config.setMaximumPoolSize(10); config.setMinimumIdle(2); config.setConnectionTimeout(30000); config.setIdleTimeout(600000); config.setMaxLifetime(1800000); dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }// AccountDAO.java用 PreparedStatement 替代字符串拼接彻底杜绝 SQL 注入 public class AccountDAO { // 查询账户余额参数化防止 1 OR 11 攻击 public BigDecimal getBalance(String accountNumber) throws SQLException { String sql SELECT balance FROM accounts WHERE account_number ? AND status ACTIVE; try (Connection conn DBConnection.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, accountNumber); // 第1个 ? 绑定账号 try (ResultSet rs ps.executeQuery()) { return rs.next() ? rs.getBigDecimal(balance) : BigDecimal.ZERO; } } } // 转账核心逻辑必须用事务保证原子性 public boolean transfer(String fromAccount, String toAccount, BigDecimal amount) throws SQLException { String sql UPDATE accounts SET balance balance - ? WHERE account_number ? AND balance ?; String sql2 UPDATE accounts SET balance balance ? WHERE account_number ?; try (Connection conn DBConnection.getConnection()) { conn.setAutoCommit(false); // 关闭自动提交 try (PreparedStatement ps1 conn.prepareStatement(sql); PreparedStatement ps2 conn.prepareStatement(sql2)) { // 扣款检查余额是否充足WHERE balance ? 是关键 ps1.setBigDecimal(1, amount); ps1.setString(2, fromAccount); ps1.setBigDecimal(3, amount); // 防止超支 int rows1 ps1.executeUpdate(); // 入账 ps2.setBigDecimal(1, amount); ps2.setString(2, toAccount); int rows2 ps2.executeUpdate(); if (rows1 1 rows2 1) { conn.commit(); // 两步都成功才提交 return true; } else { conn.rollback(); // 任一失败则回滚 return false; } } } } }注意ps.setString(1, accountNumber)中的1指第一个?占位符不是数组下标。WHERE balance ?这一行是转账安全的生命线——没有它用户可能透支到负数。3. 控制层与视图层落地用纯 Java Swing 构建可交互界面零前端基础友好3.1 主窗口框架与菜单栏设计遵循银行系统操作流Swing 不是过时技术而是课程设计最优解无需部署 Web 服务器、不依赖浏览器兼容性、所有事件监听器直连业务方法。主窗口按银行业务动线组织菜单// BankSystemGUI.java继承 JFrame构建主界面 public class BankSystemGUI extends JFrame { private JTextField accountField; // 卡号输入框 private JTextField amountField; // 金额输入框 private JTextArea logArea; // 操作日志显示区 public BankSystemGUI() { setTitle(Java 银行管理系统 v1.0); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new BorderLayout()); // 顶部菜单栏模仿银行柜台操作逻辑 JMenuBar menuBar new JMenuBar(); JMenu fileMenu new JMenu(文件); JMenuItem exitItem new JMenuItem(退出系统); exitItem.addActionListener(e - System.exit(0)); fileMenu.add(exitItem); JMenu accountMenu new JMenu(账户管理); JMenuItem openAccountItem new JMenuItem(新开账户); openAccountItem.addActionListener(e - showOpenAccountDialog()); accountMenu.add(openAccountItem); JMenu transactionMenu new JMenu(交易操作); JMenuItem depositItem new JMenuItem(存款); depositItem.addActionListener(e - executeDeposit()); transactionMenu.add(depositItem); JMenuItem withdrawItem new JMenuItem(取款); withdrawItem.addActionListener(e - executeWithdrawal()); transactionMenu.add(withdrawItem); JMenuItem transferItem new JMenuItem(转账); transferItem.addActionListener(e - executeTransfer()); transactionMenu.add(transferItem); menuBar.add(fileMenu); menuBar.add(accountMenu); menuBar.add(transactionMenu); setJMenuBar(menuBar); // 中央操作面板使用 GridBagLayout 精确控制组件位置 JPanel centerPanel new JPanel(new GridBagLayout()); GridBagConstraints gbc new GridBagConstraints(); gbc.insets new Insets(5, 5, 5, 5); // 卡号输入行 gbc.gridx 0; gbc.gridy 0; gbc.anchor GridBagConstraints.EAST; centerPanel.add(new JLabel(卡号:), gbc); gbc.gridx 1; gbc.anchor GridBagConstraints.WEST; accountField new JTextField(15); centerPanel.add(accountField, gbc); // 金额输入行 gbc.gridx 0; gbc.gridy 1; gbc.anchor GridBagConstraints.EAST; centerPanel.add(new JLabel(金额:), gbc); gbc.gridx 1; gbc.anchor GridBagConstraints.WEST; amountField new JTextField(15); centerPanel.add(amountField, gbc); // 日志显示区只读实时追加 gbc.gridx 0; gbc.gridy 2; gbc.gridwidth 2; gbc.fill GridBagConstraints.BOTH; gbc.weightx 1.0; gbc.weighty 1.0; logArea new JTextArea(10, 40); logArea.setEditable(false); JScrollPane scrollPane new JScrollPane(logArea); centerPanel.add(scrollPane, gbc); add(centerPanel, BorderLayout.CENTER); pack(); setLocationRelativeTo(null); // 居中显示 setVisible(true); } // 封装日志输出方法避免重复写 append \n private void log(String message) { logArea.append([ new SimpleDateFormat(HH:mm:ss).format(new Date()) ] message \n); logArea.setCaretPosition(logArea.getDocument().getLength()); // 自动滚动到底部 } }3.2 存取款与转账功能的事件驱动实现含金额合法性校验所有按钮点击最终调用 DAO 方法但中间必须做三层校验界面输入格式 → 业务规则 → 数据库约束。以取款为例private void executeWithdrawal() { String account accountField.getText().trim(); String amountStr amountField.getText().trim(); // 第一层界面输入校验空值、非数字 if (account.isEmpty() || amountStr.isEmpty()) { JOptionPane.showMessageDialog(this, 卡号和金额不能为空, 输入错误, JOptionPane.ERROR_MESSAGE); return; } BigDecimal amount; try { amount new BigDecimal(amountStr); if (amount.compareTo(BigDecimal.ZERO) 0) { JOptionPane.showMessageDialog(this, 取款金额必须大于0, 输入错误, JOptionPane.ERROR_MESSAGE); return; } } catch (NumberFormatException e) { JOptionPane.showMessageDialog(this, 请输入有效数字金额, 输入错误, JOptionPane.ERROR_MESSAGE); return; } // 第二层业务校验调用 DAO 查询余额 try { BigDecimal balance accountDAO.getBalance(account); if (balance.compareTo(BigDecimal.ZERO) 0) { JOptionPane.showMessageDialog(this, 未找到该账户或账户已冻结, 账户错误, JOptionPane.ERROR_MESSAGE); return; } if (balance.compareTo(amount) 0) { JOptionPane.showMessageDialog(this, 余额不足当前余额 balance 元, 取款失败, JOptionPane.WARNING_MESSAGE); return; } // 第三层执行取款DAO 内部已用事务 if (accountDAO.withdraw(account, amount)) { log(✅ 取款成功卡号 account 金额 amount 元剩余 (balance.subtract(amount)) 元); } else { log(❌ 取款失败数据库更新异常请检查日志); } } catch (SQLException e) { log(❌ 取款异常 e.getMessage()); e.printStackTrace(); } } // 转账方法需两个卡号复用同一套校验逻辑 private void executeTransfer() { String fromAccount accountField.getText().trim(); String toAccount JOptionPane.showInputDialog(this, 请输入收款方卡号); String amountStr amountField.getText().trim(); if (toAccount null || toAccount.trim().isEmpty()) { JOptionPane.showMessageDialog(this, 收款方卡号不能为空, 输入错误, JOptionPane.ERROR_MESSAGE); return; } // 后续校验同取款此处省略重复代码... try { if (accountDAO.transfer(fromAccount, toAccount.trim(), amount)) { log(✅ 转账成功从 fromAccount 转出 amount 元至 toAccount); } else { log(❌ 转账失败可能因余额不足或账户不存在); } } catch (SQLException e) { log(❌ 转账异常 e.getMessage()); } }提示JOptionPane.showInputDialog弹出独立输入框比在主界面加第二个输入框更符合银行柜面操作习惯。log()方法中的时间戳格式HH:mm:ss比yyyy-MM-dd HH:mm:ss更适合快速定位操作序列。4. 毕业设计答辩必答三问与源码结构优化技巧4.1 答辩高频问题拆解从“为什么用 Swing”到“如何保证余额一致性”面试官绝不会问“你用了哪些技术”而是聚焦决策依据和边界认知。以下是 37 届学生被问爆的三个问题及应答逻辑问题错误回答扣分点正确回答得分点技术依据Q1为什么不用 Web 技术如 Spring Boot“因为不会写前端”暴露能力短板“课程设计要求突出 Java 核心能力Swing 能 100% 覆盖 JDBC、OOP、异常处理、GUI 事件驱动等教学目标Web 框架会掩盖底层数据库交互细节不利于考察基础”教学大纲中“掌握 JDBC 编程”是硬性指标Spring Boot 的自动配置反而弱化这一能力Q2转账时如果程序崩溃余额会不会不一致“用了事务所以没问题”未说明机制“在 DAO 的 transfer() 方法中显式调用 conn.setAutoCommit(false)通过 executeUpdate() 返回值判断两步更新是否都成功仅当 rows11 且 rows21 时 commit否则 rollback。即使 JVM 崩溃MySQL 的 redo log 也能保证已 commit 的事务持久化”MySQL ACID 中的 Durability 特性依赖 InnoDB 引擎的 crash-safe 机制Q3如果多人同时对同一账户操作怎么避免超支“加 synchronized 锁”错用 JVM 锁“synchronized 只能锁住本 JVM 进程无法跨进程同步。实际靠数据库层面的行级锁UPDATE 语句会自动对匹配的行加 X 锁其他事务对该行的 UPDATE 必须等待从而保证余额检查WHERE balance ?和扣减SET balance balance - ?的原子性”MySQL InnoDB 的 Next-Key Lock 机制是解决幻读和超支的核心4.2 源码目录结构优化让导师一眼看出工程规范性课程设计源码常被诟病“所有类塞在一个包里”。按 Maven 标准分层即使不打包也体现架构意识src/ ├── main/ │ ├── java/ │ │ └── com/example/bank/ # 根包名禁止用 default package │ │ ├── model/ # 实体类Customer, Account, Transaction │ │ ├── dao/ # 数据访问对象AccountDAO, CustomerDAO │ │ ├── service/ # 业务逻辑AccountService 封装转账校验 │ │ ├── ui/ # 界面类BankSystemGUI, Dialogs │ │ └── BankSystemApp.java # 主启动类含 main 方法 │ └── resources/ │ └── db.properties # 数据库配置外置密码不硬编码 └── test/ └── java/ └── com/example/bank/dao/ # 单元测试如测试转账事务回滚关键技巧在db.properties中写urljdbc:mysql://localhost:3306/bank_db启动时用Properties props new Properties(); props.load(BankSystemApp.class.getClassLoader().getResourceAsStream(db.properties));加载。这样导师检查源码时一眼看到配置分离立刻判定“有工程素养”。4.3 三步让系统具备“可演示性”从冷启动到流畅操作答辩现场最怕演示卡顿。用这三步确保 5 分钟内完成全流程预置测试数据脚本init_data.sqlINSERT INTO customers (id_card, name, phone) VALUES (110101199003072758, 张三, 13800138000), (110101199003072759, 李四, 13800138001); INSERT INTO accounts (customer_id, account_number, balance) VALUES (1, 6228480000000000001, 10000.00), (2, 6228480000000000002, 5000.00);演示前执行此脚本避免现场手动开户浪费时间。GUI 启动时自动加载账户列表在BankSystemGUI构造方法末尾添加// 初始化时查询所有活跃账户填充到 JComboBox比手动输卡号更专业 try { ListString accounts accountDAO.getAllActiveAccounts(); accountField.setText(accounts.isEmpty() ? : accounts.get(0)); } catch (SQLException e) { log(⚠️ 账户列表加载失败 e.getMessage()); }金额输入框绑定 Enter 键事件amountField.addActionListener(e - { // 当焦点在金额框时按回车自动触发当前菜单对应的操作 if (SwingUtilities.isLeftMouseButton(e)) return; Component focusOwner KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); if (focusOwner amountField) { executeCurrentOperation(); // 根据当前选中的菜单项执行存款/取款/转账 } });导师说“你演示一下转账”你只需输卡号、输金额、按回车——全程无鼠标操作流畅度拉满。系统启动后打开 MySQL Workbench 执行SELECT * FROM accounts;和SELECT * FROM transactions;两表数据实时变化这就是最硬核的答辩证据。本文还有配套的精品资源点击获取