Chapter 4. Persisting Collections

Working with data structures, formally known as Java collections, is inevitable for any Java developer. In this chapter, we will learn about Hibernate’s support for persisting and retrieving collections. We will look in detail at how we can persist the standard Java collections using the Hibernate framework.

As a Java developer, at some point in your programming life, you have probably worked with Java collections. Java collections are the most common data structures in Java, with various algorithm features.

We will, at some point, encounter persistent objects with collections as their values. Persisting simple values from Hibernate’s view is different from persisting collection elements such as java.util.List or java.util.Map structures. We need to follow certain mapping procedures and processes to let Hibernate know our intentions.

The familiar Java collections—such as List, Set, Array, and Map—are all supported by Hibernate. In fact, it goes one step further and creates a couple of other collections, such as bag and idbag. Let’s look at them one by one.

Designing to Interfaces

Java provides collection interfaces such as java.util.Collection (which is the parent to all the collection interfaces except the map interface), java.util.List (the interface for list data structures), java.util.Set (for set data structures), java.util.Map (for key/value map data structures), and more.

The List implementation is intended to hold ordered lists of elements. We use concrete implementations, such as ArrayList or LinkedList, but the point here is that we must design to the List interface in our Hibernate application rather than to its concrete implementation.

Note

Always use interfaces when you are defining your collection variables. Hibernate does not like it when we use concrete classes as the variable types:

ArrayList<String> actors = new ArrayList<String>();

Instead, we should define the types using interfaces, like so:

List<String> actors = new ArrayList<String>();

The reason is that behind the scenes, Hibernate uses its own implementation of List!

Next we’ll explore the mechanics of persisting one of the most widely used collection structures—lists!

Persisting Lists

Lists are simple and easy data structures to hold items in an orderly manner. They can also keep track of an element’s position, using indexes. We can insert an element anywhere in the list, and retrieve the element using its index.

For example, let’s say we have persisted a list of manufacturers (Toyota, BMW, Mercedes, etc.) via our Java program. We should expect to retrieve the car data from the table in the same order in which it was inserted. So, if we run a query list.get(n), we should get back the nth element without fail.

To satisfy this requirement, Hibernate maintains another table with the cars’ indexes. So, when it fetches the cars from the main table, it also fetches the indexed order of these items from the additional table (called, say, CAR_LIST). It will then associate and map these items together to find out the order, and accordingly feed the client with the ordered responses.

But enough theory. How can we persist our cars list using Hibernate? To get a better understanding, let’s see an example.

List Example: Car Showroom

Consider a simple case of a car showroom. A showroom consists of dozens of cars for customers to view and possibly purchase. We can represent this with a simple model.

Every showroom will have a variety of cars to sell. Some cars may be brand new, while others are secondhand. We model these cars as java.util.List, as shown here in Showroom’s implementation (along with the Car definition):

// Showroom class
public class Showroom {
  private int id = 0;
  private String manager = null;
  private String location = null;
  private List<Car> cars = null;
  ...
  // Setters and getters
}

// Car class
public class Car {
  private int id;
  private String name = null;
  private String color = null;
  ...
  // Setters and getters
}

The Showroom and Car classes are very simple POJOs. The only notable point is the declaration of a one-to-many association of Showroom with cars—that is, one showroom consists of many cars.

Once the POJOs are ready, we add the mapping definitions:

<hibernate-mapping package="com.madhusudhan.jh.collections.list">

  <!-- Showroom class mapping definition -->

  <class name="Showroom" table="SHOWROOM_LIST">
    <id column="SHOWROOM_ID" name="id">
      <generator class="native"/>
    </id>
    ...
    <list name="cars" cascade="all" table="CARS_LIST">
      <key column="SHOWROOM_ID"/>
      <index column="CAR_INDEX"/>
      <one-to-many class="Car"/>
    </list>
  </class>

  <!-- Car class mapping definition -->

  <class name="Car" table="CARS_LIST">
    <id column="CAR_ID" name="id">
      <generator class="native"/>
    </id>
    <property column="NAME" name="name"/>
    <property column="COLOR" name="color"/>
  </class>
</hibernate-mapping>

