Chapter 7. Mapping collections and entity associations

In this chapter

  • Mapping persistent collections
  • Collections of basic and embeddable type
  • Simple many-to-one and one-to-many entity associations

From our experience with the Hibernate user community, the first thing many developers try to do when they begin using Hibernate is to map a parent/children relationship. This is usually the first time they encounter collections. It’s also the first time they have to think about the differences between entities and value types, or get lost in the complexity of ORM.

Managing the associations between classes and the relationships between tables is at the heart of ORM. Most of the difficult problems involved in implementing an ORM solution relate to collections and entity association management. Feel free to come back to this chapter to grok this topic fully. We start this chapter with basic collection-mapping concepts and simple examples. After that, you’ll be prepared for the first collection in an entity association—although we’ll come back to more complicated entity association mappings in the next chapter. To get the full picture, we recommend you read both this chapter and the next.

Major new features in JPA 2

  • Support for collections and maps of basic and embeddable types.
  • Support for persistent lists where the index of each element is stored in an additional database column.
  • One-to-many associations now have an orphan-removal option.

7.1. Sets, bags, lists, and maps of value types

Java has a rich collection API, from which you can choose the interface and implementation that best fits your domain model design. Let’s walk through the most common collection mappings, repeating the same Image and Item example with minor variations. We’ll start by looking at the database schema and creating and mapping a collection property in general. Then we’ll proceed to how to select a specific collection interface and map various collection types: a set, identifier bag, list, map, and finally sorted and ordered collections.

7.1.1. The database schema

Let’s extend CaveatEmptor and support attaching images to auction items. Ignore the Java code for now, and take a step back to consider only the database schema.

You need an IMAGE table in the database to hold the images, or maybe just the filenames of images. This table also has a foreign key column, say ITEM_ID, referencing the ITEM table. Look at the schema shown in figure 7.1.

Figure 7.1. The IMAGE table holds image filenames, each referencing an ITEM_ID.

That’s all there is to the schema—no collections or composition life cycle. (You could have an ON DELETE CASCADE option on the ITEM_ID foreign key column. When the application deletes an ITEM row, the database automatically deletes IMAGE rows referencing this ITEM in the database. Let’s assume for now that this isn’t the case.)

7.1.2. Creating and mapping a collection property

How would you map this IMAGE table with what you know so far? You’d probably map it as an @Entity class named Image. Later in this chapter, you’ll map a foreign key column with an @ManyToOne property. You’d also need a composite primary key mapping for the entity class, as shown in section 9.2.2.

There are no mapped collections; they aren’t necessary. When you need an item’s images, you write and execute a query in the JPA query language: select img from Image img where img.item = :itemParameter. Persistent collections are always optional—a feature. Why would you map a collection?

The collection you could create is Item#images, referencing all images for a particular item. You create and map this collection property to do the following:

  • Execute the SQL query SELECT * from IMAGE where ITEM_ID = ? automatically when you call someItem.getImages(). As long as your domain model instances are in a managed state (more later), you can read from the database on-demand while navigating the associations between your classes. You don’t have to manually write and execute a query with the EntityManager to load data. On the other hand, the collection query when you start iterating the collection is always “all images for this item,” never “only images that match criteria XYZ.”
  • Avoid saving each Image with entityManager.persist(). If you have a mapped collection, adding the Image to the collection with someItem.getImages().add() will make it persistent automatically when the Item is saved. This cascading persistence is convenient because you can save instances without calling EntityManager.
  • Have a dependent life cycle of Images. When you delete an Item, Hibernate deletes all attached Images with an extra SQL DELETE. You don’t have to worry about the life cycle of images and cleaning up orphans (assuming your database foreign key constraint doesn’t ON DELETE CASCADE). The JPA provider handles the composition life cycle.

It’s important to realize that although these benefits sound great, the price you pay is additional mapping complexity. We’ve seen many JPA beginners struggle with collection mappings, and frequently the answer to “Why are you doing this?” has been “I thought this collection was required.”

Analyzing the scenario with images for auction items, you’d benefit from a collection mapping. The images have a dependent life cycle; when you delete an item, all the attached images should be deleted. When an item is stored, all attached images should be stored. And when you display an item, you often also display all images, so someItem.getImages() is convenient in UI code. You don’t have to call the persistence service again to get the images; they’re just there.

Now, on to choosing the collection interface and implementation that best fits your domain model design. Let’s walk through the most common collection mappings, repeating the same Image and Item example with minor variations.

7.1.3. Selecting a collection interface

The idiom for a collection property in the Java domain model is

<<Interface>> images = new <<Implementation>>();
// Getter and setter methods
// ...

Use an interface to declare the type of the property, not an implementation. Pick a matching implementation, and initialize the collection right away; doing so avoids uninitialized collections. We don’t recommend initializing collections late, in constructors or setter methods.

Using generics, here’s a typical Set:

Set<String> images = new HashSet<String>();
Raw collections without generics

If you don’t specify the type of collection elements with generics, or the key/value types of a map, you need to tell Hibernate the type(s). For example, instead of a Set<String>, you map a raw Set with @ElementCollection(targetClass=String.class). This also applies to type parameters of a Map. Specify the key type of a Map with @MapKeyClass. All the examples in this book use generic collections and maps, and so should you.

Out of the box, Hibernate supports the most important JDK collection interfaces and preserves the semantics of JDK collections, maps, and arrays in a persistent fashion. Each JDK interface has a matching implementation supported by Hibernate, and it’s important that you use the right combination. Hibernate wraps the collection you’ve already initialized on declaration of the field, or sometimes replaces it, if it’s not the right one. It does that to enable, among other things, lazy loading and dirty checking of collection elements.

