4.2. Mapping entities with identity

It's vital to understand the difference between object identity and object equality before we discuss terms like database identity and the way Hibernate manages identity. Next, we explore how object identity and equality relate to database (primary key) identity.

4.2.1. Understanding Java identity and equality

Java developers understand the difference between Java object identity and equality. Object identity, ==, is a notion defined by the Java virtual machine. Two object references are identical if they point to the same memory location.

On the other hand, object equality is a notion defined by classes that implement the equals() method, sometimes also referred to as equivalence. Equivalence means that two different (nonidentical) objects have the same value. Two different instances of String are equal if they represent the same sequence of characters, even though they each have their own location in the memory space of the virtual machine. (If you're a Java guru, we acknowledge that String is a special case. Assume we used a different class to make the same point.)

Persistence complicates this picture. With object/relational persistence, a persistent object is an in-memory representation of a particular row of a database table. Along with Java identity (memory location) and object equality, you pick up database identity (which is the location in the persistent data store). You now have three methods for identifying objects:

  • Objects are identical if they occupy the same memory location in the JVM. This can be checked by using the == operator. This concept is known as object identity.

  • Objects are equal if they have the same value, as defined by the equals(Object o) method. Classes that don't explicitly override this method inherit the implementation defined by java.lang.Object, which compares object identity. This concept is known as equality.

  • Objects stored in a relational database are identical if they represent the same row or, equivalently, if they share the same table and primary key value. This concept is known as database identity.

We now need to look at how database identity relates to object identity in Hibernate, and how database identity is expressed in the mapping metadata.

4.2.2. Handling database identity

Hibernate exposes database identity to the application in two ways:

  • The value of the identifier property of a persistent instance

  • The value returned by Session.getIdentifier(Object entity)

Adding an identifier property to entities

The identifier property is special—its value is the primary key value of the database row represented by the persistent instance. We don't usually show the identifier property in the domain model diagrams. In the examples, the identifier property is always named id. If myCategory is an instance of Category, calling myCategory.getId() returns the primary key value of the row represented by myCategory in the database.

Let's implement an identifier property for the Category class:

public class Category {
    private Long id;
    ...
    public Long getId() {
        return this.id;
    }

    private void setId(Long id) {
        this.id = id;
    }
    ...
}

Should you make the accessor methods for the identifier property private scope or public? Well, database identifiers are often used by the application as a convenient handle to a particular instance, even outside the persistence layer. For example, it's common for web applications to display the results of a search screen to the user as a list of summary information. When the user selects a particular element, the application may need to retrieve the selected object, and it's common to use a lookup by identifier for this purpose—you've probably already used identifiers this way, even in applications that rely on JDBC. It's usually appropriate to fully expose the database identity with a public identifier property accessor.

On the other hand, you usually declare the setId() method private and let Hibernate generate and set the identifier value. Or, you map it with direct field access and implement only a getter method. (The exception to this rule is classes with natural keys, where the value of the identifier is assigned by the application before the object is made persistent instead of being generated by Hibernate. We discuss natural keys in chapter 8.) Hibernate doesn't allow you to change the identifier value of a persistent instance after it's first assigned. A primary key value never changes—otherwise the attribute wouldn't be a suitable primary key candidate!

The Java type of the identifier property, java.lang.Long in the previous example, depends on the primary key type of the CATEGORY table and how it's mapped in Hibernate metadata.

Mapping the identifier property

A regular (noncomposite) identifier property is mapped in Hibernate XML files with the <id> element:

<class name="Category" table="CATEGORY">
<id name="id" column="CATEGORY_ID" type="long">
    <generator class="native"/>
</id>

...
</class>

The identifier property is mapped to the primary key column CATEGORY_ID of the table CATEGORY. The Hibernate type for this property is long, which maps to a BIGINT column type in most databases and which has also been chosen to match the type of the identity value produced by the native identifier generator. (We discuss identifier generation strategies in the next section.)

For a JPA entity class, you use annotations in the Java source code to map the identifier property:

@Entity
@Table(name="CATEGORY")
public class Category {
    private Long id;
    ...

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "CATEGORY_ID")
    public Long getId() {
        return this.id;
    }

    private void setId(Long id) {
        this.id = id;
    }
    ...
}

The @Id annotation on the getter method marks it as the identifier property, and @GeneratedValue with the GenerationType.AUTO option translates into a native identifier generation strategy, like the native option in XML Hibernate mappings. Note that if you don't define a strategy, the default is also GenerationType.AUTO, so you could have omitted this attribute altogether. You also specify a database column—otherwise Hibernate would use the property name. The mapping type is implied by the Java property type, java.lang.Long.

Of course, you can also use direct field access for all properties, including the database identifier:

@Entity
@Table(name="CATEGORY")
public class Category {

    @Id @GeneratedValue
    @Column(name = "CATEGORY_ID")
    private Long id;
    ...

    public Long getId() {
        return this.id;
    }
    ...

}

Mapping annotations are placed on the field declaration when direct field access is enabled, as defined by the standard.

Whether field or property access is enabled for an entity depends on the position of the mandatory @Id annotation. In the preceding example, it's present on a field, so all attributes of the class are accessed by Hibernate through fields. The example before that, annotated on the getId() method, enables access to all attributes through getter and setter methods.

Alternatively, you can use JPA XML descriptors to create your identifier mapping:

<entity class="auction.model.Category" access="FIELD">
    <table name="CATEGORY"/>
    <attributes>
        <id name="id">
            <generated-value strategy="AUTO"/>
        </id>
        ...
    </attributes>
</entity>

In addition to operations for testing Java object identity, (a == b), and object equality, ( a.equals(b) ), you may now use a.getId().equals( b.getId() ) to test database identity. What do these notions have in common? In what situations do they all return true? The time when all are true is called the scope of guaranteed object identity; and we'll come back to this subject in chapter 9, section 9.2, "Object identity and equality."

Using database identifiers in Hibernate is easy and straightforward. Choosing a good primary key (and key-generation strategy) may be more difficult. We discuss this issue next.

4.2.3. Database primary keys

Hibernate needs to know your preferred strategy for generating primary keys. First, though, let's define primary key.

Selecting a primary key

The candidate key is a column or set of columns that could be used to identify a particular row in a table. To become a primary key, a candidate key must satisfy the following properties:

  • Its value (for any column of the candidate key) is never null.

  • Each row has a unique value.

  • The value of a particular row never changes.

If a table has only one identifying attribute, it's, by definition, the primary key. However, several columns or combinations of columns may satisfy these properties for a particular table; you choose between candidate keys to decide the best primary key for the table. Candidate keys not chosen as the primary key should be declared as unique keys in the database.

Many legacy SQL data models use natural primary keys. A natural key is a key with business meaning: an attribute or combination of attributes that is unique by virtue of its business semantics. Examples of natural keys are the U.S. Social Security Number and Australian Tax File Number. Distinguishing natural keys is simple: If a candidate key attribute has meaning outside the database context, it's a natural key, whether or not it's automatically generated. Think about the application users: If they refer to a key attribute when talking about and working with the application, it's a natural key.

Experience has shown that natural keys almost always cause problems in the long run. A good primary key must be unique, constant, and required (never null or unknown). Few entity attributes satisfy these requirements, and some that do can't be efficiently indexed by SQL databases (although this is an implementation detail and shouldn't be the primary motivation for or against a particular key). In addition, you should make certain that a candidate key definition can never change throughout the lifetime of the database before making it a primary key. Changing the value (or even definition) of a primary key, and all foreign keys that refer to it, is a frustrating task. Furthermore, natural candidate keys can often be found only by combining several columns in a composite natural key. These composite keys, although certainly appropriate for some relations (like a link table in a many-to-many relationship), usually make maintenance, ad-hoc queries, and schema evolution much more difficult.

For these reasons, we strongly recommend that you consider synthetic identifiers, also called surrogate keys. Surrogate keys have no business meaning—they're unique values generated by the database or application. Application users ideally don't see or refer to these key values; they're part of the system internals. Introducing a surrogate key column is also appropriate in a common situation: If there are no candidate keys, a table is by definition not a relation as defined by the relational model—it permits duplicate rows—and so you have to add a surrogate key column. There are a number of well-known approaches to generating surrogate key values.

Selecting a key generator

Hibernate has several built-in identifier-generation strategies. We list the most useful options in table 4.1.

