Java生成MySQL库中不存在的唯一9位银行账号实现方案
Java+MySQL实现9位唯一银行账号生成方案
针对学习项目的场景,提供两种可直接落地的实现,复杂度从低到高可选:
方案1:随机生成+查重重试(实现成本最低,优先推荐)
核心逻辑完全匹配现有业务流程,不需要额外建表,新手10分钟就能写完:
- 账号生成规则:首位取1-9的随机整数,剩余8位分别取0-9的随机整数,拼接为9位字符串即可,允许单个账号内数字重复。
- 查重逻辑:生成账号后直接查询数据库判断是否已存在,不存在就直接返回给前端展示,存在则重新生成再次查重,直到拿到可用账号。
- 兜底防重:给
accounts表的account_number字段加唯一索引,就算极端并发场景下两个用户同时生成了相同账号,最终插入数据库时会触发唯一键冲突,捕获异常后重新生成账号重试插入即可,从数据库层面彻底杜绝重复账号。
参考Java实现代码:
import java.util.concurrent.ThreadLocalRandom; import org.springframework.dao.DuplicateKeyException; import org.springframework.jdbc.core.JdbcTemplate; public class AccountGenerator { // 使用线程安全的随机数生成器,避免多线程环境下随机数重复问题 private static final ThreadLocalRandom RANDOM = ThreadLocalRandom.current(); private final JdbcTemplate jdbcTemplate; public AccountGenerator(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } /** * 生成未被占用的9位银行账号 */ public String generateAvailableAccount() { while (true) { StringBuilder accountBuilder = new StringBuilder(); // 生成首位1-9 accountBuilder.append(RANDOM.nextInt(1, 10)); // 生成剩余8位0-9 for (int i = 0; i < 8; i++) { accountBuilder.append(RANDOM.nextInt(0, 10)); } String account = accountBuilder.toString(); // 查重 Integer existCount = jdbcTemplate.queryForObject( "SELECT COUNT(1) FROM login_info.accounts WHERE account_number = ?", Integer.class, account ); if (existCount != null && existCount == 0) { return account; } } } /** * 最终创建账号时的插入重试逻辑 */ public void createAccount(/* 传入所有用户填写的参数, String account */) { int maxRetry = 3; for (int i = 0; i < maxRetry; i++) { try { // 执行INSERT语句,把account和其他用户信息一起插入 String insertSql = "INSERT INTO login_info.accounts(account_number, ...其他字段) VALUES (?, ...)"; jdbcTemplate.update(insertSql, account, ...其他参数); return; } catch (DuplicateKeyException e) { // 触发唯一键冲突,说明账号刚好被占用,重新生成账号重试 account = generateAvailableAccount(); } } throw new RuntimeException("账号生成繁忙,请稍后重试"); } }
需要提前执行的数据库加索引SQL:
ALTER TABLE login_info.accounts ADD UNIQUE INDEX uk_account_number(account_number);
这个方案的优势是实现简单,9位可用账号总量有9亿个,学习项目用户量极小,撞号概率不到百万分之一,就算撞号最多重试1-2次就能拿到可用账号,性能完全没有问题。
方案2:数据库序列自增生成(零撞号,适合练习数据库事务逻辑)
如果不想用随机重试的逻辑,可以用数据库序列的方式,拿出来的账号天生唯一,不需要查重:
- 单独建一张账号序列表,存储当前已分配的最大账号值,初始值设为99999999,第一次取号自增后刚好是最小的9位合法账号100000000。
- 每次生成账号时开启数据库事务,先执行更新语句把序列值+1,再查询更新后的值作为新账号,数据库行锁会保证并发场景下不会拿到重复值。
对应数据库SQL:
-- 建序列表 CREATE TABLE login_info.account_seq ( current_max BIGINT NOT NULL COMMENT '当前已分配的最大账号' ); -- 初始化序列值 INSERT INTO login_info.account_seq(current_max) VALUES (99999999);
取号的Java逻辑要保证在同一个事务内执行:
// 开启事务后执行 jdbcTemplate.update("UPDATE login_info.account_seq SET current_max = current_max + 1"); Long account = jdbcTemplate.queryForObject("SELECT current_max FROM login_info.account_seq", Long.class); // 事务提交
这个方案的缺点是账号是连续递增的,能看出开户先后顺序,且如果用户中途放弃开户,取出的账号不会被回收,会出现跳号,但9亿的号量完全足够使用,学习场景下不需要做废号回收,额外加逻辑反而增加复杂度。
业务流程适配注意点
- 用户进入银行账户信息页生成账号后,不要提前把账号插入
accounts表,否则用户中途退出会产生大量无效垃圾数据,只需要把生成的账号存在当前会话(比如服务端Session、前端隐藏表单域),等用户最终点击创建账号提交所有信息时,再带着账号一起执行插入操作即可。 - 账号字段推荐用
CHAR(9)类型存储,比BIGINT更稳妥,不会出现类型转换导致的位数丢失问题,加了唯一索引后查询性能和数字类型没有差异。
内容的提问来源于stack exchange,提问作者TKBK531
相关产品推荐
相关产品推荐

