Enterprise Java Development@TOPIC@

Chapter 30. Primary Key Generation

30.1. IDENTITY Primary Key Generation Strategy
30.2. SEQUENCE Primary Key Generation Strategy
30.3. TABLE Primary Key Generation Strategy
30.4. Summary

This chapter will take you through the steps to setting up entity classes for three different primary key generation strategies:

There is a fourth strategy called AUTO and is the default. Since the job of most applications is to map to a known database schema, I would not consider this to be usable outside of quick prototypes like we went through in the previous sections. When using AUTO (the default), you are saying "I don't care what you do -- just get me a primary key value" when you actually do care for production code. Fore this reason I would suggest you always supply a strategy and not depend on the default always doing what you need.

Primary key generation has three fundamental requirements

  1. The primary key must be a simple key (ie., not compound)

  2. The data type of the primary key must be a numeric

  3. The primary key value of the entity must be "unassigned" when passed to persist. Any "assigned" value will cause the provider to through an exception since the entity is presumed to already have a primary key and is not in the proper state. Unassigned states are "0" for built-in numeric types (e.g., int, long) and null for Object numeric types (e.g., Integer, Long)

Lets start with the easiest case. For IDENITY, the primary key is generated by the database on a per-table basis. Thus it is possible to have two separate entity instances with the same primary key value when mapped to two different tables.

  1. Put the following class in place in your src/main tree. The class has two properties; an ID and Name. The ID is being automatically generated using the IDENTITY strategy.

    
    
    package myorg.entityex.annotated;
    import javax.persistence.*;
    @Entity
    @Table(name="ENTITYEX_BUNNY")
    @Access(AccessType.FIELD)
    public class Bunny {
        @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
        private int id;
        private String name;
        
        public int getId() { return id; }
        public void setId(int id) {
            this.id = id;
        }
        
        public String getName() { return name; }
        public void setName(String name) {
            this.name = name;
        }
    }
  2. Add the new entity to the orm.xml file (and not the persistence.xml file). We are going to use primary key generation to demonstrate some points about metadata overrides in a moment.

    
    
    # src/main/resources/orm/Animal-orm.xml

        <entity class="myorg.entityex.annotated.Bunny"/>
  3. Build the module and notice the database schema generated for the entity using IDENTITY.

        create table ENTITYEX_BUNNY (
            id integer generated by default as identity,
            name varchar(255),
            primary key (id)
        );
    
  4. Add the following test method to the existing AnimalTest. Notice that it creates and persists several entities, checks they are received a unique primary key value, and prints all the assigned values at the end.

    
    
        @Test
        public void testPKGen() {
            logger.info("testPKGen");
            Bunny bunny = new Bunny();
            bunny.setName("fuzzy");
            assertTrue("primary key unexpectedly assigned", bunny.getId()==0);
            em.persist(bunny);
            em.flush();
            logger.info("bunny.getId()=" + bunny.getId());
            assertFalse("primary key not assigned", bunny.getId()==0);
            
            Set<Integer> ids = new HashSet<Integer>();
            ids.add(bunny.getId());
            for (String name: new String[]{"peter", "march hare", "pat"}) {
                Bunny b = new Bunny();
                b.setName(name);
                em.persist(b);
                em.flush();
                assertTrue("id not unique:" + b.getId(), ids.add(b.getId()));
            }
            logger.debug("ids=" + ids);
        }

    The em.flush() exists after the persist because we want to make sure the creation has been pushed to the database prior to the transaction committing because we want to check in while the transaction happens to be in progress.

    Note too that the entity client code does not really know the strategy used. We will take advatange of that in the next section.

  5. Build and test the primary key generation. Observe the following output and not assert errors.

    $ mvn test -Dtest=myorg.entityex.AnimalTest#testPKGen
    
     -testPKGen
    Hibernate: 
        insert 
        into
            ENTITYEX_BUNNY
            (id, name) 
        values
            (null, ?)
     -bunny.getId()=1
    ...
     -ids=[1, 2, 3, 4]
    

