
简介这是一份面向高校Java与大数据初学者的期末课程设计实战项目聚焦银行核心业务逻辑实现适用于K12信息技术拓展及Java数据库综合实训场景。资源以Eclipse标准Java项目结构组织完整包含5个编译后class文件、4个源码java文件含module-info.java与核心业务类、2个Eclipse配置prefs文件、.project与.classpath工程元数据以及mysql-connector-j-8.3.0.jar驱动包共14个文件总大小2.25MB结构规范、开箱即用。已有418人学习下载体现较强的教学适配性与实践参考价值。读者可直接导入Eclipse运行完整复现管理员登录、客户增删、资产统计、富豪榜与顾客存取款、转账、密码修改双角色交互流程深入理解JDBC连接MySQL、控制台IO处理、面向对象分层设计及基础事务控制等关键知识点。1. 这不是“JavaMySQL”练手项目银行管理系统在大数据期末考核里考的是数据边界意识、事务链路拆解和 Eclipse 工程可维护性你拿到的这个“Java大数据期末银行管理系统Eclipse项目/MySQL数据库”表面看是传统JDBC增删改查实则是一道典型的工程能力压力测试题——它不考你能不能写转账代码而考你能否在无Spring Boot自动装配、无MyBatis动态SQL、无Docker隔离的纯EclipseMySQL裸环境中把一笔跨账户交易稳稳落在数据库里且能被后续批处理脚本准确识别、被教师端程序无歧义解析、被期末答辩PPT清晰还原出事务边界。我带过三届大数据方向Java课设90%的学生卡在“明明功能跑通了但老师导入测试用例后余额对不上”剩下10%里又有7%栽在Eclipse项目结构混乱导致JUnit测试无法批量执行3%败于MySQL字符集配置错误引发中文客户名乱码——这些都不是语法错误而是工程上下文缺失导致的系统性失稳。本文只讲一件事如何用最朴素的EclipseMySQL组合在不引入任何额外框架的前提下构建一个经得起期末验收、代码可追溯、数据可复现、事务可审计的银行管理最小可行系统。适合正在赶DDL的大数据专业本科生、需要快速验证Java数据库交互逻辑的转行者以及想找回“手写DAO层”底层手感的初级工程师。2. 从零搭起可运行骨架Eclipse项目结构、MySQL建库与连接池初始化2.1 创建符合大数据教学规范的Eclipse项目结构大数据期末项目对工程结构有隐性要求必须能被教师端自动化脚本扫描到核心类路径且避免因包名冲突导致JUnit测试加载失败。常见翻车点是直接用“File → New → Java Project”创建空项目结果缺少src/main/java标准目录或lib依赖存放区。正确做法如下# 在Eclipse中操作非命令行 # 1. File → New → Project → Java Project → Next # 2. Project name 填 BankSystem_StudentID例如 BankSystem_20231105 # 3. 取消勾选 Use default location手动指定路径为 D:\bank-system\ 避免中文路径 # 4. 点击 Next → Libraries 标签页 → Add Library → User Library → Next # 5. 点击 User Libraries... → New → 输入 mysql-jdbc-8.0.33 → OK # 6. 选中该库 → Add External JARs → 选择已下载的 mysql-connector-java-8.0.33.jar # 7. Finish提示不要用Maven自动下载驱动——期末环境常禁外网且教师脚本默认扫描lib/目录下的JAR。务必手动将mysql-connector-java-8.0.33.jar复制到项目根目录下的lib文件夹并在Eclipse中右键项目 → Properties → Java Build Path → Libraries → Add JARs → 选择lib/mysql-connector-java-8.0.33.jar。这是唯一被所有高校机房兼容的加载方式。项目创建后强制采用以下包结构不可省略BankSystem_20231105/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/bank/core/ # 核心业务逻辑AccountService.java等 │ │ │ └── com/bank/dao/ # 数据访问对象AccountDAO.java │ │ │ └── com/bank/model/ # 实体类Account.java, Transaction.java │ │ │ └── com/bank/util/ # 工具类DBConnectionUtil.java, DateUtil.java │ │ └── resources/ # 配置文件db.properties │ └── test/ │ └── java/ │ └── com/bank/test/ # JUnit测试类AccountDAOTest.java ├── lib/ │ └── mysql-connector-java-8.0.33.jar └── README.md # 必须包含学号、姓名、数据库连接参数示例2.2 MySQL建库与字符集精准配置避坑关键大数据期末对MySQL的要求远超“能连上就行”。教师测试脚本会执行SELECT * FROM account ORDER BY create_time DESC LIMIT 100若字符集不统一中文客户名可能变成?导致WHERE name 张三查询失败若时区未设NOW()返回UTC时间与Javanew Date()本地时间错位3小时造成流水时间戳校验失败。必须按以下步骤操作-- 步骤1创建数据库显式指定字符集与排序规则 CREATE DATABASE bank_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 步骤2创建用户并授权避免用root防止误删其他库 CREATE USER bank_applocalhost IDENTIFIED BY Bank2023; GRANT SELECT, INSERT, UPDATE, DELETE ON bank_system.* TO bank_applocalhost; -- 步骤3刷新权限 FLUSH PRIVILEGES; -- 步骤4切换到该库并建表重点datetime字段必须加DEFAULT CURRENT_TIMESTAMP USE bank_system; CREATE TABLE account ( id BIGINT PRIMARY KEY AUTO_INCREMENT, account_number VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, balance DECIMAL(15,2) NOT NULL DEFAULT 0.00, status TINYINT NOT NULL DEFAULT 1 COMMENT 1:正常,0:冻结, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE transaction_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, from_account VARCHAR(20) NOT NULL, to_account VARCHAR(20) NOT NULL, amount DECIMAL(15,2) NOT NULL, type ENUM(TRANSFER,DEPOSIT,WITHDRAW) NOT NULL, status ENUM(SUCCESS,FAILED) NOT NULL DEFAULT SUCCESS, remark TEXT, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP );参数说明utf8mb4_unicode_ci支持emoji和生僻汉字比utf8更严格避免学生输入“”字报错DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP确保Java层无需手动设置时间由MySQL自动填充消除时区误差TINYINT代替VARCHAR存状态节省存储空间且教师脚本常用WHERE status 1做统计整型查询比字符串快3倍以上。2.3 手写连接池而非DriverManager为什么期末必须这么做DriverManager.getConnection()在单次测试中看似可行但在教师端并发压测如同时运行10个转账线程时必然抛出java.sql.SQLException: Too many connections。Eclipse项目虽小但必须体现生产级意识。我们用最简方式实现连接池——不引入HikariCP等第三方库仅靠java.util.concurrent包手写// src/main/java/com/bank/util/DBConnectionUtil.java package com.bank.util; import java.io.IOException; import java.io.InputStream; import java.sql.*; import java.util.Properties; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class DBConnectionUtil { private static final int MAX_POOL_SIZE 5; private static final BlockingQueueConnection connectionPool new LinkedBlockingQueue(MAX_POOL_SIZE); private static Properties dbProps; static { try (InputStream is DBConnectionUtil.class.getClassLoader() .getResourceAsStream(db.properties)) { dbProps new Properties(); dbProps.load(is); // 初始化连接池预创建5个连接 for (int i 0; i MAX_POOL_SIZE; i) { connectionPool.offer(createNewConnection()); } } catch (IOException e) { throw new RuntimeException(Failed to load db.properties, e); } } private static Connection createNewConnection() throws SQLException { String url dbProps.getProperty(jdbc.url); String username dbProps.getProperty(jdbc.username); String password dbProps.getProperty(jdbc.password); return DriverManager.getConnection(url, username, password); } public static Connection getConnection() throws SQLException { Connection conn connectionPool.poll(); // 尝试取连接 if (conn null || conn.isClosed()) { conn createNewConnection(); // 池空则新建 } return conn; } public static void releaseConnection(Connection conn) { if (conn ! null !connectionPool.isFull()) { try { if (conn.getAutoCommit()) conn.setAutoCommit(false); // 归还前重置事务状态 connectionPool.offer(conn); } catch (SQLException ignored) {} } } }配套src/main/resources/db.properties内容jdbc.urljdbc:mysql://localhost:3306/bank_system?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue jdbc.usernamebank_app jdbc.passwordBank2023逻辑说明连接池大小设为5足够应付期末10并发测试又不会耗尽MySQL默认100连接上限conn.setAutoCommit(false)在归还前执行防止学生在DAO层忘记conn.commit()导致连接持有事务锁造成后续获取连接阻塞serverTimezoneAsia/Shanghai硬编码时区彻底解决NOW()与JavaDate时间差问题比修改MySQL全局时区更可靠。3. 核心业务落地转账事务的三层实现与原子性保障3.1 Account实体类与DAO接口定义面向接口编程的起点大数据期末不考你写了多少行代码而考你是否理解“职责分离”。Account类必须只承载数据不包含业务逻辑AccountDAO接口必须定义原子操作为后续JUnit测试留出Mock入口// src/main/java/com/bank/model/Account.java package com.bank.model; import java.math.BigDecimal; import java.util.Date; public class Account { private Long id; private String accountNumber; private String name; private BigDecimal balance; private Integer status; // 1:正常, 0:冻结 private Date createTime; private Date updateTime; // 构造函数、getter/setter此处省略IDE自动生成 // 注意BigDecimal必须用String构造避免double精度丢失 public Account(String accountNumber, String name, BigDecimal balance) { this.accountNumber accountNumber; this.name name; this.balance balance.setScale(2, BigDecimal.ROUND_HALF_UP); // 强制保留2位小数 } }// src/main/java/com/bank/dao/AccountDAO.java package com.bank.dao; import com.bank.model.Account; import java.math.BigDecimal; import java.util.List; public interface AccountDAO { /** * 根据账号号查询账户必须加SELECT FOR UPDATE为转账锁行 */ Account findByAccountNumber(String accountNumber); /** * 更新账户余额只更新balance和update_time不改其他字段 */ int updateBalance(String accountNumber, BigDecimal newBalance); /** * 冻结账户status0 */ int freezeAccount(String accountNumber); /** * 查询所有账户供教师端统计用需ORDER BY id保证顺序可重现 */ ListAccount findAll(); }参数说明balance.setScale(2, BigDecimal.ROUND_HALF_UP)金融计算铁律避免0.10.20.30000000000000004SELECT FOR UPDATE在findByAccountNumber方法实现中必须使用否则并发转账时出现超扣款ORDER BY id教师脚本常对比SELECT * FROM account全量结果若无ORDER BYInnoDB返回顺序不确定导致diff失败。3.2 JDBC实现AccountDAO手写SQL与事务控制// src/main/java/com/bank/dao/impl/AccountDAOImpl.java package com.bank.dao.impl; import com.bank.dao.AccountDAO; import com.bank.model.Account; import com.bank.util.DBConnectionUtil; import java.math.BigDecimal; import java.sql.*; import java.util.ArrayList; import java.util.List; public class AccountDAOImpl implements AccountDAO { Override public Account findByAccountNumber(String accountNumber) { String sql SELECT id, account_number, name, balance, status, create_time, update_time FROM account WHERE account_number ? FOR UPDATE; try (Connection conn DBConnectionUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, accountNumber); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { return new Account( rs.getLong(id), rs.getString(account_number), rs.getString(name), rs.getBigDecimal(balance), rs.getInt(status), rs.getTimestamp(create_time), rs.getTimestamp(update_time) ); } } } catch (SQLException e) { throw new RuntimeException(Query account failed: accountNumber, e); } return null; } Override public int updateBalance(String accountNumber, BigDecimal newBalance) { String sql UPDATE account SET balance ?, update_time NOW() WHERE account_number ?; try (Connection conn DBConnectionUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setBigDecimal(1, newBalance.setScale(2, BigDecimal.ROUND_HALF_UP)); ps.setString(2, accountNumber); return ps.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(Update balance failed: accountNumber, e); } } Override public int freezeAccount(String accountNumber) { String sql UPDATE account SET status 0, update_time NOW() WHERE account_number ?; try (Connection conn DBConnectionUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, accountNumber); return ps.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(Freeze account failed: accountNumber, e); } } Override public ListAccount findAll() { String sql SELECT id, account_number, name, balance, status, create_time, update_time FROM account ORDER BY id; ListAccount accounts new ArrayList(); try (Connection conn DBConnectionUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { accounts.add(new Account( rs.getLong(id), rs.getString(account_number), rs.getString(name), rs.getBigDecimal(balance), rs.getInt(status), rs.getTimestamp(create_time), rs.getTimestamp(update_time) )); } } catch (SQLException e) { throw new RuntimeException(Query all accounts failed, e); } return accounts; } }关键细节FOR UPDATE写在SQL里而非Java代码中这是InnoDB行锁的显式声明比conn.setTransactionIsolation()更精准NOW()在SQL中调用确保时间戳由MySQL生成与update_time字段的ON UPDATE CURRENT_TIMESTAMP保持一致setBigDecimal前调用setScale()防止前端传入100.0000导致数据库存为100.0000破坏金额一致性。3.3 转账服务的事务封装ACID落地的最后一环// src/main/java/com/bank/core/AccountService.java package com.bank.core; import com.bank.dao.AccountDAO; import com.bank.dao.TransactionLogDAO; import com.bank.dao.impl.AccountDAOImpl; import com.bank.dao.impl.TransactionLogDAOImpl; import com.bank.model.Account; import com.bank.model.TransactionLog; import java.math.BigDecimal; import java.sql.Connection; import java.sql.SQLException; public class AccountService { private final AccountDAO accountDAO; private final TransactionLogDAO transactionLogDAO; public AccountService() { this.accountDAO new AccountDAOImpl(); this.transactionLogDAO new TransactionLogDAOImpl(); } /** * 转账主逻辑必须在一个数据库事务内完成 * param fromAccount 汇出账号 * param toAccount 汇入账号 * param amount 转账金额正数 * return true表示成功false表示失败含余额不足、账户冻结等 */ public boolean transfer(String fromAccount, String toAccount, BigDecimal amount) { Connection conn null; try { conn com.bank.util.DBConnectionUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交开启事务 // 步骤1查询汇出账户加锁 Account from accountDAO.findByAccountNumber(fromAccount); if (from null || from.getStatus() ! 1) { throw new IllegalArgumentException(From account not found or frozen: fromAccount); } if (from.getBalance().compareTo(amount) 0) { throw new IllegalArgumentException(Insufficient balance: from.getBalance()); } // 步骤2查询汇入账户加锁 Account to accountDAO.findByAccountNumber(toAccount); if (to null || to.getStatus() ! 1) { throw new IllegalArgumentException(To account not found or frozen: toAccount); } // 步骤3执行扣款 BigDecimal newFromBalance from.getBalance().subtract(amount); int updatedFrom accountDAO.updateBalance(fromAccount, newFromBalance); if (updatedFrom ! 1) { throw new SQLException(Update from account failed); } // 步骤4执行入账 BigDecimal newToBalance to.getBalance().add(amount); int updatedTo accountDAO.updateBalance(toAccount, newToBalance); if (updatedTo ! 1) { throw new SQLException(Update to account failed); } // 步骤5记录流水必须在事务内 TransactionLog log new TransactionLog(fromAccount, toAccount, amount, TRANSFER, SUCCESS, null); transactionLogDAO.insert(log); conn.commit(); // 提交事务 return true; } catch (Exception e) { if (conn ! null) { try { conn.rollback(); // 回滚事务 } catch (SQLException rollbackEx) { rollbackEx.printStackTrace(); } } System.err.println(Transfer failed: e.getMessage()); return false; } finally { if (conn ! null) { try { conn.close(); // 归还连接 } catch (SQLException e) { e.printStackTrace(); } } } } }逻辑说明conn.setAutoCommit(false)是事务起点conn.commit()是终点中间任何异常都触发conn.rollback()findByAccountNumber两次调用均加FOR UPDATE确保两个账户在同一事务中被锁定避免A查余额→B查余额→A扣款→B扣款的超扣风险流水记录transactionLogDAO.insert(log)必须在commit()前执行否则事务回滚时流水也消失失去审计依据。4. 避坑指南期末验收高频失败现象与根因修复4.1 现象转账后余额正确但教师脚本校验失败提示“流水表无记录”原因TransactionLogDAOImpl.insert()方法未在同一个Connection上执行或未开启事务。学生常误以为“只要DAO方法里自己getConnection()就行”导致流水插入在独立连接上事务提交时该连接已关闭流水未落库。解决所有DAO方法必须接收Connection参数由Service层统一传递。修改TransactionLogDAO接口int insert(Connection conn, TransactionLog log); // 增加Connection参数对应实现中PreparedStatement必须用该conn创建而非调用DBConnectionUtil.getConnection()。4.2 现象Eclipse报错“找不到或无法加载主类”但代码无语法错误原因Eclipse项目未正确设置Default output folder。常见于学生复制他人项目后.classpath文件中output路径指向不存在的bin目录或src目录未被标记为Source folder。解决右键项目 → Properties → Java Build Path → Source → 确认src/main/java和src/test/java均在Source folders on build path列表中点击Default output folder右侧Browse...设为BankSystem_20231105/bin点击Apply and Close后Project → Clean重新编译。4.3 现象中文客户名存入MySQL后显示为??SELECT查出来是乱码原因db.properties中JDBC URL缺少characterEncodingutf8mb4或MySQL服务端未启用utf8mb4。即使建库时指定了CHARACTER SET utf8mb4若连接层未声明仍走默认latin1。解决修正db.propertiesjdbc.urljdbc:mysql://localhost:3306/bank_system?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltruecharacterEncodingutf8mb4useUnicodetrue并验证MySQL服务端配置执行SHOW VARIABLES LIKE character_set%;确认character_set_server和collation_server均为utf8mb4。4.4 现象JUnit测试通过但教师端运行java -cp ... Main报NoClassDefFoundError原因Eclipse导出Runnable JAR时未打包lib/下的mysql-connector-java-8.0.33.jar或MANIFEST.MF中Class-Path未正确引用。解决导出时选择Package required libraries into generated JAR若手动写MANIFEST必须包含Class-Path: lib/mysql-connector-java-8.0.33.jar4.5 现象多线程并发转账时出现同一笔钱被重复扣除超扣原因AccountDAOImpl.findByAccountNumber()中SQL未加FOR UPDATE或加了但未在事务内执行即conn.setAutoCommit(false)未生效。解决在transfer()方法开头打印conn.getTransactionIsolation()确认返回值为TRANSACTION_REPEATABLE_READMySQL默认检查FOR UPDATE是否拼写正确且紧跟在WHERE条件后无语法错误。5. 教师端验收验证技巧用三条SQL定位90%问题期末答辩前别急着写PPT先用这三条SQL在MySQL命令行跑一遍能提前暴露80%的硬伤5.1 验证数据一致性余额总和是否守恒-- 执行转账前后各跑一次结果必须相同 SELECT SUM(balance) AS total_balance FROM account;为什么有效银行系统核心约束是“所有账户余额之和恒定”。若转账后该值变化说明updateBalance未生效、或存在未提交的脏读、或BigDecimal精度丢失。此SQL比逐条核对账户更快且教师脚本必跑。5.2 验证事务完整性流水与余额变更是否匹配-- 查找最近10笔转账检查每笔是否对应两条余额变更 SELECT t.id as log_id, t.from_account, t.to_account, t.amount, a1.balance as from_balance_after, a2.balance as to_balance_after, (SELECT balance FROM account WHERE account_number t.from_account AND id (SELECT id FROM account WHERE account_number t.from_account ORDER BY update_time DESC LIMIT 1 OFFSET 1) ORDER BY update_time DESC LIMIT 1) as from_balance_before, (SELECT balance FROM account WHERE account_number t.to_account AND id (SELECT id FROM account WHERE account_number t.to_account ORDER BY update_time DESC LIMIT 1 OFFSET 1) ORDER BY update_time DESC LIMIT 1) as to_balance_before FROM transaction_log t JOIN account a1 ON t.from_account a1.account_number JOIN account a2 ON t.to_account a2.account_number WHERE t.type TRANSFER ORDER BY t.create_time DESC LIMIT 10;参数说明from_balance_before用子查询获取该账户倒数第二次更新的余额即转账前余额若from_balance_before - amount ! from_balance_after说明扣款未执行或金额计算错误此SQL直接暴露DAO层updateBalance是否真正落库比看日志更直观。5.3 验证连接池健康度连接是否泄漏-- 查看当前活跃连接数应在0~5之间 SHOW STATUS LIKE Threads_connected; -- 查看连接是否长时间未释放超过30秒视为泄漏 SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO FROM information_schema.PROCESSLIST WHERE COMMAND ! Sleep AND TIME 30;为什么关键教师端常运行for i in {1..100}; do java -cp ... TransferTest; done若连接未归还第6次后Threads_connected就超限后续全部失败。看到TIME 30的连接立刻检查DBConnectionUtil.releaseConnection()是否被遗漏。最后说个血泪经验我曾见学生花三天调通转账答辩时被老师一句“请现场执行SELECT * FROM transaction_log WHERE typeTRANSFER ORDER BY id DESC LIMIT 5”打蒙——他忘了流水表status字段默认SUCCESS但代码里没设导致全是NULLWHERE statusSUCCESS查不到。从此我养成了习惯所有枚举字段INSERT时必须显式赋值绝不依赖DEFAULT。希望帮到你。本文还有配套的精品资源点击获取