More Books
Hibernate: A J2EE Developer's Guide
Hibernate: A J2EE™ Developer's Guide
Table of Contents
Copyright
Acknowledgments
About the Author
Preface
Required Skills
Roadmap
Chapter 1. Overview
Why Object/Relational Mapping?
What Is Hibernate?
Comparing JDBC to Hibernate
Hibernate's Mapping System
Other Java/Database Integration Solutions
How to Obtain and Install
Supported Databases
Chapter 2. Getting Oriented
Application Architecture
Mapping Files
Generating Java Source
Application Configuration
Web Application
JSP Interface
Chapter 3. Starting from Java
Java Object Model
Generated Mapping Files
Generated Schema
Working with Artifacts and Owners
Chapter 4. Starting from an Existing Schema
Initial Schema
Using Middlegen
Generated Mapping Files
Generated Java
Working with the Database
Chapter 5. Mapping Files
Basic Structure
Mapping File Reference
Chapter 6. Persistent Objects
Sessions
Objects and Identity
Life-Cycle Methods
Chapter 7. Relationships
Database Relationships
Java Collection Relationships
Java Class Relationships
Any-Based Relationships
Bi-directional Relationships
Chapter 8. Queries
HQL
HQL Reference
Select
From
Where
Group By
Having
Order By
Criteria Queries
Native SQL Queries
Chapter 9. Transactions
Introduction to Transactions
Optimistic and Pessimistic Locking
Chapter 10. Performance
Finding and Solving Problems
Queries
Inserts
Connection Pooling
Caching
Chapter 11. Schema Management
Updating an Existing Schema
Generating Update and Drop Scripts
Chapter 12. Best Practices, Style Guide, Tips and Tricks
Reducing Code with Inversion of Control
Reducing Session Creation Impact with ThreadLocal
Using Hibernate as an EJB BMP Solution
Integrating with Other Technologies
Applications That Use Hibernate
Strategies for Getting Started
Chapter 13. Future Directions
Hibernate 3.0
EJB 3.0
Here and Now
Index
SYMBOL
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X

Objects and Identity

Our applications so far have used several different mechanisms for determining whether two objects are to be considered equal. It's important to understand the different mechanisms for identity, especially concerning the difference between transient and persistent objects as defined by Hibernate.

What Is Identity?

The first form of identity is expressed in terms of the database key. For example, two records could be considered equal if they have the same primary key value.

The second form of identity is expressed in terms of the Java object reference. We can imagine a situation in which two Java objects contain the same data from the perspective of the developer but are different objects because they are stored in two different places in memory.

Finally, the third form of identity derives from a comparison of all of the data associated with the object. For example, consider an object containing student data. This object might be represented by a primary key (for example, 543), a JVM object reference (for example, 82340), or the values of the associated data (for example, the primary key, 543, the first name, and the last name).

If the student object were to change the value of the last-name property, neither the primary key nor the object reference would be changed, even though the object is clearly no longer equivalent.

Consider the following snippet of code and the different values that may be considered when evaluating equality:

 

JVM

Primary Key

Data

Student myStudent = new Student();

45678

null

null

myStudent.setID(new Long(23));

45687

23

23+null

myStudent.setLastName ("Smith");

45687

23

23+Smith

Student altStudent = new Student();

09875

null

null

myStudent.setID(new Long(23));

09875

23

23+null

myStudent.setLastName ("Garod");

09875

23

23+Garod


Obviously, these different notions of equality can lead to radically different answers when one is trying to answer the question "Are these two objects equal?"

Hibernate expresses these two different notions of equality by taking advantage of the two different mechanisms for equality built into the Java language. The Java = and == operators rely on the object reference. All Java objects, on the other hand, descend from the base java.lang.Object class, which defines an equals() method. Therefore, Hibernate uses the equals() method to determine persistent identity, and the == method to determine JVM equality.

