Primary Keys






Primary Keys

A primary key is the identity of a given entity bean. Every entity bean must have a primary key, and it must be unique. Primary keys can map to one or more properties and must map to one of the following types: any Java primitive type (including wrappers), java.lang.String , or a primary-key class composed of primitives and/or strings. Let's first focus on simple one-property primary keys.

@Id

The @javax.persistence.Id annotation identifies one or more properties that make up the primary key for your table:

package javax.persistence;

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface Id
{
}

You can generate the primary key for your entity beans manually or have the persistence provider do it for you. When you want provider-generated keys, you have to use the @javax.persistence.GeneratedValue annotation:

package javax.persistence;

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface GeneratedValue
{
   GenerationType strategy( ) default AUTO;
   String generator( ) default "";
}

public enum GenerationType
{
   TABLE, SEQUENCE, IDENTITY, AUTO
}

Persistence providers are required to provide key generation for primitive primary keys. You can define the type of primary generator you would like to have using the strategy( ) attribute. The GeneratorType.AUTO strategy is the most commonly used configuration:

package com.titan.domain;

import javax.persistence.*;

@Entity
public class Customer implements java.io.Serializable {
   private long id;
   private String firstName;
   private String lastName;

   @Id
@GeneratedValue 

   public long getId( ) { return id; }
   public void setId(long id) { this.id = id; }

   public String getFirstName( ) { return firstName; }
   public void setFirstName(String firstName) { this.firstName = firstName; }

   public String getLastName( ) { return lastName; }
   public void setLastName(String lastName) { this.lastName = lastName; }
}

The AUTO strategy tells the persistence provider that you are allowing it to generate the key for you. The IDENTITY strategy uses a special column type, available in many database implementations, for creating primary keys. Let's look at this example in XML:

<entity-mappings>
   <entity class="com.titan.domain.Customer" access="PROPERTY">
      <attributes>
         <id name="id">
            <generated-value strategy="AUTO">
         </id>
      </attributes>
   </entity>
</entity-mappings>

The <generated-value> element is a subelement of the <id> element. If you want the IDENTITY strategy instead, replace AUTO with IDENTITY.

The TABLE and SEQUENCE types require additional metadata beyond the @GeneratedValue or <generated-value> element. We discuss both of these mappings in the next two sections.

Table Generators

The TABLE strategy designates a user-defined relational table from which the numeric keys will be generated. A relational table with the following logical structure is used:

create table GENERATOR_TABLE
(
    PRIMARY_KEY_COLUMN 
 VARCHAR not null,
    VALUE_COLUMN 
 long not null
);

The PRIMARY_KEY_COLUMN holds a value that is used to match the primary key you are generating for. The VALUE_COLUMN holds the value of the counter.

To use this strategy, you must have already defined a table generator using the @javax.persistence.TableGenerator annotation. This annotation can be applied to a class or to the method or field of the primary key:

package javax.persistence;
 
@Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME)
public @interface TableGenerator
{
   String name( );
   String table( ) default "";
   String catalog( ) default "";
   String schema( ) default "";
   String pkColumnName( ) default "";
   String valueColumnName( ) default "";
   String pkColumnValue( ) default "";
   int allocationSize( ) default 50;
   UniqueConstraint[] uniqueConstraints( ) default {};
}

The name( ) attribute defines the name of the @TableGenerator and is the name referenced in the @Id.generator( ) attribute. The table( ), catalog( ), and schema( ) attributes describe the table definition of the generator table. The pkColumnName( ) attribute is the name of the column that identifies the specific entity primary key you are generating for. The valueColumnName( ) attribute specifies the name of the column that will hold the counter for the generated primary key. pkColumnValue( ) is the value used to match up with the primary key you are generating for. The allocationSize( ) attribute is how much the counter will be incremented when the persistence provider queries the table for a new value. This allows the provider to cache blocks so that it doesn't have to go to the database every time it needs a new ID. If you are autogenerating this table, then you can also define some constraints using the uniqueConstraints( ) attribute.

Let's look at how you would actually use this generator on the Customer entity:

package com.titan.domain

import javax.persistence.*;

