Chapter 7. Relationships

Object/relational mapping highlights the confusion arising from the fact that experts in the object-oriented world and the relational world use different concepts and terminology. For example, a Java developer may speak in terms of a collection and classes, whereas a database guru will talk of things in terms of foreign keys and joins. In this chapter, we'll try to explain and reconcile these terms.

The discussion in this chapter breaks the different types of relationships into a few basic categories.

  • Database Relationships: How relationships are viewed from a database perspective. Regardless of the type of relationship you choose, it will (obviously) be stored in the database somehow.

  • Java Collection Relationships: How groups of objects can relate to one other. Governs the precise behavior of a database relationship from the Java application's perspective. All collection tags (described in Chapter 5) require a nested database relationship tag.

  • Java Class Relationships: The expression of a Java class hierarchy in a database. An example of this is shown in Chapter 3.

The chapter concludes with a discussion of bi-directional collection relationships and the notion of an “any” relationship, one of the more advanced uses of Hibernate that combines aspects of Java class relationships with collection behavior.

TIP

Hibernate uses many terms in ways that differ from what a Java developer might expect. For example, the Java terms component, class, association, subclass, and collection also refer to specific Hibernate functionality or mapping tags. If a reference to a concept is confusing, make sure that you understand whether the context is a database, Java, or Hibernate.

Database Relationships

