首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 申請單流水號產生器:日期加序號機制實作

     編輯:Java 程式設計入門

需求背景

業務系統常需產生唯一且有意義的單號,格式通常為 前綴 + 年月日 + 流水號(如 CB20240115001)。本文實作一個執行緒安全的產生器,支援跨日歸零與從現有單號延續。

核心類別 SerialNum

靜態狀態變數

private static String count = "000";      // 當日流水號
private static String dateValue = "20131115"; // 最後產生日期

主要方法

1. 產生新單號 getMoveOrderNo()

public synchronized static String getMoveOrderNo() {
    long No = 0;
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
    String nowdate = sdf.format(new Date());
    No = Long.parseLong(nowdate);

    // 跨日檢查:日期變更則流水號歸零
    if (!String.valueOf(No).equals(dateValue)) {
        count = "000";
        dateValue = String.valueOf(No);
    }

    String num = String.valueOf(No);
    num += getNo(count);  // 附加遞增流水號
    num = "CB" + num;     // 加上業務前綴
    return num;
}

2. 從現有單號延續 getMoveOrderNo(String serialNum)

public synchronized static String getMoveOrderNo(String serialNum) {
    String nyr = StringUtils.substring(serialNum, 2, 10);  // 取得年月日
    String countV = StringUtils.substring(serialNum, 10);  // 取得流水號

    // 若傳入單號流水號大於目前記憶體中的值,更新狀態
    if (Integer.valueOf(countV) > Integer.valueOf(count)) {
        dateValue = nyr;
        count = String.valueOf(countV);
    }
    return getMoveOrderNo();
}

3. 流水號遞增邏輯 getNo()

public static String getNo(String s) {
    String rs = s;
    int i = Integer.parseInt(rs);
    i += 1;
    rs = "" + i;
    // 左補零至 3 位
    for (int j = rs.length(); j < 3; j++) {
        rs = "0" + rs;
    }
    count = rs;
    return rs;
}

測試驗證

public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
        System.out.println(getMoveOrderNo());
    }
}
// 輸出範例:
// CB20240115001
// CB20240115002
// ...
// CB20240115010

關鍵設計考量

✅ 執行緒安全

所有公開方法皆宣告 synchronized,確保多執行緒環境下流水號不重複、不跳號。

⚠️ 記憶體狀態易失性

重要警示:上述實作將狀態儲存在靜態變數中。若應用程式重啟、伺服器故障或叢集部署,會導致流水號從頭開始,產生重複單號。

生產環境解決方案:資料庫協同

為保證絕對唯一,建議採用「資料庫為主、記憶體為輔」的雙重機制:

  1. 啟動初始化:查詢資料庫最新單號,呼叫 getMoveOrderNo(lastSerial) 同步記憶體狀態
  2. 持久化寫入:每次產生新單號後,立即寫入資料庫記錄(可批次非同步寫入提升效能)
  3. 叢集環境:採用資料庫序列、Redis INCR 或分布式 ID 生成器(Snowflake/Leaf)替代本地計數器

改進版本範例(資料庫整合)

@Repository
public class SerialNumberService {
    @Autowired JdbcTemplate jdbcTemplate;

    @Transactional
    public String generateNext(String prefix, String bizDate) {
        // 1. 查詢當日最大流水號
        String sql = "SELECT MAX(serial_no) FROM t_serial WHERE prefix=? AND biz_date=?";
        Integer maxSeq = jdbcTemplate.queryForObject(sql, Integer.class, prefix, bizDate);
        int nextSeq = (maxSeq == null) ? 1 : maxSeq + 1;

        // 2. 組裝新單號
        String serial = String.format("%s%s%03d", prefix, bizDate, nextSeq);

        // 3. 寫入記錄
        jdbcTemplate.update(
            "INSERT INTO t_serial(prefix, biz_date, serial_no, created_at) VALUES(?,?,?,NOW())",
            prefix, bizDate, nextSeq);

        return serial;
    }
}

總結

本文提供的 SerialNum 類別適合單機、單進程、無重啟需求的簡易場景。企業級應用務必結合資料庫或分布式協調服務,才能確保單號在任何故障情境下的全域唯一性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved