JPA ID Generation and Sequence Management - Yash-777/MyWorld GitHub Wiki
🔍 Overview: This document explains how JPA/Hibernate handles ID generation with different strategies, especially in MySQL. It covers:
- ID strategies (
IDENTITY
vsSEQUENCE
) - Transactions and rollback behavior
- Batch uploads
- Sequence mismatch problems
- Common exceptions
- Best practices and real-world examples
🧾 ID Generation Strategies in JPA Hibernate ORM Documentation
Strategy | Annotation@GeneratedValue(strategy = ? )
|
Description | DB Support | Use in MySQL |
---|---|---|---|---|
IDENTITY | 🔹 GenerationType.IDENTITY * Uses the database's auto-increment feature (like AUTO_INCREMENT in MySQL).* ID is generated by the database at insert time. * No preallocation, so Hibernate can’t batch inserts. |
Uses DB’s AUTO_INCREMENT feature |
✅ MySQL, Oracle (12c+), PostgreSQL |
✅ Best choice Native support, best transaction behavior |
SEQUENCE | 🔹 GenerationType.SEQUENCE * Uses a database sequence object (supported by databases like PostgreSQL, Oracle). * Hibernate can pre-fetch IDs, enabling insert batching. * Not supported in MySQL (unless emulated). |
Uses DB sequence object |
✅ Oracle, PostgreSQL 🚫 MySQL (emulated via table) |
|
TABLE | 🔹 GenerationType.TABLE * Simulates sequence behavior using a special table to store the next ID. * Portable across all databases, including MySQL. * Slower due to extra reads/writes; avoids native DB features. |
Uses a separate table to manage IDs |
All DBs (portable, slow) | 🚫 Not used in production |
⚠️ Problem Scenario: Using SEQUENCE Strategy in MySQL Can Lead to ID Mismatches and Errors
- Since MySQL doesn’t support sequences, Hibernate emulates sequences for MySQL, but this can cause inconsistencies in ID values.
- In transactional rollbacks, sequence values might increase but the actual record insertion rolls back, causing gaps and mismatches.
- This can trigger exceptions like
DataIntegrityViolationException
orduplicate key violations
.
👇 Given:
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "idGeneratorSeq")
@SequenceGenerator(
name = "idGeneratorSeq",
sequenceName = "adhocpayment_sequence",
allocationSize = 1,
initialValue = 6500
)
@Column(name = "Id")
private Long id;
👇 DB Data:
CREATE TABLE `adhocpayment_sequence` (
`next_val` bigint DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `adhocpayment_sequence` VALUES (5);
SELECT MAX(Id) FROM adhocpayment; -- 6311
SELECT * FROM adhocpayment_sequence; -- next_val = 6409
❌ Exception Thrown:
org.hibernate.exception.ConstraintViolationException: could not execute statement;
nested exception is javax.persistence.PersistenceException: Duplicate entry '6223' for key 'adhocpayment.PRIMARY'
🧠 Root Cause
Problem | Why It Happens |
---|---|
Duplicate entry error | Hibernate tries to use a manually set or stale ID |
next_val > MAX(id) | Hibernate emulates sequence using a table; values increase even on rollback |
Missing inserts | Batch operation failed mid-way but sequence continued |
Manual ID setting | Using setId(someId) bypasses JPA's generator |
✅ Solution Summary
Problem | Solution | Recommended? |
---|---|---|
next_val ahead of actual ID | Manually reset the sequence table | |
Mismatch from batch failures | Use IDENTITY strategy and AUTO_INCREMENT
|
✅ Best for MySQL |
Manually setting ID causes duplicates | Set id = null before persist | ✅ Yes |
Emulated sequence in MySQL leads to mismatch | Avoid SEQUENCE strategy in MySQL |
✅ Strongly recommended |
using
GenerationType.IDENTITY
with MySQL prevents Hibernate frombatching INSERT
statements, because it has to wait for the database to return each generated ID before moving on. This can significantlyreduce performance for bulk inserts
.
When you use IDENTITY
to automatically generate IDs in your database (like letting the database auto-increment a number for each new row), Hibernate can't group multiple insert operations together. This is called batching—sending many inserts at once, which is faster.
Why? Because with IDENTITY
, Hibernate has to wait for the database to give back the ID of each inserted row before it can move to the next one. That slows things down and prevents batching.
But how important this is depends on your app:
- If your app is not creating lots of new rows of the same type at once, then this isn’t a big deal.
- If your app does create a lot of new rows quickly, then this could hurt performance.
✅ Problem Recap:
-
IDENTITY
(AUTO_INCREMENT
): Hibernate can’t preallocate IDs. - Result:
No JDBC batching for inserts
→slower performance
when inserting many rows.
✅ Workaround Idea (Manual ID Pre-Assignment for Batch Insert)
Instead of letting MySQL generate the IDs via AUTO_INCREMENT (GenerationType.IDENTITY), you:
- Manually fetch the next available ID (e.g. Get the current max ID from the existing table), then
- Assign new IDs in memory, and
- Batch insert all at once using
.saveAll()
orEntityManager.persist()
. Yes, this approach can work, with some caveats and best practices.
✅ TL;DR (Simple Summary):
-
ALTER TABLE ... AUTO_INCREMENT = 100
is native MySQL behavior. -
UPDATE ... SET next_val = MAX(id)+1
is used in systems that manually manage IDs, like Hibernate with sequence tables.
✅ 1. ALTER TABLE tbl AUTO_INCREMENT = 100;
(MySQL/MariaDB)
- This sets the next auto-increment value for a table.
- For example, if you're inserting rows into a table and it has an AUTO_INCREMENT column (like an id), the next inserted row will get id = 100.
➡️ This is native to MySQL/MariaDB and only works with auto-increment columns.
✅ 2. UPDATE adhocpayment_sequence SET next_val = (SELECT MAX(Id) + 1 FROM adhocpayment);
- This is manually updating a sequence table.
- It’s often used in systems where auto-increment behavior is simulated by the application or framework (like Hibernate’s TABLE generator strategy).
- Here, you're manually setting the next value for a sequence by checking the max ID already used.
➡️ This is not database-native auto-increment—it's more of an application-managed workaround.
Example: Works only in MySQL/MariaDB (not standard SQL).
CREATE TABLE adhocpayment (
id INT AUTO_INCREMENT PRIMARY KEY,
amount DECIMAL(10,2)
);
INSERT INTO adhocpayment (amount) VALUES (50.00), (75.00); -- IDs 1 and 2
ALTER TABLE adhocpayment AUTO_INCREMENT = 100;
INSERT INTO adhocpayment (amount) VALUES (100.00); -- ID will be 100
Why Use AUTO_INCREMENT
(IDENTITY
) Instead of SEQUENCE
in MySQL?
Important
Recommended Setup for MySQL(the best, safest, and most consistent strategy): Use GenerationType.IDENTITY
with native AUTO_INCREMENT
instead of emulated sequences.
- MySQL Does Not Support SEQUENCE Natively
- MySQL (up to version 8.x) does not have native support for sequence generators like Oracle or PostgreSQL.
- The
@SequenceGenerator
annotation is mostly used with databases supporting sequences (Oracle, PostgreSQL
). - In MySQL, ID generation is handled via
AUTO_INCREMENT
columns (i.e.,GenerationType.IDENTITY in JPA
)
Reference:
Hibernate documentation: "The SEQUENCE strategy requires the underlying database to support sequences. MySQL does not support sequences natively, so using GenerationType.SEQUENCE will not work properly." Hibernate ORM Documentation
🛠 Entity Class (Java)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "Id")
private Long id;
🛠 MySQL Table
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE adhocpayment
MODIFY COLUMN Id BIGINT NOT NULL AUTO_INCREMENT;
This ensures IDs are generated atomically and natively by MySQL, avoiding transaction and rollback inconsistencies.
This setup ensures:
- No need for separate
adhocpayment_sequence table
- Auto-handling of IDs by MySQL
- No risk of mismatches on
rollback
orbatch failure
Using SEQUENCE Strategy in MySQL Can Lead to ID Mismatches and Errors
Important
⚒️ If you must continue using the current setup (not recommended for MySQL) (Temporary Fix: Sequence mismatch, gaps, batch rollback issues
)
- Since MySQL doesn’t support sequences, Hibernate emulates sequences for MySQL, but this can cause inconsistencies in ID values.
- In transactional rollbacks, sequence values might increase but the actual record insertion rolls back, causing gaps and mismatches.
- This can trigger exceptions like
DataIntegrityViolationException
orduplicate key violations
.
🔄 Manually Sync Sequence Table
UPDATE adhocpayment_sequence
SET next_val = (SELECT MAX(Id) + 1 FROM adhocpayment);
Use this after failed uploads or when next_val
is clearly ahead of actual data.
Note: This is only a temporary fix. The mismatch will return on failed inserts or rollbacks.
Important
🧪 Always Set id = null
Before Persist
AdhocPayment payment = new AdhocPayment();
//payment.setId(6223); // ❌ Bad: Hibernate will try to use this ID directly
payment.setId(null); // ✅ Let Hibernate generate ID via sequence or auto-increment
entity.setAmount(100.00);
entityManager.persist(entity);
Tip
🔥 Why?
If id != null
, Hibernate assumes it’s an update, or tries to insert with that ID, bypassing auto-generation.
Real Example: Batch Upload Causing Mismatch - If you manually set id or copy it from another object (e.g., from DTO), you may bypass the generator logic.
🔄 Batch Insert Behavior by Strategy
Behavior | IDENTITY (MySQL) | SEQUENCE (MySQL Emulated) |
---|---|---|
ID Generation | DB auto-increment
|
Hibernate queries sequence table |
Transaction-safe | ✅ Yes | ❌ No — sequence still increments on rollback |
Gaps in ID | ❌ Rare | ✅ Frequent due to rollbacks/failures |
Batch support | ✅ Full |
🧾 Use Case Comparison
Use Case | Best Strategy | Why |
---|---|---|
MySQL Database |
GenerationType.IDENTITY (AUTO_INCREMENT)
|
Native support, best transaction behavior |
Oracle or PostgreSQL | SEQUENCE |
Native sequence support |
Batch processing (MySQL) | IDENTITY |
Avoids sequence mismatch |
Data migration with known ID ranges |
SEQUENCE with initialValue |
Set starting point after migration |
Legacy system needing ID control |
SEQUENCE (Oracle/PostgreSQL only) |
Allows custom ID ranges |
🧼 Best Practices
✅ ALWAYS:
- Set
id = null
before persisting a new entity,Let DB handle ID generation wherever possible
- Use
IDENTITY
for MySQL andSEQUENCE
for Oracle/PostgreSQL - Sync sequence table manually only if using
SEQUENCE
in MySQL (not recommended)
❌ NEVER:
- Copy or reuse existing IDs when creating new entities
- Use
GenerationType.SEQUENCE
in MySQL unless you understand the emulation risks - Ignore
sequence/table
sync after a batch failure or rollback
Batch Inserts with MySQL: Workaround Idea (Manual ID Pre-Assignment for Batch Insert)
using GenerationType.IDENTITY
with MySQL prevents Hibernate from batching INSERT
statements, because it has to wait for the database to return each generated ID before moving on. This can significantly reduce performance for bulk inserts.
- Get the Current Max ID
Use a query to find the current max value in the target table:
@PersistenceContext
private EntityManager em;
public Long getCurrentMaxId() {
return em.createQuery("SELECT COALESCE(MAX(e.id), 0) FROM MyEntity e", Long.class)
.getSingleResult();
}
You can also use a native SQL query if performance matters.
- Assign IDs in Memory
Let’s say you want to insert 100 new records:
List<MyEntity> entities = ... // your list of new entities
Long baseId = getCurrentMaxId() + 1;
for (int i = 0; i < entities.size(); i++) {
entities.get(i).setId(baseId + i);
}
- Batch Insert
Use Spring Data JPA:
repository.saveAll(entities); // Hibernate can batch this now
Or with JPA manually:
for (int i = 0; i < entities.size(); i++) {
em.persist(entities.get(i));
if (i % batchSize == 0) {
em.flush();
em.clear();
}
}
🔹 Concurrency Risk
If multiple threads/processes do this at the same time, they might assign overlapping IDs (since MAX(id) doesn’t lock rows). To avoid this:
- ⚙️ Use synchronization at the application level (e.g. synchronized method or lock).
you can adjust the next auto-increment value after the batch
:
ALTER TABLE my_entity AUTO_INCREMENT = <max_inserted_id> + 1;
🧪 Example Entity (Corrected)
@Entity
@Table(name = "User")
public class User extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
// Implicit sequence
@Id @Column(name = "Id")
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
// Named sequence
@Id @Column(name = "Id")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "User_Sequence") // generator = sequence-generator
private Long id;
// Simple @SequenceGenerator with configuration
@Id @Column(name = "Id")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "idGeneratorSeq")
// generator = sequence-generator - reference to generator defined below
@SequenceGenerator(name = "idGeneratorSeq", sequenceName = "User_Sequence", initialValue = 6000, allocationSize = 1) // sequenceName = explicit_user_sequence
private Long id;
@ManyToMany
@JoinTable(
name = "user_address",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "address_id")
)
private List<Address> address;
}
Again the mapping specifies explicit_user_sequence
as the physical sequence name, but it also specifies an explicit allocation-size ("increment by").