Let's look at a standard Hibernate equals() implementation, as shown in Listing 6.3. This is declared in a class called Message (as described in more detail later in this chapter). The first order of comparison in the snippet is by class instance. Then the objects are compared for equality by their primary-key value and nothing else (for more information on the Apache Jakarta Commons Lang project's EqualsBuilder class, see http://jakarta.apache.org/commons/lang/api/org/apache/commons/lang/builder/EqualsBuilder.html). Therefore, this object will rely on the notion of primary-key values for equality.

Listing 6.3. Sample Primary-Key Equality Method
public boolean equals(Object other)
{
    if (!(other instanceof Message))
        return false;
    Message castOther = (Message)other;
    return new EqualsBuilder()
        .append(this.getId(), castOther.getId())
        .isEquals();
}

Similarly, Listing 6.4 shows the matching implementation of hashCode().

Listing 6.4. Sample Primary-Key Hash Method
    public int hashCode()
    {
        return new
HashCodeBuilder().append(getId()).toHashCode();
    }

The documentation for the Apache Jakarta Commons Lang HashCodeBuilder will be found at http://jakarta.apache.org/commons/lang/api/org/apache/commons/lang/builder/HashCodeBuilder.html. The hashCode is principally derived from the primary key id, as in the equals() method.

WHAT IS A HASH CODE?

public int hashCode() returns a hash code value for the object. This method is supported for the benefit of hashtables such as those provided by java.util.Hashtable.

The general contract of hashCode is:

  • Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided that no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.

  • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.

  • If two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects need not produce distinct integer results. Be aware, however, that producing distinct integer results for unequal objects may improve the performance of a hashtable.

  • As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but the JavaTM programming language does not require this implementation technique.)

Documentation for the Java Object, hashCode method.


Identity within a Session

Hibernate has the very interesting capability of replacing instances that you pass to it with new instances as needed, in order to maintain internal consistency.

This means that you will always get back the same JVM object reference when you perform operations in the context of a single session. For example, consider the Hibernate pseudo-code shown in Listing 6.5.

Listing 6.5. Session Identity
hibernateSession = sessionFactory.openSession();
Student myStudent = hibernateSession.load(Student.class, new Long(23));
...
Student altStudent = hibernateSession.load(Student.class, new Long(23));
if(myStudent == altStudent)
      System.out.println("Students are equal!");

The snippet in Listing 6.5 will evaluate myStudent == altStudent to true, because Hibernate maintains the object reference as part of the open session.

Conversely, this state is not shared across sessions, and so if myStudent and altStudent were retrieved from different sessions, they may or may not evaluate to true, depending on the session, cache, thread, and other JVM details. All this means that you don't need to worry about object identity or concurrency as long as you don't share a session across threads (virtually never a good idea).

Later in this chapter, we will show how to use Hibernate object life-cycle methods to examine the issues of identity in more detail.

Generating Identity (Primary Keys)

The terms identity and primary key have different meanings, but they are often used interchangeably when working with Hibernate. Object identity refers to the more general notion of identitya unique value that can be used to retrieve a unique object.

Generally speaking, when a record is stored in a database, it is given a pri mary key as the main indicator of its identity. As a safeguard against possible changes to the application, this primary key typically has no particular business value or association with other data in the record.

WARNING

It bears repeating: you are strongly encouraged to not use a primary key with a business value. Avoiding the use of business values is sometimes trickier than might be expected. For example, you may think that a social security number (a standard U.S. government identifier) would serve as a reasonable primary key number. Unfortu nately, in this age of identity theft, it's possible for people to change their social security numbers (and thereby the value used for key relationships). Therefore, whenever possible use a synthetic, internal-to-the-application-only value for primary keys.


In certain instances, however, object identity is not managed by a primary key, but by another mechanism. For example, the records in a collection table may be referred to by a composite key (the fusion of the two collected foreign keys), with no primary key required.

