In relational databases, every table needs a primary key — a unique identifier for each row. In Java applications, when we use JPA (Java Persistence API) as our ORM (Object Relational Mapping) framework, primary keys play an equally important role. They ensure each entity instance can be uniquely identified within the persistence context, much like students being identified by their roll numbers in a classroom attendance register.
But how do we let JPA (and underlying providers like Hibernate) generate these keys efficiently? That’s where @Id
and @GeneratedValue
come into play. This tutorial explores primary key mappings, generation strategies, CRUD operations, and real-world best practices so you can write production-ready JPA code.
Defining Primary Keys in JPA
Every entity must declare a primary key using the @Id
annotation:
import jakarta.persistence.*;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
// Getters and setters
}
Key Points:
@Entity
marks the class as a JPA entity.@Table
specifies the table name (optional, defaults to class name).@Id
declares the primary key field.@GeneratedValue
defines how the value is generated.
Primary Key Generation Strategies
JPA provides multiple strategies via @GeneratedValue(strategy = ...)
.
1. AUTO (Default)
Delegates decision to the persistence provider (Hibernate chooses based on database dialect).
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
- Flexible but may lead to portability issues when switching databases.
2. IDENTITY
Uses the database identity column (auto-increment).
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
- Simple and efficient.
- Insert happens immediately (no batching).
- Common in MySQL, SQL Server.
3. SEQUENCE
Uses a database sequence object. Preferred in Oracle, PostgreSQL.
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq")
@SequenceGenerator(name = "user_seq", sequenceName = "user_sequence", allocationSize = 1)
private Long id;
- Supports allocationSize for performance optimization.
- Allows pre-allocation of IDs.
4. TABLE
Stores sequence numbers in a dedicated table.
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "user_table_gen")
@TableGenerator(name = "user_table_gen", table = "id_gen", pkColumnName = "gen_name", valueColumnName = "gen_value")
private Long id;
- Portable but slow (extra table access).
- Rarely used in modern systems.
CRUD Operations with EntityManager
EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-pu");
EntityManager em = emf.createEntityManager();
// Create
em.getTransaction().begin();
User u = new User();
u.setUsername("ashwani");
u.setEmail("ashwani@example.com");
em.persist(u);
em.getTransaction().commit();
// Read
User found = em.find(User.class, u.getId());
// Update
em.getTransaction().begin();
found.setEmail("newmail@example.com");
em.merge(found);
em.getTransaction().commit();
// Delete
em.getTransaction().begin();
em.remove(found);
em.getTransaction().commit();
Performance Considerations
- IDENTITY prevents batch inserts → avoid if batching is critical.
- SEQUENCE with
allocationSize > 1
improves performance significantly. - TABLE strategy is slow; prefer SEQUENCE or IDENTITY.
📌 JPA Version Notes
- JPA 2.0: Introduced Criteria API, Metamodel.
- JPA 2.1: Added Stored procedures, Entity graphs.
- Jakarta Persistence (EE 9/10/11): Package renamed from
javax.persistence
tojakarta.persistence
.
Best Practices
- Always define explicit generation strategy for portability.
- Prefer SEQUENCE for performance (if DB supports).
- Avoid TABLE unless cross-database compatibility is critical.
- Tune allocationSize for high-insert systems.
- Use surrogate keys (numeric, UUID) rather than natural keys.
Real-World Analogy
Think of SEQUENCE as a token counter at a railway station: you pull tokens in batches, which speeds up processing. IDENTITY is like taking tickets one by one at the counter, slower but simpler.
FAQ
1. What’s the difference between JPA and Hibernate?
JPA is a specification, Hibernate is an implementation.
2. How does JPA handle the persistence context?
It caches managed entities, like a classroom register tracking present students.
3. What are the drawbacks of eager fetching in JPA?
Leads to unnecessary joins and memory overhead.
4. How can I solve the N+1 select problem with JPA?
Use JOIN FETCH
, entity graphs, or batch fetching.
5. Can I use JPA without Hibernate?
Yes, with providers like EclipseLink, OpenJPA.
6. What’s the best strategy for inheritance mapping in JPA?
Depends: JOINED for normalization, SINGLE_TABLE for performance.
7. How does JPA handle composite keys?
With @EmbeddedId
or @IdClass
.
8. What changes with Jakarta Persistence?
Switch package from javax.persistence
→ jakarta.persistence
.
9. Is JPA suitable for microservices?
Yes, but consider DTOs and projections for performance.
10. When should I avoid using JPA?
For high-performance batch processing or specialized NoSQL use cases.
Conclusion and Key Takeaways
- Primary keys are the backbone of JPA entities.
@GeneratedValue
offers multiple strategies, each suited to specific databases.- For production: prefer
SEQUENCE
(with allocationSize tuning). - Always test your strategy against your DB for performance and portability.
By mastering primary keys and generation strategies, you ensure your JPA applications remain robust, scalable, and efficient.