@Entity
public class Customer implements java.io.Serializable {
   private long id;
   private String firstName;
   private String lastName;

@TableGenerator(name="CUST_GENERATOR"
                   table="GENERATOR_TABLE"
                   pkColumnName="PRIMARY_KEY_COLUMN"
                   valueColumnName="VALUE_COLUMN"
                   pkColumnValue="CUST_ID"
                   allocationSize=10)
   @Id
@GeneratedValue 
(strategy=GenerationType.TABLE,
generator="CUST_GENERATOR")
   public long getId( ) { return id; }
   public void setId(long id) { this.id = id; }

   public String getFirstName( ) { return firstName; }
   public void setFirstName(String firstName) { this.firstName = firstName; }

   public String getLastName( ) { return lastName; }
   public void setLastName(String lastName) { this.lastName = lastName; }
}

Now if you allocate and persist( ) a Customer entity, the id property will be autogenerated when the persist( ) operation is called.

Let's look at how this would be defined within XML:

<entity-mappings>
   <entity class="com.titan.domain.Customer" access="PROPERTY">
   <table-generator name="CUST_GENERATOR"
                       table="GENERATOR_TABLE"
                       pk-column-name="PRIMARY_KEY_COLUMN"
                       value-column-name="VALUE_COLUMN"
                       pk-column-value="CUST_ID"
                       allocation-size="10"/>
      <attributes>
         <id name="id">
         <generated-value strategy="TABLE" generator="CUST_GENERATOR"/>
         </id>
      </attributes>
   </entity>
</entity-mappings>

The <table-generator> element is a subelement of the <entity> element. Its attributes are the exact same as those in the annotation, so they need no further explanation. One thing to note is that with the <generated-value> element, you must specify the generator attribute, just as you would if you were using the @GeneratedValue annotation with this strategy.

Sequence Generators

Some RDBMs, specifically Oracle, have an efficient, built-in structure to generate IDs sequentially. This is the SEQUENCE generator strategy. This generator type is declared via the @javax.persistence.SequenceGenerator :

package javax.persistence;

@Target({METHOD, TYPE, FIELD}) @Retention(RUNTIME)
public @interface SequenceGenerator
{
   String name( );
   String sequenceName( ) 
 default "";
   int initialValue( ) default 1;
   int allocationSize( ) default 50;
}

The name( ) attribute specifies how this @SequenceGenerator is referenced in @Id annotations. Use the sequenceName( ) attribute to define what sequence table will be used from the database. initialValue( ) is the first value that will be used for a primary key, and allocationSize( ) is how much it will be incremented when it is accessed. Let's again look at applying the SEQUENCE strategy on our Customer entity bean:

package com.titan.domain

import javax.persistence.*;

@Entity
@Table(name="CUSTOMER_TABLE")
@SequenceGenerator(name="CUSTOMER_SEQUENCE",
                   sequenceName="CUST_SEQ")
public class Customer implements java.io.Serializable {
   private long id;
   private String firstName;
   private String lastName;

   @Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="CUSTOMER_SEQUENCE")
   public long getId( ) { return id; }
   public void setId(long id) { this.id = id; }

   public String getFirstName( ) { return firstName; }
   public void setFirstName(String firstName) { this.firstName = firstName; }

   public String getLastName( ) { return lastName; }
   public void setLastName(String lastName) { this.lastName = lastName; }
}

This example is a little different from our TABLE strategy example in that the generator is declared on the bean's class instead of directly on the property. TABLE and SEQUENCE generators can be defined in either place. As with the TABLE generation type, the primary key is autogenerated when the EntityManager.persist( ) operation is performed.

Let's look at the XML equivalent for this mapping:

<entity-mappings>
   <entity class="com.titan.domain.Customer" access="PROPERTY">
   <sequence-generator name="CUSTOMER_SEQUENCE"
                          sequence-name="CUST_SEQ"
                          initial-value="0"
                          allocation-size="50"/>
      <attributes>
         <id name="id">
         <generated-value strategy="SEQUENCE" generator="CUSTOMER_SEQUENCE"/>
         </id>
      </attributes>
   </entity>
</entity-mappings>

The <sequence-generator> element is a subelement of <entity>. Its attributes are the same as the attributes of the @SequenceGenerator annotation. Notice also that the <generated-value> element references the sequence generator defined.

Primary-Key Classes and Composite Keys