Without extending Hibernate, you can choose from the following collections:

  • A java.util.Set property, initialized with a java.util.HashSet. The order of elements isn’t preserved, and duplicate elements aren’t allowed. All JPA providers support this type.
  • A java.util.SortedSet property, initialized with a java.util.TreeSet. This collection supports stable order of elements: sorting occurs in-memory, after Hibernate loads the data. This is a Hibernate-only extension; other JPA providers may ignore the “sorted” aspect of the set.
  • A java.util.List property, initialized with a java.util.ArrayList. Hibernate preserves the position of each element with an additional index column in the database table. All JPA providers support this type.
  • A java.util.Collection property, initialized with a java.util.ArrayList. This collection has bag semantics; duplicates are possible, but the order of elements isn’t preserved. All JPA providers support this type.
  • A java.util.Map property, initialized with a java.util.HashMap. The key and value pairs of a map can be preserved in the database. All JPA providers support this type.
  • A java.util.SortedMap property, initialized with a java.util.TreeMap. It supports stable order of elements: sorting occurs in-memory, after Hibernate loads the data. This is a Hibernate-only extension; other JPA providers may ignore the “sorted” aspect of the map.
  • Hibernate supports persistent arrays, but JPA doesn’t. They’re rarely used, and we won’t show them in this book: Hibernate can’t wrap array properties, so many benefits of collections, such as on-demand lazy loading, won’t work. Only use persistent arrays in your domain model if you’re sure you won’t need lazy loading. (You can load arrays on-demand, but this requires interception with bytecode enhancement, as explained in section 12.1.3.)
Hibernate Feature

If you want to map collection interfaces and implementations not directly supported by Hibernate, you need to tell Hibernate about the semantics of your custom collections. The extension point in Hibernate is the PersistentCollection interface in the org.hibernate.collection.spi package, where you usually extend one of the existing PersistentSet, PersistentBag, and PersistentList classes. Custom persistent collections aren’t easy to write, and we don’t recommend doing this if you aren’t an experienced Hibernate user. You can find an example in the source code for the Hibernate test suite.

For the auction item and images example, assume that the image is stored somewhere on the file system and that you keep just the filename in the database.

Transactional file systems

If you only keep the filenames of images in your SQL database, you have to store the binary data of each picture—the files—somewhere. You could store the image data in your SQL database, in BLOB columns (see the section “Binary and large value types” in chapter 5). If you decide not to store the images in the database, but as regular files, you should be aware that the standard Java file system APIs, java.io.File and java.nio.file.Files, aren’t transactional. File system operations aren’t enlisted in a (JTA) system transaction; a transaction might successfully complete, with Hibernate writing the filename into your SQL database, but then storing or deleting the file in the file system might fail. You won’t be able to roll back these operations as one atomic unit, and you won’t get proper isolation of operations.

