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.
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.
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.
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.)
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:
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.
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>();
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:
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.
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.
The simplest implementation is a Set of String image filenames. Add a collection property to the Item class, as shown in the following listing.
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.
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?
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.
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.
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.
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.
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.
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.
Again, make a small change to the Java class to use a Map property.
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.
The map in the previous example was unordered. What should you do to always sort map entries by filename?
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.
@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.
@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.
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.
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.
Finally, you can load ordered key/value pairs with a LinkedHashMap.
@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.
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.
The code in the next listing shows the new Image embeddable class, capturing all the properties of an image that interest you.
@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.
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.
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.
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.
You map a Set of components as shown next.
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.
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:
@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:
@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.
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:
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:
@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/> // ... }
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.
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.
If the Images are stored in a Map, the filename can be the map key:
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.
The embeddable Image class maps all other columns, which may be nullable:
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.
Our final example is a mapping a Map, with both keys and values of embeddable type, as you can see in figure 7.10.
Instead of a string representation, you can represent a filename with a custom type, as shown next.
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:
@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.
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.
Suppose that for each Address, you want to store a list of contacts. This is a simple Set<String> in the embeddable class:
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.
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.
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.
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.
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.
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.
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:
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.
The mapping of the collection and the one-to-many side is as follows.
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.
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.
With the current mapping of @ManyToOne and @OneToMany, you need the following code to save a new Item and several Bid instances.
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.
@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.
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.
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:
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.
@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:
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.
JPA offers a (questionable) flag that enables the same behavior for @OneToMany (and only @OneToMany) entity associations.
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:
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.)
The test shown in the following listing won’t pass.
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.)
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.
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.
3.143.17.27