|

Generate Primary Keys Using JPA and Hibernate


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


How do you get the primary key values in your application? Do you use natural keys or do you generate technical IDs?

I prefer to generate simple, numerical, technical IDs like you can see in the following code snippet instead of using natural keys which often require the combination of multiple attributes.

@Id
private Long id;

Technical IDs are easier to manage and all involved systems, mainly the database and Hibernate, can index them very efficiently. This allows you to focus on the business logic of your application and avoids performance issues.

4 options to generate primary keys

The JPA specification supports 4 different primary key generation strategies which generate the primary key values programmatically or use database features, like auto-incremented columns or sequences. The only thing you have to do is to add the @GeneratedValue annotation to your primary key attribute and choose a generation strategy.

@Id
@GeneratedValue
private Long id;

GenerationType.AUTO

The GenerationType.AUTO is the default generation type and lets the persistence provider choose the generation strategy.

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

If you use Hibernate as your persistence provider, it selects a generation strategy based on the database specific dialect. For most popular databases, it selects GenerationType.SEQUENCE which I will explain later.  

GenerationType.IDENTITY

The GenerationType.IDENTITY is the easiest to use but not the best one from a performance point of view. It relies on an auto-incremented database column and lets the database generate a new value with each insert operation. From a database point of view, this is very efficient because the auto-increment columns are highly optimized, and it doesn’t require any additional statements.

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

This approach has a significant drawback if you use Hibernate. Hibernate requires a primary key value for each managed entity and therefore has to perform the insert statement immediately. This prevents it from using different optimization techniques like JDBC batching.

GenerationType.SEQUENCE

The GenerationType.SEQUENCE is my preferred way to generate primary key values and uses a database sequence to generate unique values.

It requires additional select statements to get the next value from a database sequence. But this has no performance impact for most applications. And if your application has to persist a huge number of new entities, you can use some Hibernate specific optimizations to reduce the number of statements.

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;

If you don’t provide any additional information, Hibernate will request the next value from its default sequence. You can change that by referencing the name of a @SequenceGenerator in the generator attribute of the @GeneratedValue annotation. The @SequenceGenerator annotation lets you define the name of the generator, the name, and schema of the database sequence and the allocation size of the sequence.

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "book_generator")
@SequenceGenerator(name="book_generator", sequenceName = "book_seq", allocationSize=50)
private Long id;

GenerationType.TABLE

The GenerationType.TABLE gets only rarely used nowadays. It simulates a sequence by storing and updating its current value in a database table which requires the use of pessimistic locks which put all transactions into a sequential order. This slows down your application, and you should, therefore, prefer the GenerationType.SEQUENCE, if your database supports sequences, which most popular databases do.

@Id
@GeneratedValue(strategy = GenerationType.TABLE)
private Long id;

You can use the @TableGenerator annotation in a similar way as the already explained @SequenceGenerator annotation to specify the database table which Hibernate shall use to simulate the sequence.

@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "book_generator")
@TableGenerator(name="book_generator", table="id_generator", schema="bookstore")
private Long id;

Summary

As you’ve seen, JPA offers 4 different ways to generate primary key values:

  1. AUTO: Hibernate selects the generation strategy based on the used dialect,
  2. IDENTITY: Hibernate relies on an auto-incremented database column to generate the primary key,
  3. SEQUENCE: Hibernate requests the primary key value from a database sequence,
  4. TABLE: Hibernate uses a database table to simulate a sequence.

I prefer to use the GenerationType.SEQUENCE because it is very efficient and allows Hibernate to decide when to perform the insert statement. This provides the required flexibility to use other performance optimization techniques like JDBC batching.
When you like to learn more about performance tuning and how Hibernate can optimize the GenerationType.SEQUENCE, have a look at my Hibernate Performance Tuning Online Training.

12 Comments

  1. Avatar photo Birhane Tinsae says:

    very interesting article,
    I’m struggling to use @GeneratedValue on none Id column with custom sequence generator how is that possible?

  2. Hi, nice article!
    Reviewing our legacy application, with about 100 entity classes, there is a mixture of @GeneratedValue strategies, including lots of Table strategies. I’d like to make them all use Sequence now on the basis of this article. Can I just replace the annotations, or will this risk duplicate primary key errors in production, in tables where id generation strategy has changed ? The database is MySQL 5.7
    Thanks Richard

    1. Avatar photo Thorben Janssen says:

      Hi Richard,

      you can replace the annotations if you make sure that your database sequence only returns values that are higher than the previously used ones.

      Regards,
      Thorben

  3. I am upgrading hibernate from 3.5 to 5.1.15.
    Earlier i was using custom table generator to generate primary key.
    I am using mysql database, i suppose it does not support sequence.
    Due to hibernate 5 chnages, my code for custom table generation is failing.
    More detail is in this hibernate forum.
    https://discourse.hibernate.org/t/custom-table-generator-public-integraldatatypeholder-getnextvalue/1639

    I need to know 2 things
    1. Should i replace table generation, if so how can i do it ?
    2. If i need to retain, custom table generation what should i do in getNextValue() as doWorkInNewTransaction is deprecated.

    1. Avatar photo Thorben Janssen says:

      Hi Sandeep,

      You should the GenerationType.IDENTITY with an autoincremented database column instead of the table generator.

      Regards,
      Thorben

  4. How can i get the next auto increament number?

    1. Avatar photo Thorben Janssen says:

      You don’t need to worry about that. Your database returns it in the response the SQL INSERT statement and Hibernate automatically sets it on your entity.

  5. Avatar photo Robert Oschwald says:

    With Hibernate 5.x, GenerationType.AUTO leads to the TABLE generator being used on MySQL DBs. This has performance impacts and it is not working with Galera cluster, as Galera needs a PK on every table, but the hibernate_sequence table does not define a PK.

    Setting
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator=”native”)
    @GenericGenerator(name = “native”, strategy = “native”)

    would use the IDENTITY generator on MySQL, but that leads to other problems when using TABLE_PER_CLASS, as MySQL does not support GenerationType.SEQUENCE.

    See https://hibernate.atlassian.net/browse/HHH-11014

  6. Hello

    What if I have a table with a unique ID field (PK Key) that is entered manually (or via a code), and for entering a new record, next record pk is set MAX(ID) + 1.

    How do I annotate the entity so that jpa will let me insert (save) a record while allowing the id value to be provided as part of the post?

    Thank you

    1. Avatar photo Thorben Janssen says:

      Hi Mark,

      just skip the @GeneratedValue annotation, if you want to set the primary key value programmatically.

      Regards,
      Thorben

Comments are closed.