Notice the use of the list element in the preceding snippet. This element defines the mapping of the cars declared in the Showroom object to the table.

The main table, SHOWROOM_LIST, will be created and populated as expected, so there are no surprises there. However, CARS_LIST is an additional table that gets created in the process. In addition to the CAR_ID, NAME, and COLOR properties present in the CARS_LIST table, which are directly declared on the object itself, Hibernate creates two other columns. One of them is a foreign key, SHOWROOM_ID, while the other is the CAR_INDEX column to hold the list indexes. CAR_INDEX is populated with each list element’s position, and is used later to reconstruct the list elements in their original positions.

When retrieving the cars, at runtime, Hibernate reorders the records according to the index held in CAR_INDEX. Let’s run the test client to check out how this works in practice.

Test Client for List Persistence

Fire up a small test client to test the list persistence functionality, as shown here:

private void persistLists() {
  // Create showroom object
  Showroom showroom = new Showroom();
  showroom.setLocation("East Croydon, Greater London");
  showroom.setManager("Barry Larry");

  // Create list of cars
  List<Car> cars = new ArrayList<Car>();
  cars.add(new Car("Toyota", "Racing Green"));
  cars.add(new Car("Toyota", "Racing Green"));
  cars.add(new Car("Nissan", "White"));
  cars.add(new Car("BMW", "Black"));
  cars.add(new Car("Mercedes", "Silver"));

  ...
  // Associate cars to the showroom
  showroom.setCars(cars);

  // Save the showroom
  session.save(showroom);
}

The test client is self-explanatory. Notice that I’m adding an extra Toyota to the list! When you run the test to retrieve the results, the following output is printed to the console (check out the duplicate Toyota cars too in the output!):

Showroom{id=6, manager=Barry Larry, location=East Croydon, Greater London, cars=[
Car{id=15, name=Toyota, color=Racing Green},
Car{id=16, name=Toyota, color=Racing Green},
Car{id=17, name=Nissan, color=White},
Car{id=18, name=BMW, color=Black},
Car{id=19, name=Mercedes, color=Silver}]}

As you can see, Hibernate honors the insertion order of the cars in the list.

Persisting Sets

java.util.Set represents an unordered data structure where duplicates are not allowed. Using sets is straightforward just like lists. We’ll revisit the showroom cars example from the previous example to demonstrate how sets are used with Hibernate.

In our modified example, the collection of cars that belong to a showroom is modeled as java.util.Set; thus we define the cars variable as the Set type. We use HashSet as our concrete implementation of the Set interface.

The Showroom class is shown here:

public class Showroom {
  private int id = 0;
  private String manager = null;
  private String location = null;

  // Cars are represented as set
  private Set<Car> cars = null;

  // Getters and setters
  ...

The notable change is the use of the Set collection instead of List. Once you finish modifying the Showroom class, the mapping of Set is done via the set tag, as demonstrated in the following snippet:

<hibernate-mapping package="com.madhusudhan.jh.collections.set">
  <!- The showroom class. Note the mapping of cars -->
  <class name="Showroom" table="SHOWROOM_SET">
    <id column="SHOWROOM_ID" name="id">
      <generator class="native"/>
    </id>
    ...
    <set name="cars" table="CARS_SET" cascade="all">
      <key column="SHOWROOM_ID"/>
      <one-to-many class="Car"/>
    </set>
  </class>