There are a few basic mechanisms for tracking relationships between tables in a database. The many-to-one and one-to-many relationships are used to model the simplest versions of a foreign key, and should be considered your default (especially if you are modeling legacy data). The next stage, using Hibernate to manage collection tables using the many-to-many relationship, should be considered a somewhat more advanced technique (but not necessarily—don't fall into the trap of trying to jam an incompatible model into a many-to-many relationship). Finally, a one-to-one model can be useful in certain situations.

Many-to-One

The name many-to-one simply refers to another object (in Java terms) or a foreign key (in database terms).

The many-to-one and one-to-many terms describe two sides of the same relationship. In Figure 7.1, the many-to-one relationship is used to describe the relationship from the perspective of the Parts table.

Basic Foreign-Key Relationship

Figure 7.1. Basic Foreign-Key Relationship

One-to-Many

The one-to-many relationship is used to model a collection (in Java terms) or the referred table of a foreign-key relationship (in database terms). The precise behavior of a one-to-many collection is determined by the type of collection (as described below, under the heading Java Collection Relationships).

A one-to-many relationship is generally modeled by a table with a foreign key. In Figure 7.1, for instance, a one-to-many relationship is described by the CarModel table. The one-to-many model is used to represent the relationship from the perspective of the target table.

This relationship is modeled in the CarModel mapping file, but note that the CarModel table does not actually contain any data referring to the Parts table. The only notion of the relationship that is expressed in the database comes from the addition of a foreign key (as shown in Chapter 4). Putting this in parent/child terms, the relationship is modeled on the parent, but technically the data is stored in the child.

If the one-to-many relationship is part of a bi-directional relationship (in other words, if you can navigate using your Java code from the CarModel object to Parts and back), you will probably wish to set the inverse="true" attribute for the collection tag used to wrap the relationship. For more information on bi-directional relationships, see the section at the end of this chapter.

Many-to-Many

A many-to-many relationship is used to model a collection (in Java terms) or a collection table (in database terms). In a many-to-many relationship, neither the parent nor the child table contains data about the relationship. Instead this information is stored in a collection table. Figure 7.2 shows a collection table, CarModelOrders.

Collection Table

Figure 7.2. Collection Table

Because there is no additional information in the CarModelOrders collection table, using a many-to-many relationship allows Hibernate to effectively “hide” the CarModelOrders table. Since CarModelOrders is present in the mapping files for CarModels and Orders, there is no need to make a separate mapping file for it.

Hibernate expects a many-to-many relationship to be mapped as a simple collection table, meaning that no additional data is stored in the table. You may occasionally wish to store a many-to-many mapping that also contains additional information. Since this table would no longer be a pure binding, it should be modeled with a new class and many-to-one/one-to-many relationships, as shown in Chapter 4.

If the many-to-many relationship is part of a bi-directional relationship (i.e., you can expect to navigate using your Java code from CarModel to Orders and back), you must set the inverse="true" attribute on one side of the relationship. For more information on bi-directional relationships, see the section at the end of this chapter.

One-to-One

A one-to-one relationship models two tables that are logically “glued” together in some fashion. For example, an existing human resources database might have a table used to represent employees and another table to represent employee medical records. The table for employees is available to all human resources staff, whereas the medical record table is only accessible via a special administrative connection to maintain high security. You would therefore use one-to-one mapping in the administrative application to manage this relationship.

You will need to use the assigned generator for one side of your one-to-one relationship. See Chapter 6 for more information.

Java Collection Relationships

A collection is a group of objects related to another group of objects by aggregation. For example, a weblog has a collection of posts. Depending on the type of collection, this may mean a one-to-one, many-to-one, one-to-many, or many-to-many relationship. In other words, to represent a Java collection, you will first need to choose the underlying database relationship (as described above).

Most developers assume that an ordinary foreign-key-based, one-to-many relationship is best modeled with a java.util.List. However, the precise index of a relationship is actually not included as a foreign key. For example, consider a weblog with a relationship to a number of posts. While you may retrieve the posts in order (for example, sorted by date), the precise index of the post as part of the relationship is not part of the database record. Instead, the result set (and ordering) is an artifact of the runtime results as retrieved by the query.

  • Array: An ordinary, pure Java array. Keep in mind that an array is a primitive, and therefore must be loaded entirely by the owning class (i.e., it cannot be specified as lazy; see tag class, attribute lazy in Chapter 5 for more information).

  • Bag: An unordered collection that may contain duplicate elements.

  • List: An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list.

  • Map: An object that maps keys to values. A map cannot contain duplicate keys; each key can map to at most one value. A sorted map will maintain the keys in order.

  • Primitive Array: An array of Java primitive types. Like an array, loaded entirely by the owning class and cannot be lazily loaded.

  • Set: A collection that contains no duplicate elements. More formally, sets contain no pair of elements e1 and e2 such that e1.equals(e2), and at most one null element. A sorted set will maintain the elements in order.

Several of these collection types enforce rules regarding duplicates. The rules for identity (used to handle duplicates) are described in more detail in Chapter 6.

Table 7.1 compares the different features of the collection types. The implementation column is provided for reference if you are unsure which implementation to use when creating a new collection of the appropriate type; technically, you can use any implementation of the interface shown. You should only work with these collections using the interface shown, and never rely on the underlying implementation, because Hibernate will sometimes change the underlying implementation class (for example, as part of the ability of Hibernate to manage lazy-loaded relationships).

Table 7.1. Collection Features

 

Interface

Implementation

Duplicates

Keys

Indexed

Lazy

Ordered

Array

Object[]

Object[]

Yes

No

Yes

No

Yes

Bag

java.util.List

java.util.ArrayList

Yes

No

No

Yes

No

List

java.util.List

java.util.ArrayList

Yes

No

Yes

Yes

Yes

Map

java.util.Map

java.util.HashMap
java.util.SortedMap

No

Yes

No

Yes

Optional

Primitive Array

primitive[]

n/a

Yes

No

Yes

No

Yes

Set

java.util.Set

java.util.HashSet
java.util.SortedSet

No

No

No

No

Optional

For more information on the various built-in JDK 1.4 collections and alternative interfaces, see http://java.sun.com/j2se/1.4.2/docs/guide/collections/reference.html.

  • Interface: The expected Java interface for this collection type.

  • Implementation: A suggested Java implementation.

  • Duplicates: Are duplicate elements allowed?

  • Keys: Are elements accessible by a key value (e.g. ,java.util.Hashtable)?

  • Indexed: Does a column in the database maintain the order of the elements?

  • Lazy: Can the parent object be loaded without loading the collection data?

  • Ordered: Are the results returned as a sorted collection?

The feature set shown in Table 7.1 has both functional implications and performance implications. Table 7.2 shows a rough guide to the relative performance of the different collection types.

Table 7.2. Relative Collection Performance

 

Keys

Indexed

Lazy

Insert

Update

Delete

Array

No

Yes

No

Medium

Medium

Medium

Bag

No

No

Yes

High

Poor

Poor

List

No

Yes

Yes

Medium

High

Medium

Map

Yes

No

Yes

Medium

High

Medium

Primitive Array

No

Yes

No

Medium

Medium

Medium

Set

No

No

No

Medium

High

Medium

Java Class Relationships

The Java class relationships in Hibernate boil down to two main types, subclasses and components.

A subclass refers to an object-oriented programmer's typical view of a class hierarchy. In the object-oriented view, a dog could be modeled as a subclass of mammal. From a database developer's perspective, subclasses are a way for a Java developer to logically group several different types of records that might be stored in a given table.

A component is a way to nest data within a particular class. For example, a Person class might use a nested component to store the person's address. This is simply a Java developer's tool for managing a table with many columns.

TIP

If you are designing an application top-down, you will probably want to use the discriminator column strategy described in subclasses rather than the joined subclass strategy. This is likely to be the first strategy supported by EJB 3.0, but joined subclasses may not be available until EJB 3.1.

Subclasses

Consider an application that wishes to persist objects of the type com.example.Animal, com.example.Cat, and com.example.Bird to a single table (Cat and Bird, of course, extend Animal). Animal records have the properties name and weight. Cat entries have the additional properties color and teeth, and Bird records the additional properties color and wingspan. A column (called a discriminator) is present in the single table used to keep track of all three classes. The Animal, Cat, and Bird records would be tracked in the database as shown in Table 7.3.

Table 7.3. Single Table Subclasses

Id

Discriminator

Name

Weight

Color

Teeth

Wingspan

1

com.example.Animal

Bob

10

null

null

null

2

com.example.Cat

Ashe

8

gray

26

null

3

com.example.Bird

Mika

2

blue

null

10

Because color is common to both com.example.Cat and com.example.Bird, it is not repeated. Note that columns not used by an object are set to null—for example, com.example.Cat has no wingspan, and therefore that value is set to null. Make sure that the columns used by your subclass do not define properties with the attribute not-null="true". Otherwise, an attempt to save a Cat may fail with an error indicating that there is no wingspan.

The *.hbm.xml mapping to corresponding to the table in Table 7.3 is shown in Listing 7.1.

Example 7.1. Subclass Mapping

<hibernate-mapping>
      <class name="com.example.Animal">
            <id name="id" type="long" column="ID">
            <generator class="native"/></id>
            <discriminator column="discriminator"
            type="string"/>
            <property name="name" type="string" />
            <property name="weight" type="integer" />
            <subclass name="com.example.Cat">
                <property name="color" type="string"
                column="color"/>
                <property name="teeth" type="integer"
                column="teeth" />
            </subclass>
            <subclass name="com.example.Bird">
                <property name="color" type="string"
                column="color"/>
                <property name="wingspan" type="integer"
                column="wingspan" />
            </subclass>
      </class>
</hibernate-mapping>

Note that the subclass declarations in Listing 7.1 do not specify an id tag (or a timestamp or version tag). These must be declared in the base class.

If you wish to use multiple tables for your subclasses instead of a single table, you will actually be using a joined-subclass relationship. You can't use both a subclass and a joined subclass in the same class tag; you have to choose one strategy or the other.

Joined Subclasses

A joined subclass is used to define a multiple-table object hierarchy in Hibernate. For example, let's say we wish to persist objects of the type com.example.Animal, com.example.Cat, and com.example.Bird to three tables, one per class (Cat and Bird, of course, extend Animal). Instead of using a discriminator column (as per a normal subclass), the application will use a JOIN statement to mesh the tables together as needed (hence the name “joined subclass”). Animal records have the properties name and weight. Cat records define the additional properties color and teeth, Bird records the additional properties color and wingspan. So our Animal, Cat, and Bird records could be tracked using three tables, as shown Tables 7.4, 7.5, and 7.6.

Table 7.4. Animal Base Class

Id

Name

Weight

1

Bob

10

2

Ashe

8

3

Mika

2

Table 7.5. Cat Joined Subclass

Id

Color

Teeth

2

Gray

26

Table 7.6. Bird Joined Subclass

Id

Color

Wingspan

3

Blue

10

In this case, the primary key values are used to keep track of the appropriate class— no discriminator is required.

The *.hbm.xml mapping corresponding to Table 7.4, Table 7.5, and Table 7.6 would be as shown in Listing 7.2.

Example 7.2. Joined Subclass Mapping

<hibernate-mapping>
      <class name="com.example.Animal" table="Animal">
            <id name="id" type="long" column="ID">
                  <generator class="native"/></id>
            <property name="name" type="string" />
            <property name="weight" type="integer" />
            <joined-subclass name="com.example.Cat"
            table="Cat">
                <key column="ID" />
                <property name="color" type="string"
                column="color"/>
                <property name="teeth" type="integer"
                column="teeth" />
            </subclass>
            <subclass name="com.example.Bird" table="Bird">
                <key column="ID" />
                <property name="color" type="string"
                column="color"/>
                <property name="wingspan" type="integer"
                column="wingspan" />
             </subclass>
      </class>
</hibernate-mapping>

Note that the subclasses specify a key tag pointing back to the id property of com.example.Animal. As a reminder: you can't use both subclass and joined subclass in the same class tag—you'll need to choose one strategy or the other.

Components

In Hibernate terminology, a component refers to the notion of breaking up a table into pieces. For example, many User tables grow rather large because they contain information about several different aspects of the user. With some applications, it may be easier to map these aspects separately using components instead of a single monolithic user class.

The use of a component allows for certain interesting features. For example, setting a component to null is the same as setting all of the subvalues to null. Continuing the example of a User table, you could invalidate all of the user's address fields with a single call—simply setting the address component to null—instead of naming the individual components.

You might even map multiple sequences but bind them to the same component, allowing your Java application to reuse an object to apply data in multiple places. For example, consider the mapping shown in Listing 7.3.

TIP

The use of components generally boils down to a question of the number of columns in a given table. For example, a user table with 50 columns might be an excellent candidate for the use of components. Generally speaking, I find components to be most useful when I am dealing with a legacy system, and I am more likely to model logical components with a one-to-one relationship for new development (especially if I have a core subset of the columns that will be used much more frequently than the rest).

Example 7.3. Example Component Mapping

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping SYSTEM "C:devenvhibernate-2.1.2src
etsfhibernate
Example Component Mappinghibernate-mapping-2.0.dtd">
<hibernate-mapping>
  <class name="com.cascadetg.ch07.ComponentExample"
      table="componentexample">
    <id name="id" column="ID" type="string" unsaved-
    value="null">
      <generator class="uuid.hex"/>
    </id>
    <property name="name" column="name"
      type="string" length="50" not-null="true"/>
      <component name="homeAddress"
            class="com.cascadetg.ch07.AddressComponent">
          <property name="street" column="home_street"
          type="char" />
          <property name="city" column="home_city" type="char" />
      </component>
      <component
            name="workAddress"
            class="com.cascadetg.ch07.AddressComponent">
              <property name="street" column="work_street"
              type="string" />
              <property name="city" column="work_city"
              type="string" />
      </component>
  </class>
</hibernate-mapping>

The mapping file shown in Listing 7.3 would only have a single table (componentexample), but would correspond to two Java classes (ComponentExample and AddressComponent). The columns of the componentexample table would be id, name, home_street, home city, work_street, and work city.

Given this mapping, it would then be possible to set both the home and work address information using the same object, as shown in Listing 7.4.

Example 7.4. Using Components

hibernateSession = sessionFactory.openSession();
myTransaction = hibernateSession.beginTransaction();

ComponentExample myMessage = new ComponentExample();
myMessage.setName("foo");

AddressComponent myAddressComponent = new AddressComponent();
myAddressComponent.setStreet("123 Anywhere");
myAddressComponent.setCity("Universal City");

myMessage.setHomeAddress(myAddressComponent);
myMessage.setWorkAddress(myAddressComponent);

hibernateSession.save(myMessage);

myTransaction.commit();

A component is used to break up a single monolithic table, whereas a one-to-one mapping is used to associate two different tables.

Any-Based Relationships

A relationship based on the concept of an any endpoint (for example, one-to-any or any-to-any) allows you to map a relationship from one table to more than one other table.

For example, let's say that you have three tables, a House table, an Auto table, and a Transaction table. The application wishes to record transactions in which a person has bought or sold either a car or a house. Figure 7.3 shows this relationship as expressed in the database.

Any Relationship

Figure 7.3. Any Relationship

As shown in Figure 7.3, by using an any relationship, you store a reference in each Transaction with an reference identifier and a discriminator indicating the proper target table.

For more information and some examples of different types of relationships, see http://www.xylax.net/hibernate/, which provides a list of mapping types, with explanations in terms of both the database schema and the Java code.

Bi-directional Relationships

Many of the relationships you will wish to model using Hibernate will be bi-directional. For example, given an author, you will wish to know the posts written by that author, and given a post, you will wish to know the author. As you use the various collection tags to model these bi-directional relationships, you will encounter one of the more confusing aspects of Hibernate: the inverse attribute.

Simply put, inverse="false" defines the side of a bi-directional relationship responsible for maintaining the association. The collection mapping with the inverse="false" attribute (the default value) is responsible for the appropriate SQL query (INSERT, UPDATE, or DELETE). If the inverse="false" attribute is set on a one-to-many relationship, the column of the target will be updated. If inverse="false" is set on a many-to-many association, the association table will be updated.

Conversely, Hibernates ignores changes made to the association set to inverse="true".

This can be somewhat confusing, but once you understand that the inverse="false" side is responsible for managing a relationship, there are just a few simple guidelines to follow.

If you are using a one-to-many/many-to-one bi-directional relationship, the many-to-one side will be responsible for managing the relationship (and therefore the one-to-many side should be set to inverse="true"). For example, in the example shown in Chapter 2, the Author side of the bi-directional relationship is set to inverse="true". This means that Hibernate ignores changes to the Author object's set of posts. For performance reasons, this is virtually always the correct model.

The choice of inverse="true" and inverse="false" is really only complex in a bi-directional many-to-many relationship (i.e., when Hibernate handles a collection table is handled under the covers). In this situation, the rule of thumb is that inverse="false" should be set for the side of the relationship with the smallest number of elements changed the most frequently. For example, consider the relationship shown in Figure 7.4.

Choosing the Inverse Side

Figure 7.4. Choosing the Inverse Side

Assuming that the list of beverages (i.e., the Beverage table) is relatively small and static, but that the customers are numerous and place many orders, you should model this many-to-many relationship by setting the mapping of the Beverage collection to inverse="true" and the Customer side to inverse="false".

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

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