Table 4-1. Hibernate's built-in identifier-generator modules
Generator nameJPA GenerationTypeOptionsDescription
nativeAUTOThe native identity generator picks other identity generators like identity, sequence, or hilo, depending on the capabilities of the underlying database. Use this generator to keep your mapping metadata portable to different database management systems.
identityIDENTITYThis generator supports identity columns in DB2, MySQL, MS SQL Server, Sybase, and HypersonicSQL. The returned identifier is of type long, short, or int.
sequenceSEQUENCEsequence, parametersThis generator creates a sequence in DB2, PostgreSQL, Oracle, SAP DB, or Mckoi; or a generator in InterBase is used. The returned identifier is of type long, short, or int. Use the sequence option to define a catalog name for the sequence (hibernate_sequence is the default) and parameters if you need additional settings creating a sequence to be added to the DDL.
increment(Not available)At Hibernate startup, this generator reads the maximum (numeric) primary key column value of the table and increments the value by one each time a new row is inserted. The generated identifier is of type long, short, or int. This generator is especially efficient if the single-server Hibernate application has exclusive access to the database but should not be used in any other scenario.
hilo(Not available)table, column, max_loA high/low algorithm is an efficient way to generate identifiers of type long, given a table and column (by default hibernate_unique_key and next, respectively) as a source of high values. The high/low algorithm generates identifiers that are unique only for a particular database. High values are retrieved from a global source and are made unique by adding a local low value. This algorithm avoids congestion when a single source for identifier values has to be accessed for many inserts. See "Data Modeling 101" (Ambler, 2002) for more information about the high/low approach to unique identifiers. This generator needs to use a separate database connection from time to time to retrieve high values, so it isn't supported with user-supplied database connections. In other words, don't use it with sessionFactory.openSession(myConnection). The max_lo option defines how many low values are added until a new high value is fetched. Only settings greater than 1 are sensible; the default is 32767 (Short.MAX_VALUE).
seqhilo(Not available)sequence, parameters, max_loThis generator works like the regular hilo generator, except it uses a named database sequence to generate high values.
(JPA only)TABLEtable, catalog, schema, pkColumnName, valueColumnName, pkColumnValue, allocationSizeMuch like Hibernate's hilo strategy, TABLE relies on a database table that holds the last-generated integer primary key value, and each generator is mapped to one row in this table. Each row has two columns: pkColumnName and valueColumnName. The pkColumnValue assigns each row to a particular generator, and the value column holds the last retrieved primary key. The persistence provider allocates up to allocationSize integers in each turn.
uuid.hex(Not available)separatorThis generator is a 128-bit UUID (an algorithm that generates identifiers of type string, unique within a network). The IP address is used in combination with a unique timestamp. The UUID is encoded as a string of hexadecimal digits of length 32, with an optional separator string between each component of the UUID representation. Use this generator strategy only if you need globally unique identifiers, such as when you have to merge two databases regularly.
guid(Not available)This generator provides a database-generated globally unique identifier string on MySQL and SQL Server.
select(Not available)keyThis generator retrieves a primary key assigned by a database trigger by selecting the row by some unique key and retrieving the primary key value. An additional unique candidate key column is required for this strategy, and the key option has to be set to the name of the unique key column.

Some of the built-in identifier generators can be configured with options. In a native Hibernate XML mapping, you define options as pairs of keys and values:

<id column="MY_ID">
    <generator class="sequence">
        <parameter name="sequence">MY_SEQUENCE</parameter>
        <parameter name="parameters">
            INCREMENT BY 1 START WITH 1
        </parameter>
    </generator>
</id>

You can use Hibernate identifier generators with annotations, even if no direct annotation is available:

@Entity
@org.hibernate.annotations.GenericGenerator(
    name = "hibernate-uuid",
    strategy = "uuid"
)
class name MyEntity {

    @Id
    @GeneratedValue(generator = "hibernate-uuid")
    @Column(name = "MY_ID")
    String id;
}

The @GenericGenerator Hibernate extension can be used to give a Hibernate identifier generator a name, in this case hibernate-uuid. This name is then referenced by the standardized generator attribute.

This declaration of a generator and its assignment by name also must be applied for sequence- or table-based identifier generation with annotations. Imagine that you want to use a customized sequence generator in all your entity classes. Because this identifier generator has to be global, it's declared in orm.xml:

<sequence-generator name="mySequenceGenerator"
    sequence-name="MY_SEQUENCE"
    initial-value="123"
    allocation-size="20"/>

This declares that a database sequence named MY_SEQUENCE with an initial value of 123 can be used as a source for database identifier generation, and that the persistence engine should obtain 20 values every time it needs identifiers. (Note, though, that Hibernate Annotations, at the time of writing, ignores the initialValue setting.)

To apply this identifier generator for a particular entity, use its name:

@Entity
class name MyEntity {

    @Id @GeneratedValue(generator = "mySequenceGenerator")
    String id;
}

If you declared another generator with the same name at the entity level, before the class keyword, it would override the global identifier generator. The same approach can be used to declare and apply a @TableGenerator.

You aren't limited to the built-in strategies; you can create your own identifier generator by implementing Hibernate's IdentifierGenerator interface. As always, it's a good strategy to look at the Hibernate source code of the existing identifier generators for inspiration.

It's even possible to mix identifier generators for persistent classes in a single domain model, but for nonlegacy data we recommend using the same identifier generation strategy for all entities.

For legacy data and application-assigned identifiers, the picture is more complicated. In this case, we're often stuck with natural keys and especially composite keys. A composite key is a natural key that is composed of multiple table columns. Because composite identifiers can be a bit more difficult to work with and often only appear on legacy schemas, we only discuss them in the context of chapter 8, section 8.1, "Integrating legacy databases."

We assume from now on that you've added identifier properties to the entity classes of your domain model, and that after you completed the basic mapping of each entity and its identifier property, you continued to map value-typed properties of the entities. However, some special options can simplify or enhance your class mappings.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset