I’ve have started building a banking project using Java, a Spring Boot application, and a PostgreSQL database. I want to generate a random 8 or 10-digit account number each time I create a new account. What’s the best way to achieve this?
Option 1: I can handle this in the Java service layer when creating a new account (but how can I ensure it’s unique?).
Option 2: I can design the database like this:
CREATE TABLE IF NOT EXISTS account (
id SERIAL PRIMARY KEY,
account_number VARCHAR(8) UNIQUE, -- Unique 8-character account number
-- Other columns for account details
);
Then create an SQL function to generate a random number like:
CREATE OR REPLACE FUNCTION generate_random_account_number()
RETURNS VARCHAR(8) AS $$
BEGIN
RETURN lpad(floor(random() * 100000000)::int::text, 8, '0');
END;
$$ LANGUAGE plpgsql;
Then insert it like:
INSERT INTO account (account_number, other_columns)
VALUES (generate_random_account_number(), 'other_data');
I’m not sure which approach is better to achieve this, and how I can ensure that the account numbers will be unique each time they are created.
PS: I don’t anticipate encountering issues with uniqueness because this is a hobby project, and I won’t be creating more than 100 accounts.
3
Answers
I would go with this way:
Option 1: I can handle this in the Java service layer when creating a new account
How can you ensure it’s unique?
Create the random account number and after creation, execute a database query which returns all datasets with the account number. If the amount of returned results is greater than 0, then create a new random account number and check again. Do this in a loop until the database query does not return any datasets.
Reasons for this answer are:
Tipps from an older iron
And now grow and be the best Version of yourself!
The first question is why you want that. If that number has to meet specific criteria, list them precisely. Does the number have to be truly random or just arbitrary? Why digits, and why 8 or 10 of those? Leading zeros are ok? Significant? Etc.
Why the additional surrogate PK
id
? Isn’taccount_number
a natural key?Why
varchar(8)
if it’s just digits?Think of multiple accounts created concurrently by different clients. The database can avoid conflicts reliably, as opposed to the client.
Unless there are specific criteria, use a
bigint
IDENTITY
column. An arbitrary, but not random number. Leading zeros are insignificant noise:See:
10 digits is one too many for plain
integer
. Sobigint
.I threw in a
CHECK
constraint to enforce your number range.To insert and get back the generated
account_id
in one step:If you need a 10-digit string format, you can always format the number for presentation. Like
to_char(account_id, '0000000000')
, orlpad(account_id::text, 10, '0')
– which is better because immutable. You can even add a generated column to the table if you must:See:
Truly random
If you need truly random numbers, the inherent weakness is that conflicts are possible in principle.
Then:
UUID
To make conflicts practically impossible, use
uuid
values instead of your 10-digit number. Those can be generated in the client or in the database as you like. They are just a bit unwieldy, and occupy 16 bytes. About the likelihood of conflicts, consider the chapter "Probability of a hash collision?" here:You need a function that reliably generates 10-digit numbers from sequential inputs and which is seemingly random, that is, someone not knowing your function will not know which numbers follow the previous. A way to achieve it is to define a pattern. Let’s say that we have a 10-digit multiple of large numbers, like 9754031400
which is
12345 × 98765 × 8
So, we can have
1 + (98765 * 1), 1 + (98765 * 2), …, 1 + (98765 * 98759)
as our first set of numbers (multiples of 98765 + 1 below 9754031400). When we run out of numbers in this interval, we can continue the series with adding 2 to the multiples:
2 + (98765 * 1), 2 + (98765 * 2), …, 2 + (98765 * 98759)
and so on. Admittedly, this is not obfuscated-enough yet. So we can shuffle the digits. If the digits of the original number were initially
d1 d2 d3 d4 d5 d6 d7 d8 d9 d10
then you can change their order for the purpose of obfuscating the results to
d10 d7 d5 d6 d3 d4 d8 d1 d9 d2
This would be really difficult to reverse-engineer. But if this is not enough, you can always add further levels of obfuscation. Since this is an algorithmic example and Javascript is the only language we can add as a snippet, I will illustrate this via Javascript, but you can implement this algorithm in Java or SQL without any problems