The @TableGeneratorannotation is used in a very similar way to the @SequenceGenerator annotation—but because @TableGeneratormanipulates a standard database table to obtain its primary key values, instead of using a vendor-specific sequence object, it is guaranteed to be portable between database platforms.
■ Note For optimal portability andoptimal performance, you should not specify the use of a table genera- tor, but instead use the @GeneratorValue(strategy=GeneratorType.AUTO)configuration, which allows the persistence provider to select the most appropriate strategy for the database in use.
As with the sequence generator, the name attributes of @TableGeneratorare mandatory and the other attributes are optional, with the table details being selected by the persistence provider.
@Id
@TableGenerator(name="tablegen", table="ID_TABLE", pkColumnName="ID",
valueColumnName="NEXT_ID")
@GeneratedValue(strategy=TABLE,generator="tablegen") public int getId() {
return id;
}
The optional attributes are as follows:
• allocationSize: Allows the increment on the primary key value to be specified.
• catalog: Allows the catalog that the table resides within to be specified.
• initialValue: Allows the starting primary key value to be specified.
• pkColumnName: Allows the primary key column of the table to be identified. The table can contain the details necessary for generating primary key values for multiple entities.
• pkColumnValue: Allows the primary key for the row containing the primary key genera- tion information to be identified.
C H A P T E R 6 ■ M A P P I N G W I T H A N N OTAT I O N S 104
• schema: Allows the schema that the table resides within to be specified.
• table: The name of the table containing the primary key values.
• uniqueConstraints: Allows additional constraints to be applied to the table for schema generation.
• valueColumnName: Allows the column containing the primary key generation informa- tion for the current entity to be identified.
Because the table can be used to contain the primary key values for a variety of entries, it is likely to contain a single row for each of the entities using it. It therefore needs its own primary key (pkColumnName), as well as a column containing the next primary key value to be used (pkColumnValue) for any of the entities obtaining their primary keys from it.
Compound Primary Keys with @Id, @IdClass, or @EmbeddedId
While the use of single column surrogate keys is advantageous for various reasons, you may sometimes be forced to work with business keys. When these are contained in a single col- umn, you can use @Idwithout specifying a generation strategy (forcing the user to assign a primary key value before the entity can be persisted). However, when the primary key con- sists of multiple columns, you need to take a different strategy to group these together in a way that allows the persistence engine to manipulate the key values as a single object.
You must create a class to represent this primary key. It will not require a primary key of its own, of course, but it must be a public class, must have a default constructor, must be seri- alizable, and must implement hashCode()and equals()methods to allow the Hibernate code to test for primary key collisions (i.e., they must be implemented with the appropriate data- base semantics for the primary key values).
Your three strategies for using this primary key class once it has been created are as follows:
• Mark it as @Embeddableand add to your entity class a normal property for it, marked with @Id.
• Add to your entity class a normal property for it, marked with @EmbeddableId.
• Add properties to your entity class for all of its fields, mark them with @Id, and mark your entity class with @IdClass, supplying the class of your primary key class.
All these techniques require the use of an idclass because Hibernate must be supplied with a primary key object when various parts of its persistence API are invoked. For example, you can retrieve an instance of an entity by invoking the Sessionobject’s get()method, which takes as its parameter a single serializable object representing the entity’s primary key.
The use of @Idwith a class marked as @Embeddable, as shown in Listing 6-6, is the most natural approach. The @Embeddabletag can be used for non–primary key embeddable values anyway (@Embeddableis discussed in more detail later in the chapter). It allows you to treat the compound primary key as a single property, and it permits the reuse of the @Embeddableclass in other tables.
C H A P T E R 6 ■ M A P P I N G W I T H A N N OTAT I O N S 105
Listing 6-6.Using the @Idand @EmbeddableAnnotations to Map a Compound Primary Key
package com.hibernatebook.annotations;
import javax.persistence.*;
@Entity
public class Account {
private String description;
private AccountPk id;
public Account (String description) { this.description = description;
}
protected Account() { }
@Id
public AccountPk getId() { return this.id;
}
public String getDescription() { return this.description;
}
public void setId(AccountPk id) { this.id = id;
}
public void setDescription(String description) { this.description = description;
}
@Embeddable
public static class AccountPk { private String code;
private Integer number;
public AccountPk() { }
public String getCode() { return this.code;
}
C H A P T E R 6 ■ M A P P I N G W I T H A N N OTAT I O N S 106
public Integer getNumber() { return this.number;
}
public void setNumber(Integer number) { this.number = number;
}
public void setCode(String code) { this.code = code;
}
public int hashCode() { int hashCode = 0;
if( code != null ) hashCode ^= code.hashCode();
if( number != null ) hashCode ^= number.hashCode();
return hashCode;
}
public boolean equals(Object obj) {
if( !(obj instanceof AccountPk) ) return false;
AccountPk target = (AccountPk)obj;
return ((this.code == null) ? (target.code == null) :
this.code.equals(target.code))
&& ((this.number == null) ? (target.number == null) :
this.number.equals(target.number));
} } }
The next most natural approach is the use of the @EmbeddedIdtag. Here, the primary key class cannot be used in other tables since it is not an @Embeddableentity, but it does allow us to treat the key as a single attribute of the Accountclass (in Listings 6-7 and 6-8, the implementation of AccountPkis identical to that in Listing 6-6, and is thus omitted for brevity). Note that in Listings 6-7 and 6-8, the AccountPkclass is not marked as @Embeddable.
Listing 6-7.Using the @EmbeddedIdAnnotation to Map a Compound Primary Key
package com.hibernatebook.annotations;
import javax.persistence.*;
@Entity
public class Account {
private String description;
private AccountPk id;
C H A P T E R 6 ■ M A P P I N G W I T H A N N OTAT I O N S 107
public Account(String description) { this.description = description;
}
protected Account() { }
@EmbeddedId
public AccountPk getId() { return this.id;
}
public String getDescription() { return this.description;
}
public void setId(AccountPk id) { this.id = id;
}
public void setDescription(String description) { this.description = description;
}
public static class AccountPk { // ...
} }
Finally, the use of the @IdClassand @Idannotations allows us to map the compound pri- mary key class using properties of the entity itself corresponding to the names of the properties in the primary key class. The names must correspond (there is no mechanism for overriding this), and the primary key class must honor the same obligations as with the other two tech- niques. The only advantage to this approach is its ability to “hide” the use of the primary key class from the interface of the enclosing entity. The @IdClassannotation takes a value parame- ter of Classtype, which must be the class to be used as the compound primary key. The fields that correspond to the properties of the primary key class to be used must all be annotated with @Id—note in Listing 6-8 that the getCode()and getNumber()methods of the Accountclass are so annotated, and the AccountPkclass is not mapped as @Embeddable, but it is supplied as the value of the @IdClassannotation.
C H A P T E R 6 ■ M A P P I N G W I T H A N N OTAT I O N S 108
Listing 6-8.Using the @IdClassand @IdAnnotations to Map a Compound Primary Key
package com.hibernatebook.annotations;
import javax.persistence.*;
@Entity
@IdClass(Account.AccountPk.class) public class Account {
private String description;
private String code;
private Integer number;
public Account(String description) { this.description = description;
}
protected Account() { }
@Id
public String getCode() { return this.code;
}
@Id
public Integer getNumber() { return this.number;
}
public String getDescription() { return this.description;
}
public void setDescription(String description) { this.description = description;
}
public void setNumber(Integer number) { this.number = number;
}
public void setCode(String code) { this.code = code;
}
C H A P T E R 6 ■ M A P P I N G W I T H A N N OTAT I O N S 109
public static class AccountPk { // ...
} }
Regardless of which of these approaches we take to declare our compound primary key, the table that will be used to represent it will require the same set of columns. Listing 6-9 shows the DDL that will be generated from any of Listings 6-6, 6-7, or 6-8.
Listing 6-9.The DDL Generated from the Annotated AccountClass (Regardless of the Approach Used)
create table Account (
code varchar(255) not null, number integer not null, description varchar(255), primary key (code, number) );