There are several built-in strategies available for primary key generation. Hibernate controls the configuration of identity generation for standard primary keys using the generator tag in the *.hbm.xml mapping file (see the generator tag in Chapter 5 for more information). Hibernate allows you to specify a custom Java class that implements the interface net.sf .hibernate.id.IdentifierGenerator for identity generation, but it also includes a large suite of built-in generation classes.

Built-In Hibernate Generators

Hibernate includes many built-in implementations of net.sf.hibernate .id.IdentifierGenerator that allow you to easily support a wide variety of possible key generators. You can refer to these built-in generators either by their fully qualified class names or by their "shortcut" names.

If you are working with an existing database, you'll want to select the generator that matches the current key generation strategy.

  • increment

    Generates in-process (i.e., within this JVM instance only) identifiers of type long, short, or int. Only guaranteed to be unique when no other process is inserting data into the same table. Do not use in a cluster.

  • identity

    Use when working with an identity column in DB2, MySQL, MS SQL Server, Sybase, or HypersonicSQL. The returned identifier is of type long, short, or int.

  • sequence (net.sf.hibernate.id.SequenceGenerator)

    Use when working with a sequence column in DB2, PostgreSQL, Oracle, SAP DB, McKoi, or a generator in Interbase. The returned identifier is of type long, short, or int.

  • hilo (net.sf.hibernate.id.TableHiLoGenerator)

    Uses a particular hi/lo table to keep manage identifier generation. Whereas a simple table generator hits the database every time an identifier is requested, the hi/lo strategy minimizes database access by requesting possible identifiers in blocks. The size of the block of possible identifiers reserved is specified by the required <param name="max_lo"> 100</param> tag.

    You can use <param nam="table">mytable</param> tag to override the default table name (hibernate_unique_key), and <param nam="column">mycolumn</param> to override the default column name (next) if desired.

    The hi/lo algorithm generates identifiers that are unique only for a particular database. This generator requires a new database transaction to retrieve the identifier, so do not use it with connections managed by JTA or with a user-supplied connection.

  • seqhilo (net.sf.hibernate.id.SequenceHiLoGenerator)

    Similar to the hi/lo algorithm, but uses an underlying sequence to generate identifiers of type long, short, or int, given a named database sequence. In addition to the <param name="max_lo">100 </param>, you will also need to identify the sequence using a <param name="sequence">hi_value</param> tag.

  • uuid.hex (net.sf.hibernate.id.UUIDHexGenerator)

    Uses a 128-bit UUID algorithm to generate identifiers of type string, unique within a network. The UUID is encoded as a 32-character-long string of hexadecimal digits. The UUIDs are based on IP address, startup time of the JVM (accurate to a quarter-second), system time, and a counter value (unique in the JVM). You can pass in a <param name= "separator">-</param> tag to configure a separator character. Use this class if you want human-readable UUID values.

  • uuid.string (net.sf.hibernate.id.UUIDStringGenerator)

    Uses the same UUID algorithm as uuid.hex, only instead of encoding as a 32-character-long string of hex digits, uses a 16-character string of ASCII characters (including unprintable, non-human-readable characters). Do not use with PostgreSQL or if you want a human to be able to reasonably view or edit the key.

  • native (assigned in net.sf.hibernate.id. Identifier Generator)

    Automatically chooses either identity, sequence, or hilo depending upon the capabilities of the target database.

  • assigned (net.sf.hibernate.id.Assigned)

    Lets the application assign an identifier to the object before Session .save() is called. This generator is especially important for use in one-to-one relationships (see the one-to-one tag in Chapter 5 for more information). Due to its inherent nature, entities that use this generator cannot be saved via the Session.saveOrUpdate() method. Instead you have to explicitly specify to Hibernate whether the object should be saved or updated by calling either Session.save() or Session .update(). If you are using this strategy, be very careful to assign proper identifiers (especially if you also rely on cascading object updates).

  • foreign (net.sf.hibernate.id.ForeignGenerator)

  • Uses the identifier of another associated object. Generally used in conjunction with a one-to-one primary-key association. Requires a <param name="property">target_key</param>.