  <!-- The car mapping definition - very simple -->
  <class name="Car" table="CARS_SET">
    <id column="CAR_ID" name="id">
      <generator class="native"/>
    </id>
    <property column="NAME" name="name"/>
    <property column="COLOR" name="color"/>
  </class>
</hibernate-mapping>

A Showroom instance is mapped to SHOWROOM_SET table, whereas the cars variable representing the set collection is mapped to the CARS_SET table, as expected. The key element represents the presence of a foreign key in the CARS_SET table. Hibernate adds this foreign key to the CARS_SET table automatically. Hence, the CARS_SET table, which is created and managed by Hibernate, will have the additional foreign key SHOWROOM_ID, thus associating the two tables.

Create a test client as shown here:

private void persistSets() {
  // Create and populate showroom
  Showroom showroom = new Showroom();
  showroom.setLocation("East Croydon, Greater London");
  showroom.setManager("Barry Larry");

  // Create and populate cars set
  Set<Car> cars = new HashSet<Car>();

  cars.add(new Car("Toyota", "Racing Green"));
  cars.add(new Car("Nissan", "White"));
  cars.add(new Car("BMW", "Black"));
  cars.add(new Car("BMW", "Black"));

  // Associate cars to the showroom and persist it
  showroom.setCars(cars);
  session.save(showroom);
  ...
 }

In the preceding example, we created a Showroom object to which we’ve added three new cars. We are using HashSet as our concrete implementation for our cars collection. Did you notice that we are trying to add another BMW to the set? The set would identify these two cars as identical based on equality matching and hence would throw away the duplicate one.

When working with sets, we need to satisfy an equality requirement: we must create equals and hashCode methods in the Car object. As we know, each individual item that’s being added to the set must be unique. The equals and hashCode methods would help to satisfy this requirement. Make sure the equals and hashCode contracts are fulfilled correctly—for example, use the fields that will identify a car uniquely.

The retrieveSets test method would fetch the persisted set from the database, as shown in this listing:

Showroom{id=7, manager=Barry Larry, location=East Croydon, Greater London, cars=[
Car{id=27, name=Nissan, color=White},
Car{id=26, name=Mercedes, color=Silver},
Car{id=28, name=Toyota, color=Racing Green},
Car{id=29, name=BMW, color=Black}]}
...

Did you notice that the BMW isn’t listed twice, although we added another to the set earlier? This demonstrates the set’s “exclusion of duplicates” policy.

Persisting Maps

When you have a requirement to represent name/value pairs, your first choice should be Maps. The Map data structures are like dictionaries where you have a key (word) and related values (meanings). Maps are the de facto choice for key/value-paired data, such as bank accounts (value) of a single customer (key) or stock quotes for an issuer.

Continuing with our car showroom example, next we’ll add the capability for the showroom to hold potential customers’ reservations for test driving cars. We can best implement this functionality by employing a Map data structure, linking customers to car reservations:

public class Showroom {
  private int id = 0;
  private String manager = null;
  private String location = null;
  private Map<String, Car> cars = null;

  // getters & setters
  ...

Each car is reserved for a customer, and all the cars belong to the showroom. We can implement the customer-to-cars data type as a Map<String, Car> type.

The main meat is in the mapping, which is defined here:

<hibernate-mapping package="com.madhusudhan.jh.collections.map">
  <!-- Showroom mapping definition with cars variable
  mapped to CARS_MAP table using map tag -->

  <class name="Showroom" table="SHOWROOM_MAP">
    <id column="SHOWROOM_ID" name="id">
      <generator class="native"/>
    </id>
    <property column="MANAGER" name="manager"/>
    ...
    <map name="cars" cascade="all" table="CARS_MAP">
      <key column="SHOWROOM_ID"/>
      <map-key column="CUST_NAME" type="string" />
      <one-to-many class="Car"/>
    </map>
  </class>

  <!-- Simple Car class-table mapping -->
  <class name="Car" table="CARS_MAP">
    <id column="CAR_ID" name="id">
      <generator class="native"/>
    </id>
    <property name="name" column="CAR_NAME" />
    <property name="color" column="COLOR" />
  </class>
</hibernate-mapping>

As expected, the showroom’s cars variable is represented by a map element referring to a table, CARS_MAP, in the mapping definition. The map element defines a foreign key (SHOWROOM_ID, in this case). The map-key attribute defines the key of the map—the customer, in our case. The car class mapping is a simple and straightforward one. Note that Hibernate would add a couple of more columns to the CARS_MAP table—SHOWROOM_ID and CUST_NAME—in addition to the name and color columns.

Once the mapping is done, we need to demonstrate its workings using a test client as follows:

private void persistMaps() {
  Showroom showroom = new Showroom();
  showroom.setLocation("East Croydon, Greater London");
  showroom.setManager("Cherry Flurry");

  Map<String, Car> cars = new HashMap<String, Car>();
  cars.put("barry", new Car("Toyota", "Racing Green"));
  cars.put("larry", new Car("Nissan", "White"));
  cars.put("harry", new Car("BMW", "Black"));

  showroom.setCars(cars);
  ...
}

Here we create a map with a customer name and the cars to test drive. We then attach them to the showroom. As you can see in our Map data structure, we have a brand new car corresponding to a customer.

As expected, the following output would be printed to the console if we run the retrieveMaps method on the client:

Showroom{id=1, manager=Cherry Flurry, location=East Croydon, Greater London,
cars={barry=Car{id=1, name=Toyota, color=Racing Green},
harry=Car{id=2, name=BMW, color=Black},
larry=Car{id=3, name=Nissan, color=White},
fairy=Car{id=4, name=Mercedes, color=Pink}}}
...

The output shows all the cars in the showroom are reserved to customers for a test drive.

Persisting Arrays

Persisting arrays is similar to persisting lists, so we will only breeze through the mechanics and will not delve into too much detail here. The Showroom class now has a variable, cars, of the String array type, as listed here:

public class Showroom {
  private int id = 0;
  private String manager = null;
  private String location = null;
  // List of cars
  private String[] cars = null;
  ...

The mapping of the classes is defined here:

<hibernate-mapping package="com.madhusudhan.jh.collections.array">
  <class name="Showroom" table="SHOWROOM_ARRAY">
    <id column="SHOWROOM_ID" name="id">
      <generator class="native"/>
    </id>
    ...
    <array name="cars" cascade="all" table="CARS_ARRAY">
      <key column="SHOWROOM_ID"/>
      <index column="CAR_INDEX"/>
      <element column="CAR_NAME" type="string" not-null="true"/>
    </array>
  </class>
</hibernate-mapping>

The array tag defines the mapping between the cars variable and the CARS_ARRAY table. As expected, the CARS_ARRAY table will have a foreign key (SHOWROOM_ID, in this case). Hibernate also preserves the insertion order; hence, index tag must be defined with a name. element defines the actual values composing the array—in this case, the model name of each car.

The persistArrays method on our test class (shown next) would persist the relevant arrays in the database. We create the String[] of cars, passing in the model names, as shown in the following snippet:

private void persistArrays() {
  ...
  Showroom showroom = new Showroom();
  showroom.setLocation("East Croydon, Greater London");
  showroom.setManager("Barry Larry");

  // Create array of cars and
  // associate with the showroom
  String[] cars = new String[]{"Toyota","BMW","Citroen"};
  showroom.setCars(cars);

  // Normal saving of the showroom
  session.save(showroom);
  ...
}

The retrieveArrays method on the test client would fetch the cars as expected:

Showroom{id=9, manager=Barry Larry, location=East Croydon, Greater London,
cars=[Toyota, BMW, Citroen]}

Persisting Bags and IdBags

If we wish to have an unordered collection and no indexing of the elements, Java doesn’t have any data structure that supports that. The closest is java.util.List, but obviously it maintains both order and indexing. To satisfy this requirement, Hibernate created a special type of collection called bags.

Bags are the opposite of lists: they are unordered and nonindexed collections that allow duplicate elements. Bags are unique to Hibernate, and there is no equivalent collection in the Java space.

Implementing bags is very simple; we don’t notice any difference to our entities. In fact, we could still be using List to represent the bag in the Java code (remember, there is no bags collection in Java). The actual difference appears in the mapping side. Instead of declaring the collection as a list, we use bag.

See the following mapping definition, with no changes to entities:

<hibernate-mapping package="com.madhusudhan.jh.collections.bags">
  <class name="Showroom" table="SHOWROOM_BAGS">
    <id column="SHOWROOM_ID" name="id">
      <generator class="native"/>
    </id>
    ...
        <bag name="cars" cascade="all" table="CARS_LIST">
      <key column="SHOWROOM_ID"/>
      <one-to-many class="Car"/>
    </bag>
  </class>
  <class name="Car" table="CARS_BAGS">
    ...
  </class>
</hibernate-mapping>

In the bag element, did you notice we dropped the index element that must exist in the list definition? In bags, the index of the collection is not persisted anymore; hence, you won’t see the index element defined in the mapping.

Apart from this difference, the rest of the mechanics for running the tests is exactly the same as with lists. The test class defined here shows how we populate the data before persisting the entity:

private void persist() {
  ...
  Showroom showroom = new Showroom();
  showroom.setLocation("East Croydon, Greater London");
  showroom.setManager("Barry Larry");

  // Define our cars - note their type
  String[] cars = new String[]{"Toyota","BMW","Citroen"};

  // Attach them to the showroom and persist
  showroom.setCars(cars);
  session.save(showroom);
  ...
}

Warning

Bags are not a standard collection; they are Hibernate-specific. Although your code still uses java.util.List for a bag, the mapping needs to be explicit. It’s better to stay away from bags if possible, and choose standard collections wherever you can.

In addition to bags, Hibernate supports idbags, a collection that provides a mechanism to have a surrogate key on the persisted collection itself, unlike bags where no key exists. As usual, the POJOs will not be changed, but the mapping deserves special attention:

<hibernate-mapping package="com.madhusudhan.jh.collections.idbags">
  <class name="Showroom" table="SHOWROOM_IDBAGS">
    <id column="SHOWROOM_ID" name="id">
      <generator class="native"/>
    </id>
    ...
        <idbag name="cars" cascade="all" table="SHOWROOM_CARS_IDBAGS">
      <collection-id  column="SHOWROOM_CAR_ID" type="long">
        <generator class="hilo"/>
      </collection-id>
      <key column="SHOWROOM_ID"/>
      <many-to-many class="Car" column="CAR_ID"/>
    </idbag>
  </class>
  <class name="Car" table="CARS_IDBAGS">
    <id column="CAR_ID" name="id">
      <generator class="native"/>
    </id>
    ...
  </class>
</hibernate-mapping>

Here we introduce the idbags element to represent our cars collection, pointing to a join table, SHOWROOM_CARS_IDBAGS. The collection-id element creates a primary key on the join table. In addition to its own primary key, the join table will also carry primary keys from the other two tables.

Warning

The idbags collection is rarely used, so I would suggest you revisit your requirements should you wish to use it.

Persisting Collections Using Annotations

In the previous sections, we have seen the inner workings of saving collections using the XML mapping route. As an alternative, we can follow the annotations path for persisting the collections. The first thing we need to do is decorate the entities with the appropriate annotations. We’ll enhance the car showroom example in this section.

There are two methods of preparing our code for annotations: using a foreign key or using an intermediary join table. We’ll cover both of them next.

Using a Foreign Key

As we know, each showroom will have many cars, as represented by a one-to-many association. The Showroom entity consists of the collection of cars, showcasing them to customers. The cars, on the other hand, belong to a showcase; hence, are modeled to have a foreign key relationship to the showroom.

Let’s first see the Showroom entity, which is defined as follows:

@Entity(name="SHOWROOM_LIST_ANN")
@Table(name="SHOWROM_LIST_ANN")
public class Showroom {
  @Id
  @Column(name="SHOWROOM_ID")
  @GeneratedValue(strategy=GenerationType.AUTO)
  private int id = 0;

