Designing an order number generation system in Spring Boot mainly considers several requirements that the generated order number needs to meet: uniqueness, scalability, and possible business relevance. Here are some common solutions and corresponding sample code:
UUID
The simplest method is to use a UUID to generate a unique order number. UUID (Universally Unique Identifier) is a widely used identifier, composed of 128 bits, usually represented by 32 hexadecimal digits divided into five groups, in an 8-4-4-4-12 string format, e.g., 123e4567-e89b-12d3-a456–426614174090. UUID is globally unique, easy to implement, but the downside is that UUID is long, not easy to remember, and store.
Sample Code
Here is a sample code in Java to generate a UUID:
import java.util.UUID;public class UUIDGenerator {public static String generateUUID() {// Generate a UUIDUUID uuid = UUID.randomUUID();// Convert the UUID to a stringString uuidAsString = uuid.toString();// Return the UUID stringreturn uuidAsString;}public static void main(String[] args) {String uuid = generateUUID();System.out.println("Generated UUID: " + uuid);}}
Database Sequence or Auto-Increment ID
Use the database sequence (like PostgreSQL’s SEQUENCE) or auto-increment ID(like MySQL’s AUTO_INCREMENT) to generate unique order numbers. Database sequence or auto-increment ID is a common way to generate unique identifiers, especially in monolithic applications or non-distributed systems.
This method relies on the built-in mechanism of the database to ensure that a unique identifier is automatically generated each time a new record is inserted. The disadvantage is that it is difficult to maintain uniqueness in a distributed environment.
//Assume using JPA@Entitypublic class order {@Id@GeneratedValue(strategy= GenerationType.AUTO)private Long id;// other properties}
Database Sequence (like PostgreSQL’s SEQUENCE)
CREATE SEQUENCE order_id_seq START WITH 1 INCREMENT BY 1;CREATE TABLE orders(order_id bigint NOT NULL DEFAULT nextval('order_id_seq'), order_data text);
Auto-Increment ID (like MySQL’s AUTO INCREMENT)
CREATE TABLE orders (order_id INT AUTO_INCREMENT,order_data TEXT,PRIMARY KEY (order_id));
Timestamp + Random Number/Sequence
Combine the timestamp and the random number (or the custom sequence) to generate unique and readable order numbers. You can enhance business relevance by adding a business-related prefix.
Sample Code
Here is a simple Java example showing how to generate an order number by combining a timestamp, a random number, and a business prefix:
import java.text.SimpleDateFormat;import java.util.Date;import java.util.concurrent.ThreadLocalRandom;public class OrderNumberGenerator {private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");private static final int RANDOM_NUM_BOUND = 10000; // Define the random number rangepublic static String generateOrderNumber(String prefix) {// Generate the timestamp partString timestamp = dateFormat.format(new Date());// Generate the random number partint randomNumber = ThreadLocalRandom.current().nextInt(RANDOM_NUM_BOUND);// Combine into an order numberreturn prefix + timestamp + String.format("%04d", randomNumber);}public static void main(String[] args) {// Example: generate an order number, suppose the business prefix is "ORD"String orderNumber = generateOrderNumber("ORD");System.out.println("Generated Order Number: " + orderNumber);}}
Distributed Unique ID Generation Solution
In a distributed system, you can use algorithms like Twitter’s Snowflake to generate unique IDs.
Snowflake algorithm can generate a 64-bit long integer, which includes timestamp, data center ID, and machine ID, ensuring the generated ID is both unique and sorted.
Snowflake ID Structure
The 64-bit ID generated by Snowflake can be divided into the following parts:
- 1-bit sign bit: Since the highest bit in an integer is the sign bit, and the highest bit in a 64-bit integer is the sign bit, and the highest bit in a 64-bit integer is the sign bit, this bit is usually set to 0 to ensure the ID is positive.
- 41-bit timestamp bit: Records the difference of the timestamp (relative to a fixed point in time), in milliseconds. A 41-bit timestamp (relative to a fixed point in time, in milliseconds. A 41-bit timestamp can be used for 69years.
- 10-bit data center ID and machine ID: Usually divided into a 5-bit data center ID and a 5-bit machine ID, supporting up to 32 data centers, each data center supporting up to 32 machines.
- 12-bit sequence number: Used to distinguish different IDs generated within the same millisecond. The 12-bit sequence number supports each node generating 4096 ID numbers per millisecond
Here is a simplified example of the Snowflake algorithm implementation:
public class SnowflakeIdGenerator {private long datacenterId; // Data center IDprivate long machineId; // Machine IDprivate long sequence = 0L; // Sequence numberprivate long lastTimestamp = -1L; // Last timestampprivate final long twepoch = 1288834974657L;private final long datacenterIdBits = 5L;private final long machineIdBits = 5L;private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);private final long maxMachineId = -1L ^ (-1L << machineIdBits);private final long sequenceBits = 12L;private final long machineIdShift = sequenceBits;private final long datacenterIdShift = sequenceBits + machineIdBits;private final long timestampLeftShift = sequenceBits + machineIdBits + datacenterIdBits;private final long sequenceMask = -1L ^ (-1L << sequenceBits);public SnowflakeIdGenerator(long datacenterId, long machineId) {if (datacenterId > maxDatacenterId || datacenterId < 0) {throw new IllegalArgumentException("datacenterId can't be greater than %d or less than 0");}if (machineId > maxMachineId || machineId < 0) {throw new IllegalArgumentException("machineId can't be greater than %d or less than 0");}this.datacenterId = datacenterId;this.machineId = machineId;}public synchronized long nextId() {long timestamp = System.currentTimeMillis();if (timestamp < lastTimestamp) {throw new RuntimeException("Clock moved backwards. Refusing to generate id");}if (lastTimestamp == timestamp) {sequence = (sequence + 1) & sequenceMask;if (sequence == 0) {timestamp = tilNextMillis(lastTimestamp);}} else {sequence = 0L;}lastTimestamp = timestamp;return ((timestamp - twepoch) << timestampLeftShift) |(datacenterId << datacenterIdShift) |(machineId << machineIdShift) |sequence;}private long tilNextMillis(long lastTimestamp) {long timestamp = System.currentTimeMillis();while (timestamp <= lastTimestamp) {timestamp = System.currentTimeMillis();}return timestamp;}}
Here is a line-by-line explanation of this code:
Class Definition and Variable Initialization
- private long datacenterId; Define the data center ID.
- private long machineId; Define the machine ID.
- private long sequence = 0L; Sequence number, used to distinguish different IDs generated within the same millisecond.
- private long lastTimestamp = -1L; The timestamp when the last ID was generated.
The following are some key parameters of the Snowflake algorithm:
- private final long twepoch = 1288834974657L; The system’s start timestamp, here is a fixed time point chosen by the author of the Snowflake algorithm (2010–11–04 09:42:54.657 GMT).
- private final long datacenterIdBits = 5L; The number of bits occupied by the data center ID.
- private final long machineIdBits = 5L; The number of bits occupied by the machine ID.
- private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); The maximum value of the data center ID, here calculated by bit operation.
- private final long maxMachineId = -1L ^ (-1L << machineIdBits); The maximum value of the machine ID, also obtained by bit operation.
- private final long sequenceBits = 12L; The number of bits occupied by the sequence number.
The following are some parameters for bit operation, used to calculate the final ID:
- private final long machineIdShift = sequenceBits; The offset bit number of the machine ID.
- private final long datacenterIdShift = sequenceBits + machineIdBits; The offset bit number of the data center ID.
- private final long timestampLeftShift = sequenceBits + machineIdBits + datacenterIdBits; The offset bit number of the timestamp.
- private final long sequenceMask = -1L ^ (-1L << sequenceBits); Used to ensure the sequence number cycles within a specified range.
Constructor
- The constructor SnowflakeIdGenerator(long datacenterId, long machineId) accepts the data center ID and machine ID as parameters and checks these parameters to ensure they are within the legal range.
ID Generation Method
- public synchronized long nextId() is the core method to generate ID. It uses synchronized to ensure thread safety.
- First, it gets the current timestamp.
- If the current timestamp is less than the last timestamp when the ID was generated, an exception is thrown, because clock rollback will lead to ID duplication.
- If the current timestamp is equal to the last timestamp (i.e., within the same millisecond), different IDs are generated by increasing the sequence number; if the sequence number overflows (exceeds the maximum value), it waits until the next millisecond.
- If the current timestamp is greater than the last timestamp, the sequence number is reset to 0.
- Finally, it left-shifts the timestamp, data center ID, machine ID, and sequence number by their respective offset amounts, then