Primary Keys and Generation Strategies
The Four Strategies
| Strategy | How the id is produced | Databases | Batch inserts |
|---|---|---|---|
IDENTITY | Auto-increment column; id known only after INSERT | MySQL, PostgreSQL, SQL Server, H2 | No (each insert runs immediately) |
SEQUENCE | Database sequence object; ids fetched in blocks | PostgreSQL, Oracle, H2, SQL Server | Yes (best performance) |
TABLE | A special table holds the next value | Any | Slow, avoid |
UUID (JPA 3.1) | Random UUID generated in Java | Any | Yes |
AUTO (default) | Hibernate chooses; in Hibernate 6 this is a sequence (emulated by a table on MySQL) | Any | Depends |
Choosing per Database
// MySQL: use IDENTITY (MySQL has no sequences)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// PostgreSQL / Oracle: use a SEQUENCE
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_seq")
@SequenceGenerator(name = "product_seq", sequenceName = "product_seq", allocationSize = 50)
private Long id;
// Public, non-guessable ids (e.g. order ids in URLs): UUID
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;Sequence log
SQL Hibernate runs on PostgreSQL with the sequence strategy:
-- persisting 3 productsExample output
Hibernate: create sequence product_seq start with 1 increment by 50
Hibernate: select nextval('product_seq')
Hibernate: insert into products (category,description,name,price,stock,id) values (?,?,?,?,?,?)
Hibernate: insert into products (category,description,name,price,stock,id) values (?,?,?,?,?,?)
Hibernate: insert into products (category,description,name,price,stock,id) values (?,?,?,?,?,?)Note: With plain @GeneratedValue (AUTO) on MySQL, Hibernate 6 creates an extra table products_seq and runs select next_val ... for update to emulate a sequence. Most MySQL projects therefore write IDENTITY explicitly.
How allocationSize Saves Round Trips
Example
With allocationSize = 50, one sequence call reserves a block of 50 ids, which Hibernate hands out from memory. This simulation persists 120 products:
public class Main {
public static void main(String[] args) {
Sequence dbSequence = new Sequence(50);
PooledIdGenerator gen = new PooledIdGenerator(dbSequence, 50);
for (int i = 1; i <= 120; i++) {
long id = gen.nextId();
if (i <= 2 || i == 50 || i == 51 || i == 120) System.out.println("product #" + i + " -> id " + id);
}
System.out.println("Database round trips for ids: " + dbSequence.calls);
}
}
class Sequence { // the database object
private long next = 1;
private final int increment;
int calls = 0;
Sequence(int increment) { this.increment = increment; }
long nextval() { calls++; long v = next; next += increment; return v; }
}
class PooledIdGenerator { // what Hibernate keeps in memory
private final Sequence seq;
private final int size;
private long current, max;
PooledIdGenerator(Sequence seq, int size) { this.seq = seq; this.size = size; }
long nextId() {
if (current == 0 || current > max) {
current = seq.nextval();
max = current + size - 1;
}
return current++;
}
}Output
product #1 -> id 1 product #2 -> id 2 product #50 -> id 50 product #51 -> id 51 product #120 -> id 120 Database round trips for ids: 3
Tip: The sequence increment by in the database must match allocationSize, otherwise Hibernate fails validation or produces duplicate ids.
Natural vs Surrogate Keys
A surrogate key (id generated by the database) has no business meaning. A natural key (email, SKU, PAN number) does. Prefer surrogate keys as the primary key and protect natural keys with a unique constraint — business values can change (a user changes email), primary keys should not.