You have now successfully mapped an entity to the database using the IDENTITY strategy and it was pretty simple. The only down side to using this technique is that it is not portable to all database providers. Most notably, Oracle does not support the IDENTITY strategy.

In this section, we are going to change the primary key generation strategy through the use of the XML descriptor rather than changing the Java annotation. This is an example of how your application could be deployed with one version of the orm.xml for development and another for production when your development and production databases don't support the same features.

SEQUENCES are specialized constratucts databases have implemented to efficiently generate primary key values across tables. Using a common sequence means that two entities mapped to two separate database tables will not have the same primary key value (as long as the tables use the same sequence). We can define multiple sequences.

  1. Update the Animal-orm.xml definition for the entity to include a generated-value strategy of SEQUENCE.

    
    
        <entity class="myorg.entityex.annotated.Bunny">
            <attributes>
                <id name="id">
                    <generated-value strategy="SEQUENCE"/>
                </id>
            </attributes>
        </entity>
  2. Rebuild the module and observe the new schema generated for the entity no longer has the identity as part of the table definition and has added a sequence with the default name "hibernate-sequence"

       create table ENTITYEX_BUNNY (
            id integer not null,
            name varchar(255),
            primary key (id)
        );
    
        create sequence hibernate_sequence;
    
  3. Optionally remove the IDENTITY strategy from the entity class that was overridden by the XML file so that there is less confusion which technique is being used. This change should not impact any behavior since the definition in the XML file already has priority over the Java annotations.

    
    
        @GeneratedValue//(strategy=GenerationType.IDENTITY)
  4. Rebuild the module and observe the output from the test method already in place. Remember that -- since the client does not care which strategy is used -- we are able to change the implementation and re-run with a different strategy and not change the client.

    $ mvn test -Dtest=myorg.entityex.AnimalTest#testPKGen
    ...
     -testPKGen
    Hibernate: 
        call next value for hibernate_sequence
    Hibernate: 
        insert 
        into
            ENTITYEX_BUNNY
            (name, id) 
        values
            (?, ?)
     -bunny.getId()=10
    ...
     -ids=[10, 11, 12, 13]
    

    Notice that hibernate went to the database and obtained the next value to start with (which was 10 in this case) and was free to generate one-up numbers from that point within a window of values. Since hibernate has been told it is using a database specific dialect and can communicate with the database at runtime, you hope that the default values for the sequence are well understood. In the next step we will make this more explicit.

  5. Update the Animal-orm.xml entity definition to include a seguence-generator and generator reference from the strategy. The sequence-generator will have an internal JPA name, a name used within the schema and a database name (be sure to use under_score and not dash-es).

    
    
        <entity class="myorg.entityex.annotated.Bunny">
            <sequence-generator name="animal-sequence-gen"
                sequence-name="animal_sequence">
            </sequence-generator>
            <attributes>
                <id name="id">
                    <generated-value strategy="SEQUENCE"
                        generator="animal-sequence-gen"/> 
                </id>
            </attributes>
        </entity>
  6.     create table ENTITYEX_BUNNY (
            id integer not null,
            name varchar(255),
            primary key (id)
        );
    
        create sequence animal_sequence;
    
  7. Re-build the module and re-run the PKGen test method and you should see the new sequence being used.

     -testPKGen
    Hibernate: 
        call next value for animal_sequence
    Hibernate: 
        insert 
        into
            ENTITYEX_BUNNY
            (name, id) 
        values
            (?, ?)
     -bunny.getId()=10
    ...
     -ids=[10, 11, 12, 13]
    

We have successfully mapped the primary key generation to a database sequence and did so through a specification in the deployment descriptor to highlight the fact that the orm.xml file overrides and augments the class annotations. We also defined a custom generator which was used to generate the primary key values. If you were to do that with annotations -- the sequence-generator annotation would have gone over any one of the JPA entity classes.