  @OneToMany
  @JoinColumn(name="SHOWROOM_ID")
  @Cascade(CascadeType.ALL)
  private List<Car> cars = null;

  // other properties
  private String manager = null;
  private String location = null;

The class is declared as a persistable entity (via the @Entity annotation) mapping to a table identified with the @Table annotation. We define the identifier using an autogeneration strategy, meaning the identifier is set by one of the database’s functions, such as auto_increment or identity.

Let’s focus on one important property of the showroom: the collection of cars represented by a variable called cars. We use a java.util.List collection to hold the cars data. This variable is decorated with the @OneToMany annotation because each showroom will have many cars, and each car belongs to a showroom.

We learned earlier that the cars collection will have its own table with a foreign key referring to the showroom table’s primary key (SHOWROOM_ID, in this case).

To let Hibernate know about this dependency, we declare the cars variable along with an @JoinColumn annotation defining the foreign key. We must provide the column name SHOWROOM_ID to pick up the list of cars from the cars table. The @Cascade annotation enables Hibernate to persist the collections associated with the main instance.

The Car entity is simple and straightforward:

@Entity(name="CAR_LIST_ANN")
@Table(name="CAR_LIST_ANN")
public class Car {
  @Id
  @GeneratedValue(strategy= GenerationType.AUTO)
  @Column(name="CAR_ID")
  private int id;
  private String name = null;
  private String color = null;
  ...
}

Here are the showroom and car database table scripts:

-- Table for showroom.
CREATE TABLE showroom_list_ann (
  SHOWROOM_ID int(10) NOT NULL AUTO_INCREMENT,
  location varchar(255) DEFAULT NULL,
  manager varchar(255) DEFAULT NULL,
  PRIMARY KEY (SHOWROOM_ID)
)
-- Table for cars
CREATE TABLE car_list_ann (
  CAR_ID int(11) NOT NULL AUTO_INCREMENT,
  color varchar(255) DEFAULT NULL,
  name varchar(255) DEFAULT NULL,
  SHOWROOM_ID int(11) DEFAULT NULL,
  PRIMARY KEY (CAR_ID),
  FOREIGN KEY (SHOWROOM_ID) REFERENCES showroom_list_ann (SHOWROOM_ID)
)

The cars table has its own primary key (CAR_ID) plus a foreign key (SHOWROOM_ID) referring to the main table.

Prepare your test case, adding the annotated classes to the configuration, as shown here:

  Configuration config = new Configuration()
   .configure("collections/list/ann/hibernate.cfg.xml")
   .addAnnotatedClass(Showroom.class)
   .addAnnotatedClass(Car.class);

As the client’s persist mechanism won’t be any different from what we saw when persisting lists, we will not repeat the test case here.

The output of the showroom is as follows:

Showroom{id=1, manager=Barry Larry, location=East Croydon, Greater London,
cars=[Car{id=1, name=Toyota, color=Racing Green},
Car{id=2, name=Nissan, color=White},
Car{id=3, name=BMW, color=Black},
Car{id=4, name=Mercedes, color=Silver}]}

Now that we have seen how to persist collections using a foreign key, let’s explore the second method: using a join table.

Using a Join Table

When using a join table strategy, we must have a mapping (join) table, which is an intermediary table holding the primary keys from both tables. For example, the following join table consists of primary keys from both showroom and cars:

// Join Table for showrooms and cars
SHOWROOM_ID             CAR_ID
1                               1
1                               2
2                               1
2                               2

Make the following alterations to the Showroom entity, annotating the cars table as appropriate:

@Entity(name="SHOWROOM_SET_ANN_JOINTABLE")
@Table(name="SHOWROOM_SET_ANN_JOINTABLE")
public class Showroom {
  @Id
  @Column(name="SHOWROOM_ID")
  @GeneratedValue(strategy=GenerationType.AUTO)
  private int id = 0;
  private String manager = null;
  private String location = null;

  @OneToMany
  @JoinTable
  (name="SHOWROOM_CAR_SET_ANN_JOINTABLE",
   joinColumns = @JoinColumn(name="SHOWROOM_ID")
   )
  @Cascade(CascadeType.ALL)
  private Set<Car> cars = null;
 ...
}

The @JoinTable annotation in the preceding snippet indicates that we will be using an intermediary table (SHOWROOM_CAR_SET_ANN_JOINTABLE, in this case). Also, note that cars are fetched using the SHOWROOM_ID join column.

The join table that is created is as follows:

CREATE TABLE showroom_car_set_ann_jointable (
  SHOWROOM_ID int(11) NOT NULL,
  car_id int(11) NOT NULL,
  PRIMARY KEY (SHOWROOM_ID,car_id),
  FOREIGN KEY (SHOWROOM_ID) REFERENCES showroom_set_ann_jointable (SHOWROOM_ID),
  FOREIGN KEY (car_id) REFERENCES car_set_ann_jointable (id)
)

As expected, the primary key is the combination of showroom_id and car_id. Also, notice that we have defined the foreign keys from the individual tables for showroom and car.

Summary

In this chapter, we have run through the various parts of persisting collections using the Hibernate framework. We have walked through the entities and their mappings in detail. We worked through examples of lists, sets, array lists, bags, and maps. We wrapped up by exploring how to persist the collections using annotations.

Get Just Hibernate now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.