Sometimes relational mappings require a primary key to be composed of multiple persistent properties. For instance, let's say that our relational model specified that our Customer entity should be identified by both its last name and its Social Security number instead of an autogenerated numeric key. These are called composite keys. The Java Persistence specification provides multiple ways to map this type of model. One is through the @javax.persistence.IdClass annotation; the other is through the @javax.persistence.EmbeddedId annotation.

@IdClass

The first way to define a primary-key class (and, for that matter, composite keys) is to use the @IdClass annotation. Your bean class does not use this primary-key class internally, but it does use it to interact with the entity manager when finding a persisted object through its primary key. @IdClass is a class-level annotation and specifies what primary-key class you should use when interacting with the entity manager.

@Target(TYPE)
@Retention(RUNTIME)
public @interface IdClass
{
   Class value( );
}

In your bean class, you designate one or more properties that make up your primary key, using the @Id annotation. These properties must map exactly to properties in the @IdClass. Let's look at changing our Customer bean class to have a composite key made up of last name and Social Security number. First, let's define our primary-key class:

package com.titan.domain;

public class CustomerPK 
 implements java.io.Serializable {
   private String lastName;
   private long ssn;

   public CustomerPK( ) {}

   public CustomerPK(String lastName, long ssn)
   {
      this.lastName = lastName;
      this.ssn = ssn;
   }

   public String getLastName( ) { return this.lastName; }
   public void setLastName(String lastName) { this.lastName = lastName; }

   public long getSsn( ) { return ssn; }
   public void setSsn(long ssn) { this.ssn = ssn; }

   public boolean equals(Object obj)
   {
      if (obj == this) return true;
      if (!(obj instanceof CustomerPK)) return false;
      CustomerPK pk = (CustomerPK)obj;
      if (!lastName.equals(pk.lastName)) return false;
      if (ssn != pk.ssn) return false;
      return true;
   }

   public int hashCode( )
   {
      return lastName.hashCode( ) + (int)ssn;
   }
}

The primary-key class must meet these requirements:

  • It must be serializable.

  • It must have a public no-arg constructor.

  • It must implement the equals( ) and hashCode( ) methods.

Our Customer bean must have the same exact properties as the CustomerPK class, and these properties are annotated with multiple @Id annotations:

package com.titan.domain;

import javax.persistence.*;

@Entity
@IdClass(CustomerPK.class)
public class Customer implements java.io.Serializable {
   private String firstName;
   private String lastName;
   private long ssn;

   public String getFirstName( ) { return firstName; }
   public void setFirstName(String firstName) { this.firstName = firstName; }

@Id
   public String getLastName( ) { return lastName; }
   public void setLastName(String lastName) { this.lastName = lastName; }

@Id
   public long getSsn( ) { return ssn; }
   public void setSsn(long ssn) { this.ssn = ssn; }
}

Primary-key autogeneration is not supported for composite keys and primary-key classes. You will have to manually create the key values in code.


Let's now look at the XML mapping equivalent to @IdClass:

<entity-mappings>
   <entity class="com.titan.domain.Customer" access="PROPERTY">
   <id-class>com.titan.domain.CustomerPK</id-class>
      <attributes>
      <id name="lastName"/>
         <id name="ssn"/>
      </attributes>
   </entity>
</entity-mappings>

The <id-class> element is a subelement of <entity>, and its value is the fully qualified class name of the primary-key class. Notice also that multiple <id> elements for each property map to the primary-key class.

The primary-key class is used whenever you are querying for the Customer:

CustomerPK pk = new CustomerPK("Burke", 9999999);
Customer cust = entityManager.find(Customer.class, pk);

Whenever you call an EntityManager method like find( ) or getreference( ), you must use the primary key class to identify the entity.

@EmbeddedId

A different way to define primary-key classes and composite keys is to embed the primary-key class directly in your bean class. The @javax.persistence.EmbeddedId annotation is used for this purpose in conjunction with the @javax.persistence.Embeddable annotation:

package javax.persistence;

public @interface EmbeddedId
{
}

public @interface AttributeOverrides
{
   AttributeOverride[] value( );
}

public @interface AttributeOverride
{
   String name( );

   Column[] column( ) default {};
}

public @interface Embeddable
{
}

