You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 16:27:39