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 SubclassesId | 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 nullfor 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.
Listing 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 ClassId | Name | Weight |
|---|
1 | Bob | 10 | 2 | Ashe | 8 | 3 | Mika | 2 |
Table 7.5. Cat Joined SubclassId | Color | Teeth |
|---|
2 | Gray | 26 |
Table 7.6. Bird Joined SubclassId | 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.
Listing 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 tagyou'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 callsimply setting the address component to nullinstead 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).
Listing 7.3. Example Component Mapping
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping SYSTEM "C:\devenv\hibernate-2.1.2\src\net\sf\hibernate
\hibernate-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.
Listing 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.
 |