There are two ways to map the properties of your primary-key class to columns in your table. One is to specify the @Column mappings within the primary-key class source code; the other is to use @AttributeOverrides. Let's look at the former and then the latter.

package com.titan.domain;

import javax.persistence.*;

@Embeddable
public class CustomerPK implements java.io.Serializable {
   private String lastName;
   private long ssn;

   public CustomerPK( ) {}

   public CustomerPK(String lastName, long ssn)
   {
      this.lastName = lastName;
      this.ssn = ssn;
   }

   @Column(name="CUSTOMER_LAST_NAME")
   public String getLastName( ) { return this.lastName; }
   public void setLastName(String lastName) { this.lastName = lastName; }

   @Column(name="CUSTOMER_SSN")

   public long getSsn( ) { return ssn; }
   public void setSsn(long ssn) { this.ssn = ssn; }

   public boolean equals(Object obj)
   {
      if (obj == this) return true;
      if (!(obj instanceof CustomerPK)) 
 return false;
      CustomerPK pk = (CustomerPK)obj;
      if (!lastName.equals(pk.lastName)) return false;
      if (ssn != pk.ssn) return false;
      return true;
   }

   public int hashCode( )
   {
      return lastName.hashCode( ) + (int)ssn;
   }
}

We then change our Customer bean class to use the CustomerPK directly, using the @EmbeddedId annotation:

package com.titan.domain

import javax.persistence.*;

@Entity
public class Customer implements java.io.Serializable {
   private String firstName;
   private CustomerPK pk;

   public String getFirstName( ) { return firstName; }
   public void setFirstName(String firstName) { this.firstName = firstName; }

@EmbeddedId
   public PK getPk( ) { return pk; }
   public void setPk(CustomerPK pk) { this.pk = pk; }

}

The CustomerPK primary-key class is used whenever you are fetching the Customer using EntityManager APIs:

CustomerPK pk = new CustomerPK("Burke", 9999999);
Customer cust = entityManager.find(Customer.class, pk);

Whenever you call an EntityManager method like find( ) or getreference( ), you must use the primary-key class to identify the entity.

If you do not want to have the @Column mappings with the primary-key class, or you just want to override them, you can use @AttributeOverrides to declare them directly in your bean class:

@Entity
public class Customer implements java.io.Serializable {
   private String firstName;
   private CustomerPK pk;

   public String getFirstName( ) { return firstName; }
   public void setFirstName(String firstName) { this.firstName = firstName; }

@EmbeddedId
   @AttributeOverrides({
       @AttributeOverride(name="lastName", [email protected](name="LAST_NAME"),
       @AttributeOverride(name="ssn", [email protected](name="SSN"))
   })
   public PK getPk( ) { return pk; }
   public void setPk(CustomerPK pk) { this.pk = pk; }

}

The @AttributeOverrides annotation is an array list of @AttributeOverride annotations. The name( ) attribute specifies the property name in the embedded class you are mapping to. The column( ) attribute allows you to describe the column the property maps to.

Let's now look at the XML mapping equivalent to @IdClass :

<entity-mappings>
<embeddable class="com.titan.domain.CustomerPK" access-type="PROPERTY">
      <embeddable-attributes>
         <basic name="lastName">
            <column name="CUSTOMER_LAST_NAME"/>
         </basic>
         <basic name="ssn">
            <column name="CUSTOMER_SSN"/>
         </basic>
      </embeddable-attributes>
   </embeddable>
   <entity class="com.titan.domain.Customer" access="PROPERTY">
      <attributes>
      <embedded-id name="pk">
            <attribute-override name="lastName">
               <column name="LAST_NAME"/>
            </attribute-override>
            <attribute-override name="ssn">
               <column name="SSN"/>
            </attribute-override>
         </embedded_id>
      </attributes>
   </entity>
</entity-mappings>

The <embeddable> element is a subelement of <entity-mappings> and describes the embeddable class. The <embeddable-attributes> element identifies the persistent properties of the embeddable class. Within <embeddable-attributes>, you can define <basic>, <lob>, <temporal>, and <enumerated> column mappings. These subelements are optional.

The <embedded-id> element is used within the <attributes> element to identify the embedded primary-key property of the entity. The <attribute-override> element is used to specify any column overrides for the properties of the embedded class.



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows