6.2. Collections of components

You could map Image as an entity class and create a one-to-many relationship from Item to Image. However, this isn't necessary, because Image can be modeled as a value type: Instances of this class have a dependent lifecycle, don't need their own identity, and don't have to support shared references.

As a value type, the Image class defines the properties name, filename, sizeX, and sizeY. It has a single association with its owner, the Item entity class, as shown in figure 6.5.

As you can see from the composition association style (the black diamond), Image is a component of Item, and Item is the entity that is responsible for the lifecycle of Image instances. The multiplicity of the association further declares this association as many-valued—that is, many (or zero) Image instances for the same Item instance.

Let's walk through the implementation of this in Java and through a mapping in XML.

Figure 6-5. Collection of Image components in Item

6.2.1. Writing the component class

First, implement the Image class as a regular POJO. As you know from chapter 4, component classes don't have an identifier property. You must implement equals() (and hashCode()) and compare the name, filename, sizeX, and sizeY properties. Hibernate relies on this equality routine to check instances for modifications. A custom implementation of equals() and hashCode() isn't required for all component classes (we would have mentioned this earlier). However, we recommend it for any component class because the implementation is straightforward, and "better safe than sorry" is a good motto.

The Item class may have a Set of images, with no duplicates allowed. Let's map this to the database.

6.2.2. Mapping the collection

Collections of components are mapped similarly to collections of JDK value type. The only difference is the use of <composite-element> instead of an <element> tag. An ordered set of images (internally, a LinkedHashSet) can be mapped like this:

<set name="images"
     table="ITEM_IMAGE"
     order-by="IMAGENAME asc">

    <key column="ITEM_ID"/>

    <composite-element class="Image">
       <property name="name" column="IMAGENAME" not-null="true"/>
       <property name="filename" column="FILENAME" not-null="true"/>
       <property name="sizeX" column="SIZEX" not-null="true"/>
       <property name="sizeY" column="SIZEY" not-null="true"/>
    </composite-element>
</set>

The tables with example data are shown in figure 6.6.

This is a set, so the primary key of the collection table is a composite of the key column and all element columns: ITEM_ID, IMAGENAME, FILENAME, SIZEX, and SIZEY. Because these columns all appear in the primary key, you needed to declare them with not-null="true" (or make sure they're NOT NULL in any existing schema). No column in a composite primary key can be nullable—you can't identify what you don't know. This is probably a disadvantage of this particular mapping. Before you improve this (as you may guess, with an identifier bag), let's enable bidirectional navigation.

Figure 6-6. Example data tables for a collection of components mapping

6.2.3. Enabling bidirectional navigation

The association from Item to Image is unidirectional. You can navigate to the images by accessing the collection through an Item instance and iterating: anItem.getImages().iterator(). This is the only way you can get these image objects; no other entity holds a reference to them (value type again).

On the other hand, navigating from an image back to an item doesn't make much sense. However, it may be convenient to access a back pointer like anImage.getItem() in some cases. Hibernate can fill in this property for you if you add a <parent> element to the mapping:

<set name="images"
     table="ITEM_IMAGE"
     order-by="IMAGE_NAME asc">

    <key column="ITEM_ID"/>

    <composite-element class="Image">
       <parent name="item"/>
       <property name="name" column="IMAGENAME" not-null="true"/>
       <property name="filename" column="FILENAME" not-null="true"/>
       <property name="sizeX" column="SIZEX" not-null="true"/>
       <property name="sizeY" column="SIZEY" not-null="true"/>
    </composite-element>
</set>

True bidirectional navigation is impossible, however. You can't retrieve an Image independently and then navigate back to its parent Item. This is an important issue: You can load Image instances by querying for them. But these Image objects won't have a reference to their owner (the property is null) when you query in HQL or with a Criteria. They're retrieved as scalar values.

Finally, declaring all properties as not-null is something you may not want. You need a different primary key for the IMAGE collection table, if any of the property columns are nullable.

6.2.4. Avoiding not-null columns

Analogous to the additional surrogate identifier property an <idbag> offers, a surrogate key column would come in handy now. As a side effect, an <idset> would also allow duplicates—a clear conflict with the notion of a set. For this and other reasons (including the fact that nobody ever asked for this feature), Hibernate doesn't offer an <idset> or any surrogate identifier collection other than an <idbag>. Hence, you need to change the Java property to a Collection with bag semantics:

private Collection images = new ArrayList();
...
public Collection getImages() {
    return this.images;
}

public void setImages(Collection images) {
    this.images = images;
}

This collection now also allows duplicate Image elements—it's the responsibility of your user interface, or any other application code, to avoid these duplicate elements if you require set semantics. The mapping adds the surrogate identifier column to the collection table:

<idbag name="images"
       table="ITEM_IMAGE"
       order-by="IMAGE_NAME asc">

    <collection-id type="long" column="ITEM_IMAGE_ID">
        <generator class="sequence"/>
    </collection-id>
    <key column="ITEM_ID"/>

    <composite-element class="Image">
       <property name="name" column="IMAGENAME"/>
       <property name="filename" column="FILENAME" not-null="true"/>
       <property name="sizeX" column="SIZEX"/>
       <property name="sizeY" column="SIZEY"/>
    </composite-element>
</idbag>

The primary key of the collection table is now the ITEM_IMAGE_ID column, and it isn't important that you implement equals() and hashCode() on the Image class (at least, Hibernate doesn't require it). Nor do you have to declare the properties with not-null="true". They may be nullable, as can be seen in figure 6.7.

Figure 6-7. Collection of Image components using a bag with surrogate key

We should point out that there isn't a great deal of difference between this bag mapping and a standard parent/child entity relationship like the one you map later in this chapter. The tables are identical. The choice is mainly a matter of taste. A parent/child relationship supports shared references to the child entity and true bidirectional navigation. The price you'd pay is more complex lifecycles of objects. Value-typed instances can be created and associated with the persistent Item by adding a new element to the collection. They can be disassociated and permanently deleted by removing an element from the collection. If Image would be an entity class that supports shared references, you'd need more code in your application for the same operations, as you'll see later.

Another way to switch to a different primary key is a map. You can remove the name property from the Image class and use the image name as the key of a map:

<map name="images"
     table="ITEM_IMAGE"
     order-by="IMAGENAME asc">

    <key column="ITEM_ID"/>

    <map-key type="string" column="IMAGENAME"/>

    <composite-element class="Image">
       <property name="filename" column="FILENAME" not-null="true"/>
       <property name="sizeX" column="SIZEX"/>
       <property name="sizeY" column="SIZEY"/>
    </composite-element>
</map>

The primary key of the collection table is now a composite of ITEM_ID and IMAGENAME.

A composite element class like Image isn't limited to simple properties of basic type like filename. It may contain other components, mapped with <nested-composite-element>, and even <many-to-one> associations to entities. It can't own collections, however. A composite element with a many-to-one association is useful, and we come back to this kind of mapping in the next chapter.

This wraps up our discussion of basic collection mappings in XML. As we mentioned at the beginning of this section, mapping collections of value types with annotations is different compared with mappings in XML; at the time of writing, it isn't part of the Java Persistence standard but is available in Hibernate.

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

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