業務系統常需產生唯一且有意義的單號,格式通常為 前綴 + 年月日 + 流水號(如 CB20240115001)。本文實作一個執行緒安全的產生器,支援跨日歸零與從現有單號延續。
SerialNumprivate static String count = "000"; // 當日流水號
private static String dateValue = "20131115"; // 最後產生日期
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;
}
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();
}
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,確保多執行緒環境下流水號不重複、不跳號。
重要警示:上述實作將狀態儲存在靜態變數中。若應用程式重啟、伺服器故障或叢集部署,會導致流水號從頭開始,產生重複單號。
為保證絕對唯一,建議採用「資料庫為主、記憶體為輔」的雙重機制:
getMoveOrderNo(lastSerial) 同步記憶體狀態@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 類別適合單機、單進程、無重啟需求的簡易場景。企業級應用務必結合資料庫或分布式協調服務,才能確保單號在任何故障情境下的全域唯一性。