Fortunately, open source transactional file system implementations for Java are available, such as XADisk (see https://xadisk.java.net). You can easily integrate XADisk with a system transaction manager such as Bitronix, used by the examples of this book. File operations are then enlisted, committed, and rolled back together with Hibernate’s SQL operations in the same UserTransaction.

Let’s map a collection of image filenames of an Item.

7.1.4. Mapping a set

The simplest implementation is a Set of String image filenames. Add a collection property to the Item class, as shown in the following listing.

Listing 7.1. Images mapped as a simple set of strings

Path: /model/src/main/java/org/jpwh/model/collections/setofstrings/Item.java

The @ElementCollection JPA annotation is required for a collection of value-typed elements. Without the @CollectionTable and @Column annotations, Hibernate would use default schema names. Look at the schema in figure 7.2: the primary key columns are underlined.

Figure 7.2. Table structure and example data for a set of strings

The IMAGE table has a composite primary key of both the ITEM_ID and FILENAME columns. That means you can’t have duplicate rows: each image file can only be attached once to one item. The order of images isn’t stored. This fits the domain model and Set collection.

It doesn’t seem likely that you’d allow the user to attach the same image more than once to the same item, but let’s suppose you did. What kind of mapping would be appropriate in that case?

7.1.5. Mapping an identifier bag

Hibernate Feature

A bag is an unordered collection that allows duplicate elements, like the java.util.Collection interface. Curiously, the Java Collections framework doesn’t include a bag implementation. You initialize the property with an ArrayList, and Hibernate ignores the index of elements when storing and loading elements.

Listing 7.2. Bag of strings, allowing duplicate elements

Path: /model/src/main/java/org/jpwh/model/collections/bagofstrings/Item.java

This looks much more complex: you can’t continue with the same schema as before. The IMAGE collection table needs a different primary key to allow duplicate FILENAME values for each ITEM_ID. You introduce a surrogate primary key column named IMAGE_ID , and you use a Hibernate-only annotation to configure how the primary key is generated . If you don’t remember key generators, read section 4.2.4. The modified schema is shown in figure 7.3.

Figure 7.3. Surrogate primary key column for a bag of strings

Here’s an interesting question: if all you see is this schema, can you tell how the tables are mapped in Java? The ITEM and IMAGE tables look the same: each has a surrogate primary key column and some other normalized columns. Each table could be mapped with an @Entity class. We decided to use a JPA feature and map a collection to IMAGE, however, even with a composition life cycle. This is, effectively, a decision that some predefined query and manipulation rules are all you need for this table, instead of the more generic @Entity mapping. When you make such a decision, be sure you know the reasons and consequences.

The next mapping technique preserves the order of images with a list.

7.1.6. Mapping a list

When you haven’t used ORM software before, a persistent list seems to be a very powerful concept; imagine how much work storing and loading a java.util.List <String> is with plain JDBC and SQL. If you add an element to the middle of the list, depending on the list implementation, the list shifts all subsequent elements to the right or rearranges pointers. If you remove an element from the middle of the list, something else happens, and so on. If the ORM software can do all of this automatically for database records, this makes a persistent list look more appealing than it actually is.

As we noted in section 3.2.4, the first reaction is often to preserve the order of data elements as users enter them. You often have to show them later in the same order. But if another criterion can be used for sorting the data, like an entry timestamp, you should sort the data when querying and not store the display order. What if the display order changes? The order the data is displayed in is most likely not an integral part of the data, but an orthogonal concern. Think twice before you map a persistent List; Hibernate isn’t as smart as you might think, as you’ll see in the next example.

First, let’s change the Item entity and its collection property.

Listing 7.3. Persistent list, preserving the order of elements in the database

Path: /model/src/main/java/org/jpwh/model/collections/listofstrings/Item.java

There is a new annotation in this example: @OrderColumn. This column stores an index in the persistent list, starting at zero. Note that Hibernate stores and expects the index to be contiguous in the database. If there are gaps, Hibernate will add null elements when loading and constructing the List. Look at the schema in figure 7.4.

Figure 7.4. The collection table preserves the position of each list element.

The primary key of the IMAGE table is a composite of ITEM_ID and IMAGES_ORDER. This allows duplicate FILENAME values, which is consistent with the semantics of a List.

We said earlier that Hibernate isn’t as smart as you might think. Consider modifications to the list: say the list has the three elements A, B, and C, in that order. What happens if you remove A from the list? Hibernate executes one SQL DELETE for that row. Then it executes two UPDATEs, for B and C, shifting their position to the left to close the gap in the index. For each element to the right of the deleted element, Hibernate executes an UPDATE. If you write SQL for this by hand, you can do it with one UPDATE. The same is true for insertions in the middle of the list. Hibernate shifts all existing elements to the right one by one. At least Hibernate is smart enough to execute a single DELETE when you clear() the list.

Now, suppose the images for an item have user-supplied names in addition to the filename. One way to model this in Java is with a map, using key/value pairs.

7.1.7. Mapping a map

Again, make a small change to the Java class to use a Map property.

Listing 7.4. Persistent map storing its key and value pairs

Path: /model/src/main/java/org/jpwh/model/collections/mapofstrings/Item.java

Each map entry is a key/value pair. Here you map the key with @MapKeyColumn to FILENAME and the value to the IMAGENAME column . This means the user can only use a file once, because a Map doesn’t allow duplicate keys.

As you can see from the schema in figure 7.5, the primary key of the collection table is a composite of ITEM_ID and FILENAME. The example uses a String as the key for the map; but Hibernate supports any basic type, such as BigDecimal and Integer. If the key is a Java enum, you must use @MapKeyEnumerated. With any temporal types such as java.util.Date, use @MapKeyTemporal. We discussed these options, albeit not for collections, in sections 5.1.6 and 5.1.7.

Figure 7.5. Tables for a map, using strings as indexes and elements

The map in the previous example was unordered. What should you do to always sort map entries by filename?

7.1.8. Sorted and ordered collections

Hibernate Feature

In a startling abuse of the English language, the words sorted and ordered mean different things when it comes to persistent collections in Hibernate. You sort a collection in memory using a Java comparator. You order a collection when it’s loaded from the database, using an SQL query with an ORDER BY clause.

Let’s make the map of images a sorted map. You need to change the Java property and the mapping.

Listing 7.5. Sorting map entries in memory, using a comparator

Path: /model/src/main/java/org/jpwh/model/collections/sortedmapofstrings/Item.java

@Entity
public class Item {
<enter/>
    @ElementCollection
    @CollectionTable(name = "IMAGE")
    @MapKeyColumn(name = "FILENAME")
    @Column(name = "IMAGENAME")
    @org.hibernate.annotations.SortComparator(ReverseStringComparator.class)
    protected SortedMap<String, String> images =
        new TreeMap<String, String>();
<enter/>
    // ...
}

Sorted collections are a Hibernate feature; hence the org.hibernate.annotations.SortComparator annotation with an implementation of java.util.Comparator. We won’t show you this trivial class here; it sorts strings in reverse order.

The database schema doesn’t change, which is also the case for all following examples. Look at the illustrations in the previous sections if you need a reminder.

You map a java.util.SortedSet as shown next.

Listing 7.6. Sorting set elements in memory with String#compareTo()

Path: /model/src/main/java/org/jpwh/model/collections/sortedsetofstrings/Item.java

@Entity
public class Item {
<enter/>
    @ElementCollection
    @CollectionTable(name = "IMAGE")
    @Column(name = "FILENAME")
    @org.hibernate.annotations.SortNatural
    protected SortedSet<String> images = new TreeSet<String>();
<enter/>
    // ...
}

Here natural sorting is used, falling back on the String#compareTo() method.

Unfortunately, you can’t sort a bag; there is no TreeBag. The indexes of list elements predefine their order.

Alternatively, instead of switching to Sorted* interfaces, you may want to retrieve the elements of a collection in the right order from the database, and not sort in memory. Instead of a java.util.SortedSet, a java.util.LinkedHashSet is used in the following example.

Listing 7.7. LinkedHashSet offers insertion order for iteration

Path: /model/src/main/java/org/jpwh/model/collections/setofstringsorderby/Item.java

The LinkedHashSet class has a stable iteration order over its elements, and Hibernate will fill it in the right order when loading a collection. To do this, Hibernate applies an ORDER BY clause to the SQL statement that loads the collection. You must declare this SQL clause with the proprietary @org.hibernate.annotations.OrderBy annotation. You could even call an SQL function, like @OrderBy("substring(FILENAME, 0, 3) desc"), which would sort by the first three letters of the filename. Be careful to check that the DBMS supports the SQL function you’re calling. Furthermore, you can use the SQL:2003 syntax ORDER BY ... NULLS FIRST|LAST, and Hibernate will automatically transform it into the dialect supported by your DBMS.

Hibernate @OrderBy vs. JPA @OrderBy

You can apply the annotation @org.hibernate.annotations.OrderBy to any collection; its parameter is a plain SQL fragment that Hibernate attaches to the SQL statement loading the collection. Java Persistence has a similar annotation, @javax.persistence.OrderBy. Its (only) parameter is not SQL but somePropertyDESC|ASC. A String or Integer element value has no properties. Hence, when you apply JPA’s @OrderBy annotation on a collection of basic type, as in the previous example with a Set<String>, the specification says, “the ordering will be by value of the basic objects.” This means you can’t change the order: in the previous example, the order will always be by FILENAME asc in the generated SQL query. We use the JPA annotation later when the element value class has persistent properties and isn’t of basic/scalar type, in section 7.2.2.

The next example shows the same ordering at load time with a bag mapping.

Listing 7.8. ArrayList provides stable iteration order

Path: /model/src/main/java/org/jpwh/model/collections/bagofstringsorderby/Item.java

Finally, you can load ordered key/value pairs with a LinkedHashMap.

Listing 7.9. LinkedHashMap keeps key/value pairs in order

Path: /model/src/main/java/org/jpwh/model/collections/mapofstringsorderby/Item.java

@Entity
public class Item {
<enter/>
    @ElementCollection
    @CollectionTable(name = "IMAGE")
    @MapKeyColumn(name = "FILENAME")
    @Column(name = "IMAGENAME")
    @org.hibernate.annotations.OrderBy(clause = "FILENAME desc")
    protected Map<String, String> images = new LinkedHashMap<String, String>();
<enter/>
    // ...
}

Keep in mind that the elements of ordered collections are only in the desired order when they’re loaded. As soon as you add and remove elements, the iteration order of the collections might be different than “by filename”; they behave like regular linked sets, maps, or lists.

In a real system, it’s likely that you’ll need to keep more than just the image name and filename. You’ll probably need to create an Image class for this extra information. This is the perfect use case for a collection of components.

7.2. Collections of components

You mapped an embeddable component earlier: the address of a User in section 5.2. The current situation is different because an Item has many references to an Image, as shown in figure 7.6. The association in the UML diagram is a composition (the black diamond); hence, the referenced Images are bound to the life cycle of the owning Item.

Figure 7.6. Collection of Image components in Item

The code in the next listing shows the new Image embeddable class, capturing all the properties of an image that interest you.

Listing 7.10. Encapsulating all properties of an image

Path: /model/src/main/java/org/jpwh/model/collections/setofembeddables/Image.java

@Embeddable
public class Image {
<enter/>
    @Column(nullable = false)
    protected String title;
<enter/>
    @Column(nullable = false)
    protected String filename;
<enter/>
    protected int width;
<enter/>
    protected int height;
<enter/>
    // ...
}

First, note that all properties are non-optional, NOT NULL. The size properties are non-nullable because their values are primitives. Second, you have to consider equality and how the database and Java tier compare two images.

7.2.1. Equality of component instances

Let’s say you keep several Image instances in a HashSet. You know that sets don’t allow duplicate elements. How do sets detect duplicates? The HashSet calls the equals() method on each Image you put in the Set. (It also calls the hashCode() method to get a hash, obviously.) How many images are in the following collection?

someItem.getImages().add(new Image(
        "Foo", "foo.jpg", 640, 480
));
someItem.getImages().add(new Image(
        "Bar", "bar.jpg", 800, 600
));
someItem.getImages().add(new Image(
        "Baz", "baz.jpg", 1024, 768
));
someItem.getImages().add(new Image(
        "Baz", "baz.jpg", 1024, 768
));
assertEquals(someItem.getImages().size(), 3);

Did you expect four images instead of three? You’re right: the regular Java equality check relies on identity. The java.lang.Object#equals() method compares instances with a==b. Using this procedure, you’d have four instances of Image in the collection. Clearly, three is the “correct” answer for this use case.

For the Image class, you don’t rely on Java identity—you override the equals() and hashCode() methods.

Listing 7.11. Implementing custom equality with equals() and hashCode()

Path: /model/src/main/java/org/jpwh/model/collections/setofembeddables/Image.java

This custom equality check in equals() compares all values of one Image to the values of another Image. If all values are the same, then the images must be the same. The hashCode() method has to be symmetric; if two instances are equal, they must have the same hash code.

Why didn’t you override equality before, when you mapped the Address of a User, in section 5.2? Well, the truth is, you probably should have done that. Our only excuse is that you won’t have any problems with the regular identity equality unless you put embeddable components into a Set or use them as keys in a Map. Then you should redefine equality based on values, not identity. It’s best if you override these methods on every @Embeddable class; all value types should be compared “by value.”

Now consider the database primary key: Hibernate will generate a schema that includes all non-nullable columns of the IMAGE collection table in a composite primary key. The columns have to be non-nullable because you can’t identify what you don’t know. This reflects the equality implementation in the Java class. You’ll see the schema in the next section, with more details about the primary key.

Note

We have to mention a minor issue with Hibernate’s schema generator: if you annotate an embeddable’s property with @NotNull instead of @Column(nullable=false), Hibernate won’t generate a NOT NULL constraint for the collection table’s column. A Bean Validation check of an instance works as expected, only the database schema is missing the integrity rule. Use @Column(nullable=false) if your embeddable class is mapped in a collection, and the property should be part of the primary key.

The component class is now ready, and you can use it in collection mappings.

7.2.2. Set of components

You map a Set of components as shown next.

Listing 7.12. Set of embeddable components with an override

Path: /model/src/main/java/org/jpwh/model/collections/setofembeddables/Item.java

As before, the @ElementCollection annotation is required. Hibernate automatically knows that the target of the collection is an @Embeddable type, from your declaration of a generic collection. The @CollectionTable annotation overrides the default name for the collection table, which would have been ITEM_IMAGES.

The Image mapping defines the columns of the collection table. Just as for a single embedded value, you can use @AttributeOverride to customize the mapping without modifying the target embeddable class. Look at the database schema in figure 7.7.

Figure 7.7. Example data tables for a collection of components

You’re mapping a set, so the primary key of the collection table is a composite of the foreign key column ITEM_ID and all “embedded” non-nullable columns: TITLE, FNAME, WIDTH, and HEIGHT.

The ITEM_ID value wasn’t included in the overridden equals() and hashCode() methods of Image, as discussed in the previous section. Therefore, if you mix images of different items in one set, you’ll run into equality problems in the Java tier. In the database table, you obviously can distinguish images of different items, because the item’s identifier is included in primary key equality checks.

If you want to include the Item in the equality routine of the Image, to be symmetric with the database primary key, you need an Image#item property. This is a simple back-pointer, provided by Hibernate when Image instances are loaded:

Path: /model/src/main/java/org/jpwh/model/collections/setofembeddables/Image.java

@Embeddable
public class Image {
<enter/>
    @org.hibernate.annotations.Parent
    protected Item item;
<enter/>
    // ...
}

You can now get the parent Item value in the equals() and hashCode() implementations and write, for example, a comparison with this.getItem().getId().equals (other.getItem().getId()). Be careful if the Item isn’t persistent and has no identifier value; we’ll explore this problem in more depth in section 10.3.2.

If you need load-time ordering of elements and a stable iteration order with a LinkedHashSet, use the JPA @OrderBy annotation:

Path: /model/src/main/java/org/jpwh/model/collections/setofembeddablesorderby/Item.java

@Entity
public class Item {
<enter/>
    @ElementCollection
    @CollectionTable(name = "IMAGE")
    @OrderBy("filename, width DESC")
    protected Set<Image> images = new LinkedHashSet<Image>();
<enter/>
    // ...
}

The arguments of the @OrderBy annotation are properties of the Image class, followed by either ASC for ascending or DESC for descending order. The default is ascending, so this example sorts ascending by image filename and then descending by the width of each image. Note that this is different from the proprietary @org.hibernate.annotations.OrderBy annotation, which takes a plain SQL clause, as discussed in section 7.1.8.

Declaring all properties of Image as @NotNull may not be something you want. If any of the properties are optional, you need a different primary key for the collection table.

7.2.3. Bag of components

You used the @org.hibernate.annotations.CollectionId annotation before to add a surrogate key column to the collection table. The collection type however, was not a Set but a Collection, a bag. This is consistent with the udpated schema: If you have a surrogate primary key column, duplicate “element values” are allowed. Let’s walk through this with an example.

First, the Image class may now have nullable properties:

Path: /model/src/main/java/org/jpwh/model/collections/bagofembeddables/Image.java

Remember to account for the optional title of the Image in your overridden equals() and hashCode() methods, when you compare instances “by value”.

Next, the mapping of the bag collection in Item:

Path: /model/src/main/java/org/jpwh/model/collections/bagofembeddables/Item.java

@Entity
public class Item {
<enter/>
    @ElementCollection
    @CollectionTable(name = "IMAGE")
    @org.hibernate.annotations.CollectionId(
            columns = @Column(name = "IMAGE_ID"),
            type = @org.hibernate.annotations.Type(type = "long"),
            generator = Constants.ID_GENERATOR)
    protected Collection<Image> images = new ArrayList<Image>();
<enter/>
    // ...
}
Hibernate Feature

As before, in section 7.1.5, you declare an additional surrogate primary key column IMAGE_ID with the proprietary @org.hibernate.annotations.CollectionId annotation. Figure 7.8 shows the database schema.

Figure 7.8. Collection of components table with a surrogate primary key column

The title of the Image with identifier 2 is null.

Next, we look at another way to change the primary key of the collection table with a Map.

7.2.4. Map of component values

If the Images are stored in a Map, the filename can be the map key:

Path: /model/src/main/java/org/jpwh/model/collections/mapofstringsembeddables/Item.java

The primary key of the collection table, as shown in figure 7.9, is now the foreign key column ITEM_ID and the key column of the map, FILENAME.

Figure 7.9. Database tables for a map of components

The embeddable Image class maps all other columns, which may be nullable:

Path: /model/src/main/java/org/jpwh/model/collections/mapofstringsembeddables/Image.java

In the previous example, the values in the map were instances of an embeddable component class and the keys of the map a basic string. Next, you use embeddable types for both key and value.

7.2.5. Components as map keys

Our final example is a mapping a Map, with both keys and values of embeddable type, as you can see in figure 7.10.

Figure 7.10. The Item has a Map keyed by Filename.

Instead of a string representation, you can represent a filename with a custom type, as shown next.

Listing 7.13. Representing a filename with a custom type

Path: /model/src/main/java/org/jpwh/model/collections/mapofembeddables/Filename.java

If you want to use this class for the keys of a map, the mapped database columns can’t be nullable, because they’re all part of a composite primary key. You also have to override the equals() and hashCode() methods, because the keys of a map are a set, and each Filename must be unique within a given key set.

You don’t need any special annotations to map the collection:

Path: /model/src/main/java/org/jpwh/model/collections/mapofembeddables/Item.java

@Entity
public class Item {
<enter/>
    @ElementCollection
    @CollectionTable(name = "IMAGE")
    protected Map<Filename, Image> images = new HashMap<Filename, Image>();
<enter/>
    // ...
}

In fact, you can’t apply @MapKeyColumn and @AttributeOverrides; they have no effect when the map’s key is an @Embeddable class. The composite primary key of the IMAGE table includes the ITEM_ID, NAME, and EXTENSION columns, as you can see in figure 7.11.

Figure 7.11. Database tables for a Map of Images keyed on Filenames

A composite embeddable class like Image isn’t limited to simple properties of basic type. You’ve already seen how you can nest other components, such as City in Address. You could extract and encapsulate the width and height properties of Image in a new Dimensions class.

An embeddable class can also own collections.

7.2.6. Collection in an embeddable component

Suppose that for each Address, you want to store a list of contacts. This is a simple Set<String> in the embeddable class:

Path: /model/src/main/java/org/jpwh/model/collections/embeddablesetofstrings/Address.java

The @ElementCollection is the only required annotation; the table and column names have default values. Look at the schema in figure 7.12: the USER_ID column has a foreign key constraint referencing the owning entity’s table, USERS. The primary key of the collection table is a composite of the USER_ID and NAME columns, preventing duplicate elements appropriate for a Set.

Figure 7.12. USER_ID has a foreign key constraint referencing USERS.

Instead of a Set, you could map a list, bag, or map of basic types. Hibernate also supports collections of embeddable types, so instead of a simple contact string, you could write an embeddable Contact class and have Address hold a collection of Contacts.

Although Hibernate gives you a lot of flexibility with component mappings and fine-grained models, be aware that code is read more often than written. Think about the next developer who has to maintain this in a few years.

Switching focus, we turn our attention to entity associations: in particular, simple many-to-one and one-to-many associations.

7.3. Mapping entity associations

At the beginning of this chapter, we promised to talk about parent/children relationships. So far, you’ve mapped an entity, Item. Let’s say this is the parent. It has a collection of children: the collection of Image instances. The term parent/child implies some kind of life cycle dependency, so a collection of strings or embeddable components is appropriate. The children are fully dependent on the parent; they will be saved, updated, and removed always with the parent, never alone. You already mapped a parent/child relationship! The parent was an entity, and the many children were of value type.

Now you want to map a relationship of a different kind: an association between two entity classes. Their instances don’t have a dependent life cycle. An instance can be saved, updated, and removed without affecting any other. Naturally, sometimes you have dependencies even between entity instances. You need more fine-grained control of how the relationship between two classes affects instance state, not completely dependent (embedded) types. Are we still talking about a parent/child relationship? It turns out that parent/child is vague, and everyone has their own definition. We’ll try not to use that term from now on and will instead rely on more-precise or at least well-defined vocabulary.

The relationship we’ll explore in the following sections is always the same, between the Item and Bid entity classes, as shown in figure 7.13. The association from Bid to Item is a many-to-one association. Later you’ll make this association bidirectional, so the inverse association from Item to Bid will be one-to-many.

Figure 7.13. Relationship between Item and Bid

The many-to-one association is the simplest, so we’ll talk about it first. The other associations, many-to-many and one-to-one, are more complex and we’ll discuss them in the next chapter.

Let’s start with the many-to-one association.

7.3.1. The simplest possible association

We call the mapping of the Bid#item property a unidirectional many-to-one association. Before we discuss this mapping, look at the database schema in figure 7.14.

Figure 7.14. A many-to-one relationship in the SQL schema

Listing 7.14. Bid has a single reference to an Item

Path: /model/src/main/java/org/jpwh/model/associations/onetomany/bidirectional/Bid.java

The @ManyToOne annotation marks a property as an entity association, and it’s required. Unfortunately, its fetch parameter defaults to EAGER: this means the associated Item is loaded whenever the Bid is loaded. We usually prefer lazy loading as a default strategy, and we’ll talk more about it later in section 12.1.1.

A many-to-one entity association maps naturally to a foreign key column: ITEM_ID in the BID table. In JPA, this is called the join column. You don’t need anything but the @ManyToOne annotation on the property. The default name for the join column is ITEM_ID: Hibernate automatically uses a combination of the target entity name and its identifier property, separated with an underscore.

You can override the foreign key column with the @JoinColumn annotation. We used it here for a different reason: to make the foreign key column NOT NULL when Hibernate generates the SQL schema. A bid always has to have a reference to an item; it can’t survive on its own. (Note that this already indicates some kind of life cycle dependency you have to keep in mind.) Alternatively, you could mark this association as non-optional with either @ManyToOne(optional = false) or, as usual, Bean Validation’s @NotNull.

This was easy. It’s critically important to realize that you can write a complete and complex application without using anything else.

You don’t need to map the other side of this relationship; you can ignore the one-to-many association from Item to Bid. There is only a foreign key column in the database schema, and you’ve already mapped it. We are serious about this: when you see a foreign key column and two entity classes involved, you should probably map it with @ManyToOne and nothing else. You can now get the Item of each Bid by calling someBid.getItem(). The JPA provider will dereference the foreign key and load the Item for you; it also takes care of managing the foreign key values. How do you get all of an item’s bids? Well, you write a query and execute it with EntityManager, in whatever query language Hibernate supports. For example, in JPQL, you’d use select b from Bid b where b.item = :itemParameter. One of the reasons you use a full ORM tool like Hibernate is, of course, that you don’t want to write and execute that query yourself.

7.3.2. Making it bidirectional

At the beginning of this chapter, we had a list of reasons a mapping of the collection Item#images was a good idea. Let’s do the same for the collection Item#bids. This collection would implement the one-to-many association between Item and Bid entity classes. If you create and map this collection property, you get the following:

  • Hibernate executes the SQL query SELECT * from BID where ITEM_ID = ? automatically when you call someItem.getBids() and start iterating through the collection elements.
  • You can cascade state changes from an Item to all referenced Bids in the collection. You can select what life cycle events should be transitive: for example, you could declare that all referenced Bid instances should be saved when the Item is saved, so you don’t have to call EntityManager#persist() repeatedly for all bids.

Well, that isn’t a very long list. The primary benefit of a one-to-many mapping is navigational access to data. It’s one of the core promises of ORM, enabling you to access data by calling only methods of your Java domain model. The ORM engine is supposed to take care of loading the required data in a smart way while you work with a high-level interface of your own design: someItem.getBids().iterator().next().get-Amount(), and so on.

The fact that you can optionally cascade some state changes to related instances is a nice bonus. Consider, though, that some dependencies indicate value types at the Java level, not entities. Ask yourself if any table in the schema will have a BID_ID foreign key column. If not, map the Bid class as @Embeddable, not @Entity, using the same tables as before but with a different mapping with fixed rules for transitive state changes. If any other table has a foreign key reference on any BID row, you need a shared Bid entity; it can’t be mapped embedded with an Item.

So, should you map the Item#bids collection at all? You get navigational data access, but the price you pay is additional Java code and significantly more complexity. This is frequently a difficult decision. How often will you call someItem.getBids() in your application and then access/display all bids in a predefined order? If you only want to display a subset of bids, or if you need to retrieve them in a different order every time, then you need to write and execute queries manually anyway. The one-to-many mapping and its collection would only be maintenance baggage. In our experience, this is a frequent source of problems and bugs, especially for ORM beginners.

In CaveatEmptor’s case, the answer is yes, you frequently call someItem.getBids() and then show a list to the user who wants to participate in an auction. Figure 7.15 shows the updated UML diagram with this bidirectional association.

Figure 7.15. Bidirectional association between Item and Bid

The mapping of the collection and the one-to-many side is as follows.

Listing 7.15. Item has a collection of Bid references

Path: /model/src/main/java/org/jpwh/model/associations/onetomany/bidirectional/Item.java

The @OneToMany annotation is required. In this case, you also have to set the mappedBy parameter. The argument is the name of the property on the “other side.”

Look again at the other side: the many-to-one mapping in listing 7.15. The property name in the Bid class is item. The bid side is responsible for the foreign key column, ITEM_ID, which you mapped with @ManyToOne. mappedBy tells Hibernate to “load this collection using the foreign key column already mapped by the given property”—in this case, Bid#item. The mappedBy parameter is always required when the one-to-many is bidirectional, when you already mapped the foreign key column. We’ll talk about that again in the next chapter.

The default for the fetch parameter of a collection mapping is always FetchType.LAZY. You won’t need this option in the future. It’s a good default setting; the opposite would be the rarely needed EAGER. You don’t want all the bids eagerly loaded every time you load an Item. They should be loaded when accessed, on demand.

The second reason for mapping the Item#bids collection is the ability to cascade state changes.

7.3.3. Cascading state

If an entity state change can be cascaded across an association to another entity, you need fewer lines of code to manage relationships. The following code creates a new Item and a new Bid and then links them:

You have to consider both sides of this relationship: the Bid constructor accepts an item, used to populate Bid#item. To maintain integrity of the instances in memory, you need to add the bid to Item#bids. Now the link is complete from the perspective of your Java code; all references are set. If you aren’t sure why you need this code, please see section 3.2.4.

Let’s save the item and its bids in the database, first without and then with transitive persistence.

Enabling transitive persistence

With the current mapping of @ManyToOne and @OneToMany, you need the following code to save a new Item and several Bid instances.

Listing 7.16. Managing independent entity instances separately

Path: /examples/src/test/java/org/jpwh/test/associations/OneToManyBidirectional.java

When you create several bids, calling persist() on each seems redundant. New instances are transient and have to be made persistent. The relationship between Bid and Item doesn’t influence their life cycle. If Bid were to be a value type, the state of a Bid would be automatically the same as the owning Item. In this case, however, Bid has its own completely independent state.

We said earlier that fine-grained control is sometimes necessary to express the dependencies between associated entity classes; this is such a case. The mechanism for this in JPA is the cascade option. For example, to save all bids when the item is saved, map the collection as shown next.

Listing 7.17. Cascading persistent state from Item to all bids

Path: /model/src/main/java/org/jpwh/model/associations/onetomany/cascadepersist/Item.java

@Entity
public class Item {
<enter/>
    @OneToMany(mappedBy = "item", cascade = CascadeType.PERSIST)
    protected Set<Bid> bids = new HashSet<>();
<enter/>
    // ...
}

Cascading options are per operation you’d like to be transitive, so you use CascadeType.PERSIST for the EntityManager#persist() operation. You can now simplify the code that links items and bids and then saves them.

Listing 7.18. All referenced bids are automatically made persistent

Path: /examples/src/test/java/org/jpwh/test/associations/OneToManyCascadePersist.java

At commit time, Hibernate examines the managed/persistent Item instance and looks into the bids collection. It then calls persist() internally on each of the referenced Bid instances, saving them as well. The value stored in the column BID#ITEM_ID is taken from each Bid by inspecting the Bid#item property. The foreign key column is “mapped by” with @ManyToOne on that property.

The @ManyToOne annotation also has the cascade option. You won’t use this often. For example, we can’t really say “when the bid is saved, also save the item”. The item has to exist beforehand; otherwise, the bid won’t be valid in the database. Think about another possible @ManyToOne: the Item#seller property. The User has to exist before they can sell an Item.

Transitive persistence is a simple concept, frequently useful with @OneToMany or @ManyToMany mappings. On the other hand, you have to apply transitive deletion carefully.

Cascading deletion

It seems reasonable that deletion of an item implies deletion of all the bids for the item, because they’re no longer relevant alone. This is what the composition (the filled-out diamond) in the UML diagram means. With the current cascading options, you have to write the following code to delete an item:

Path: /examples/src/test/java/org/jpwh/test/associations/OneToManyCascadePersist.java

First you remove the bids , and then you remove the owner: the Item . The deletion order is important. If you remove the Item first, you’ll get a foreign key–constraint violation, because SQL operations are queued in the order of your remove() calls. First the row(s) in the BID table have to be deleted, and then the row in the ITEM table.

JPA offers a cascading option to help with this. The persistence engine can remove an associated entity instance automatically.

Listing 7.19. Cascading removal from Item to all bids

Path: /model/src/main/java/org/jpwh/model/associations/onetomany/cascaderemove/Item.java

@Entity
public class Item {
<enter/>
    @OneToMany(mappedBy = "item",
               cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
    protected Set<Bid> bids = new HashSet<>();
<enter/>
    // ...
}

Just as before with PERSIST, Hibernate now cascades the remove() operation on this association. If you call EntityManager#remove() on an Item, Hibernate loads the bids collection elements and internally calls remove() on each instance:

Path: /examples/src/test/java/org/jpwh/test/associations/OneToManyCascadeRemove.java

The collection must be loaded because each Bid is an independent entity instance and has to go through the regular life cycle. If there is an @PreRemove callback method present on the Bid class, Hibernate has to execute it. You’ll see more on object states and callbacks in chapter 13.

This deletion process is inefficient: Hibernate must always load the collection and delete each Bid individually. A single SQL statement would have the same effect on the database: delete from BID where ITEM_ID = ?.

You know this because nobody in the database has a foreign key reference on the BID table. Hibernate doesn’t know this and can’t search the whole database for any row that might have a BID_ID.

If Item#bids was instead a collection of embeddable components, someItem.getBids().clear() would execute a single SQL DELETE. With a collection of value types, Hibernate assumes that nobody can possibly hold a reference to the bids, and removing only the reference from the collection makes it orphan removable data.

Enabling orphan removal

JPA offers a (questionable) flag that enables the same behavior for @OneToMany (and only @OneToMany) entity associations.

Listing 7.20. Enabling orphan removal on a @OneToMany collection

Path: /model/src/main/java/org/jpwh/model/associations/onetomany/orphanremoval/Item.java

The orphanRemoval=true argument tells Hibernate that you want to permanently remove a Bid when it’s removed from the collection. Here is an example of deleting a single Bid:

Path: /examples/src/test/java/org/jpwh/test/associations/OneToManyOrphanRemoval.java

Hibernate monitors the collection and on transaction commit will notice that you removed an element from the collection. Hibernate now considers the Bid to be orphaned. You guarantee that nobody else had a reference to it; the only reference was the one you just removed from the collection. Hibernate automatically executes an SQL DELETE to remove the Bid instance in the database.

You still won’t get the clear() one-shot DELETE as with a collection of components. Hibernate respects the regular entity-state transitions, and the bids are all loaded and removed individually.

Why is orphan removal questionable? Well, it’s fine in this example case. There is so far no other table in the database with a foreign key reference on BID. There are no consequences to deleting a row from the BID table; the only in-memory references to bids are in Item#bids. As long as all of this is true, there is no problem with enabling orphan removal. It’s a convenient option, for example, when your presentation layer can remove an element from a collection to delete something; you only work with domain model instances, and you don’t need to call a service to perform this operation.

Consider what happens when you create a User#bids collection mapping—another @OneToMany, as shown in figure 7.16. This is a good time to test your knowledge of Hibernate: what will the tables and schema look like after this change? (Answer: The BID table has a BIDDER_ID foreign key column, referencing USERS.)

Figure 7.16. Bidirectional associations between Item, Bid, and User

The test shown in the following listing won’t pass.

Listing 7.21. Hibernate doesn’t clean up in-memory references after database removal

Path: /examples/src/test/java/org/jpwh/test/associations/OneToManyOrphanRemoval.java

Hibernate thinks the removed Bid is orphaned and deletable; it will be deleted automatically in the database, but you still hold a reference to it in the other collection, User#bids. The database state is fine when this transaction commits; the deleted row of the BID table contained both foreign keys, ITEM_ID and BIDDER_ID. You have an inconsistency in memory, because saying, “Remove the entity instance when the reference is removed from the collection” naturally conflicts with shared references.

Instead of orphan removal, or even CascadeType.REMOVE, always consider a simpler mapping. Here, Item#bids would be fine as a collection of components, mapped with @ElementCollection. The Bid would be @Embeddable and have an @ManyToOne bidder property, referencing a User. (Embeddable components can own unidirectional associations to entities.)

This would provide the life cycle you’re looking for: a full dependency on the owning entity. You have to avoid shared references; the UML diagram (figure 7.16) makes the association from Bid to User unidirectional. Drop the User#bids collection; you don’t need this @OneToMany. If you need all the bids made by a user, write a query: select b from Bid b where b.bidder = :userParameter. (In the next chapter, you’ll complete this mapping with an @ManyToOne in an embeddable component.)

Enabling ON DELETE CASCADE on the foreign key
Hibernate Feature

All the removal operations we’ve shown are inefficient; bids have to be loaded into memory, and many SQL DELETEs are necessary. SQL databases support a more efficient foreign key feature: the ON DELETE option. In DDL, it looks like this: foreign key (ITEM_ID) references ITEM on delete cascade for the BID table.

This option tells the database to maintain referential integrity of composites transparently for all applications accessing the database. Whenever you delete a row in the ITEM table, the database will automatically delete any row in the BID table with the same ITEM_ID key value. You only need one DELETE statement to remove all dependent data recursively, and nothing has to be loaded into application (server) memory.

You should check whether your schema already has this option enabled on foreign keys. If you want this option added to the Hibernate-generated schema, use the Hibernate @OnDelete annotation.

Listing 7.22. Generating foreign key ON DELETE CASCADE in the schema

Path: /model/src/main/java/org/jpwh/model/associations/onetomany/ondeletecascade/Item.java

One of the Hibernate quirks is visible here: the @OnDelete annotation affects only schema generation by Hibernate. Settings that affect schema generation are usually on the “other” mappedBy side, where the foreign key/join column is mapped. The @OnDelete annotation is usually next to the @ManyToOne in Bid. When the association is mapped bidirectional, however, Hibernate will only recognize it on the @OneToMany side.

Enabling foreign key cascade deletion in the database doesn’t influence Hibernate’s runtime behavior. You can still run into the same problem as shown in listing 7.21. Data in memory may no longer accurately reflect the state in the database. If all related rows in the BID table are automatically removed when a row in the ITEM table is deleted, your application code is responsible for cleaning up references and catching up with database state. If you aren’t careful, you may even end up saving something that you or someone else previously deleted.

The Bid instances don’t go through the regular life cycle, and callbacks such as @PreRemove have no effect. Additionally, Hibernate doesn’t automatically clear the optional second-level global cache, which potentially contains stale data. Fundamentally, the kinds of problems you may encounter with database-level foreign key cascading are the same as when another application besides yours is accessing the same database or any other database trigger makes changes. Hibernate can be a very effective utility in such a scenario, but there are other moving parts to consider. We’ll talk more about concurrency and caching later in this book.

If you work on a new schema, the easiest approach is to not enable database-level cascading and map a composition relationship in your domain model as embedded/embeddable, not as an entity association. Hibernate can then execute efficient SQL DELETE operations to remove the entire composite. We made this recommendation in the previous section: if you can avoid shared references, map the Bid as an @ElementCollection in Item, not as a standalone entity with @ManyToOne and @OneToMany associations. Alternatively, of course, you might not map any collections at all and use only the simplest mapping: a foreign key column with @ManyToOne, unidirectional between @Entity classes.

7.4. Summary

  • Using simple collection mappings, such as a Set<String>, you worked through a rich set of interfaces and implementations.
  • You know how sorted collections work as well as Hibernate’s options for letting the database return the collection elements in the desired order.
  • We discussed complex collections of user-defined embeddable types and sets, bags, and maps of components.
  • You saw how to use components as both keys and values in maps, and a collection in an embeddable component.
  • Mapping the first foreign key column to an entity many-to-one association makes it bidirectional as a one-to-many. You also learned about several cascading options.
  • We covered key concepts of object/relational mapping. Once you’ve mapped your first @ManyToOne and maybe a simple collection of strings, the worst will be behind you.
  • Be sure you try the code (and watch the SQL log)!
..................Content has been hidden....................

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