Hibernate Annotation Examples: Collection of Elements

Collection of Elements

Consider the following example: we have a page in our web application that allows a user to select a preferred language. The page shows a drop-down that lists the various language. Initially, our class looked like this:

This was pretty simple; one class with a language code (like “en_CA”) and a description (in one language). It was pretty obvious how to persist this class.

Here’s what the Java code looked like:

@Entity
@Table(name="language")
public class Language {
  private String isoCode;
  private String description;

  @Id
  @Column(name="code", length=5)
  public String getIsoCode() {
    return this.isoCode;
  }
  public void setIsoCode(String isoCode) {
    this.isoCode = isoCode;
  }

  public String getDescription() {
    return this.description;
  }
  public void setDescription(String description) {
    this.description = description;
  }
}

What we wanted, instead, was something like this:

public String getDescription(String forLanguage) {
   ...
}

We needed to have some kind of map of different descriptions for our language (i.e. we need to describe each language in different local languages). Revising our Java code, we get something like this:

private Map<String, String> getMap() {
  return this.map;
}
private void setMap(Map<String, String> map) {
  this.map = map;
}
public String getDefaultDescription() {
  return getMap().get(Locale.ENGLISH.toString());
}
public String getDescription(String language) {
  String description = getMap().get(language);
  if (StringUtils.isBlank(description)) {
    Locale locale = new Locale(language);
    description = getMap().get(locale.getLanguage());
  }
  return StringUtils.isBlank(description)
    ? getDefaultDescription()
    : description;
}

We know from past experience that Hibernate has no problems reading from and writing to private getters/setters. Since we’re modeling classes that are kinda immutable, we decided to hide these accessors from the Java code. Tragically, this approach introduces a compiler warning, but you can use a SuppressWarning annotation to… well… we’ll leave this part as an exercise for the reader.

Now the trick: how to annotate this?

It was easy to imagine what the final tables should look like:

What wasn’t as simple was imagining the set of annotations to accomplish this. We weren’t interested in having another class to hold the language descriptions ? we wanted simple String elements in our Map. That led us to the “CollectionOfElements” pattern in Hibernate. We tried this:

@CollectionOfElements
private Map<String, String> getMap() {
  return this.map;
}
private void setMap(Map<String, String> map) {
  this.map = map;
}

We used the Hibernate Schema generator to create tables, and it was pretty close to what we wanted. What we got was this:

There were two key things to do here:

  1. assign a table name of our choosing; and
  2. assign column names that we favoured.

(It turned out we also needed to mark the getDefaultNamemethod as Transient, so that Hibernate didn’t try to consider it a persistable property).

First, we added a JoinTable annotation to change the name that Hibernate wanted to generate.

@CollectionOfElements
@JoinTable(name="language_description")
private Map<String, String> getMap() {
  return this.map;
}

Next, we needed to deal with each of the three columns. Each column plays a very different role in the final result, and the name for each column was configured very differently.

First, the key to the parent (LANGUAGE) table was set in the JoinColumn annotation (but the annotation was put in a funny spot):

@CollectionOfElements
@JoinTable(name="language_description",
  joinColumns = @JoinColumn(name="language_code"))
private Map<String, String> getMap() {
  return this.map;
}

Next, we wanted to rename the column that acts as the key to the map. Hibernate’s favourite name for this column is “MAPKEY”, but that’s just stupid. We can change this using the MapKey annotation. Careful, however, because there are two different implementations of this annotation. We used the Hibernate implementation:

@CollectionOfElements
@JoinTable(name="language_description",
  joinColumns = @JoinColumn(name="language_code"))
@MapKey(columns={@Column(name="for_language",length=5)})
private Map<String, String> getMap() {
  return this.map;
}

Notice that we also set the length of this field, since we know that it’ll never be more than 5 characters.

Finally, we wanted to change the name of the String that’s kept as the value of the map. Hibernate’s favourite name for this column is “ELEMENT”, but again with the stupid.

@CollectionOfElements
@JoinTable(name="language_description",
  joinColumns = @JoinColumn(name="language_code"))
@MapKey(columns={@Column(name="for_language",length=5)})
@Column(name="description")
private Map<String, String> getMap() {
  return this.map;
}

After we added these annotations, we ended up with the table structure that we were looking for.

It's only fair to share...
Share on FacebookGoogle+Tweet about this on TwitterShare on LinkedIn

One thought on “Hibernate Annotation Examples: Collection of Elements

  1. Pingback: Hibernate Annotation Examples | i-Proving –

Leave a Reply