JPA ID Generation and Sequence Management - Yash-777/MyWorld GitHub Wiki

📘 JPA ID Generation with MySQL: Full Guide with Transaction & Batch Uploads

🔍 Overview: This document explains how JPA/Hibernate handles ID generation with different strategies, especially in MySQL. It covers:

  • ID strategies (IDENTITY vs SEQUENCE)
  • 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)
⚠️ Emulated, not recommended
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 or duplicate 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 ⚠️ Temporary fix only
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 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.

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 insertsslower 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() or EntityManager.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.

  1. 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 or batch 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 or duplicate 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 ⚠️ Risky, causes mismatch

🧾 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 and SEQUENCE 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.

  1. 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.

  1. 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);
}
  1. 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();
    }
}

⚠️ Important Considerations

🔹 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").

⚠️ **GitHub.com Fallback** ⚠️