In this section we will be mapping the primary key generation using a database table strategy. In this strategy a table and key name is identified for a sequence that will be used to bootstrap unique value generators. This technique always seems the most portable (and possibly the most expensive) since it involves no unique database behavior.

  1. Change the generation strategy from SEQUENCE to TABLE in the orm.xml descriptor. You can leave the sequence generator definition in place.

    
    
        <entity class="myorg.entityex.annotated.Bunny">
    ...
            <attributes>
                <id name="id">
                    <generated-value strategy="TABLE"/> 
                </id>
            </attributes>
        </entity>
  2. Rebuild the module and note the database schema generated. The table name was defaulted to hibernate_sequences and was given some default column names as well.

       create table ENTITYEX_BUNNY (
            id integer not null,
            name varchar(255),
            primary key (id)
        );
    
        create table hibernate_sequences (
             sequence_name varchar(255),
             sequence_next_hi_value integer 
        ) ;
    
  3. In watching the output of the unit test, you should observe the following initialization of the sequence row prior to getting started with the actual work of persisting our entities.

     -testPKGen
    Hibernate: 
        select
            sequence_next_hi_value 
        from
            hibernate_sequences 
        where
            sequence_name = 'ENTITYEX_BUNNY' for update
                
    Hibernate: 
        insert 
        into
            hibernate_sequences
            (sequence_name, sequence_next_hi_value) 
        values
            ('ENTITYEX_BUNNY', ?)
    Hibernate: 
        update
            hibernate_sequences 
        set
            sequence_next_hi_value = ? 
        where
            sequence_next_hi_value = ? 
            and sequence_name = 'ENTITYEX_BUNNY'
    Hibernate: 
        insert 
        into
            ENTITYEX_BUNNY
            (name, id) 
        values
            (?, ?)
     -bunny.getId()=1
    ...
     -ids=[1, 2, 3, 4]
    
  4. Add a table generator definition as shown below to the orm.xml file. Attempt to override many of the defaults.

    
    
        <entity class="myorg.entityex.annotated.Bunny">
            <table-generator name="animal-table-gen"
                table="animal_sequences"
                initial-value="3"
                allocation-size="10"
                pk-column-name="key"
                pk-column-value="animals"
                value-column-name="seq"/>
            <attributes>
                <id name="id">
                    <generated-value strategy="TABLE"
                        generator="animal-table-gen"/> 
                </id>
            </attributes>
  5. Re-build the module and observe how the generated schema matches what was specified in the table generator specification.

        create table ENTITYEX_BUNNY (
            id integer not null,
            name varchar(255),
            primary key (id)
        );
    
        create table animal_sequences (
             key varchar(255),
             seq integer 
        ) ;
    
  6. Run the unit test and observe pretty much the same behavior as above except this time using our customized table definitions. Note the initial values and allocation size is not yet noticeable.

     -testPKGen
    Hibernate: 
        select
            seq 
        from
            animal_sequences 
        where
            key = 'animals' for update
                
    Hibernate: 
        insert 
        into
            animal_sequences
            (key, seq) 
        values
            ('animals', ?)
    Hibernate: 
        update
            animal_sequences 
        set
            seq = ? 
        where
            seq = ? 
            and key = 'animals'
    Hibernate: 
        insert 
        into
            ENTITYEX_BUNNY
            (name, id) 
        values
            (?, ?)
     -bunny.getId()=1
    ...
     -ids=[1, 2, 3, 4]
    

You have now successfully mapped your entity class using a TABLE primary key mechanism. This is likely the most portable between databases but like also the most expensive because of all the knowledge and management coming from the client side of the connection.

In this chapter you defined your primary key as being automatically generated by the database and then specified one of three types of implementations (IDENTITY, SEQUENCE, and TABLE) to implement that value generation. The orm.xml file was also used to show how the actual primary key mechanism can be changed without changing the entity or client code. This can be helpful when switching between development and production databases that do not support the same features.