Composite Identity

Most objects will rely on a built-in generator for key generation. Some objects, however (in particular, those used in collection tables) may rely on composite keys. An example of this is shown in the ownership table in Chapter 3. Listing 6.6 shows the description of the ownership table as given by MySQL. Note that the table has two primary keys. In the discussion that follows, we will assume that the most popular use of composite keys is for collection tables (of course this may not be true for certain applications), and therefore we will discuss them in that context.

Listing 6.6. Composite Identity Example
mysql> desc ownership;
+-------------+------------+------+-----+---------+-------+
| Field       | Type       | Null | Key | Default | Extra |
+-------------+------------+------+-----+---------+-------+
| owner_id    | bigint(20) |      | PRI | 0       |       |
| artifact_id | bigint(20) |      | PRI | 0       |       |
+-------------+------------+------+-----+---------+-------+

On reflection, you will realize that this makes it difficult to work individual records outside the context of the related objects. Although this is largely a non-issue, it makes working with composite identity records more difficult as you develop certain complex relationships. If you are content to allow Hibernate to manage your collections, and do not impose any additional fields beyond those Hibernate requires for the collection (for example, index columns as needed, but no additional data fields), you may never need to deal with composite identity directly.

Putting this in the context of the example application shown in Chapter 3, you will note that there is no class-level declaration of the ownership table. Instead, all references to the ownership table are made via collection-tag bindings. Listing 6.7 shows one side of this binding from the Artifact.hbm.xml file. A similar (but inverted) binding exists in the Owner.hbm.xml file.

Listing 6.7. One Side of a Collection
<set name="owners" table="ownership" lazy="false" inverse="true" cascade="none"
 sort="unsorted">
<key column="artifact_id"/>
<many-to-many class="com.cascadetg.ch03.Owner" column="owner_id" outer-join="auto"/>
</set>

Thus, even though this application uses a table with composite identity, there is no need to explicitly deal with the composite-identity as such; Hibernate takes care of this for us.

In certain situations, particularly when working with a legacy database schema, you won't have the luxury of a pure collection table. In situations in which you have a collection table with additional data, you are often best off representing the collection table as an independent class (an example of which is shown in Chapter 4). In this case, the table identity may need to be modeled using a composite identity tag, such as composite-element, composite-id, or composite-index. These tags, described in Chapter 5, are used in combination with nested property tags to declare the columns used to manage the composite identifiers.

It's very important to correctly implement the Object.equals() and Object.hashCode() methods to correctly handle composite identity. The default methods, as shown in Listing 6.3 and Listing 6.4, should include all of the property values required to correctly map the composite identity, not just the getId() methods as shown.

Unsaved Value

Hibernate offers a feature that allows you to perform a save-or-update with a single method call (as shown in Table 6.7). In other words, let's say that you wish to either update an object (if it exists) or create it (if it doesn't). In order to know whether an object has been created (at least from the perspective of the application running in the JVM), Hibernate will inspect certain values to see if they have been set.

The most immediate and obvious example is the primary key (or id) of the object. For example, perhaps you are using a java.lang.Long to track the primary key of an object. If this field is set to null, then Hibernate knows that the object has not been saved in the database. Similarly, if the value is not null, then Hibernate knows that the object has a primary key and therefore must exist in the database (because null is the unsaved value).

This use of a null is an important reason to use the object version of a value (e.g., java.lang.Long) instead of the primitive form (e.g., long). While null values are sometimes simulated with a primitive (e.g., 0 or -1), this is a fragile and non-standard approach. Normally, this is the most significant impact of the notion of unsaved value, but as you develop complex data models or cope with certain legacy data models, you may find other reasons to change the notion of an unsaved value.