Chapter 6. Collection Mapping

An object of value type has no database identity, and objects of value type are dependent on the owning entity object. The lifespan of the value type is bound by the lifespan of the owning entity. Entities that have a collection of value types need to be mapped to an appropriate collection interface from the Java API. For entities to have a collection of value types, you need to have a separate table with the entity's primary key as a foreign key.

Hibernate supports most of the collection interfaces provided by Java API. It's recommended that you use the collection interface to declare the type of the property and a matching implementation. Hibernate suggests using interfaces to declare collections because it supports Java collection, maps, and arrays; it has a <set> tag to support for all java.util.Set collections and <bag> or <idbag> to support java.util.Collection. You should initialize the implementation at the time of declaration to avoid uninitialized collections. The following code declares chapters to be type java.util.Set (which is an interface) and the matching implementation as java.util.HashSet:

Private Set<String> chapters = new HashSet<String>();

This chapter shows you how to map collections for value types. In Hibernate, collections can be lazily initialized, which means the state of the collection objects is loaded from the database only when the application needs it. This initialization is done transparently from the user. The chapter also deals with sorting collections in memory and at database level. You add a collection of chapters to your Book class as an example.

Mapping a Set

Problem

How do you map value types as collections of type java.util.Set?

Solution

The <set> mapping is the most common collection mapping that is used. You use the <set> element to map for java.util.Set. The property should be initialized to HashSet. The implementation doesn't allow duplicate elements, and the order of the elements isn't retained.

How It Works

The Book class has a property called chapters that is of type java.util.Set. The property is initialized as a HashSet:

public class Book6_1 implements Serializable{

  private Long book_id;
  private String isbn;
  private String name;
  private Date publishDate;
  private Integer price;
  private Set chapters = new HashSet();
  // getters and setters
}

Figure 6-1 shows the association between the BOOK table and the BOOK_CHAPTER table for this recipe.

Entity-relationship drawing between BOOK and BOOK_CHAPTER with a set association

Figure 6-1. Entity-relationship drawing between BOOK and BOOK_CHAPTER with a set association

In Book's Hibernate mapping file, the <set> element associates the chapters with the book. The <set> element contains the key column element that sets the book's ID (primary key as the foreign key). The association is mapped to a separate table, which has a composite key of the book's ID and the chapter name as the primary key.

<hibernate-mapping package="com.hibernaterecipes.chapter6">
  <class name="Book6_1" table="Book6_1" schema="BOOK6">
    <id name="book_id" type="long" column="BOOK_ID" >
      <generator class="native">
      </generator>
    </id>
    <property name="isbn" type="string">
    <column name="ISBN" length="50" not-null="true" unique="true" />
    </property>
    <property name="name" type="string">
    <column name="BOOK_NAME" length="100" not-null="true" />
    </property>
    <property name="publishDate" type="date" column="PUBLISH_DATE" />
    <property name="price" type="int" column="PRICE" />
    <set name="chapters" table="Book61_Chapter" schema="BOOK6">
      <key column="BOOK_ID"></key>
      <element type="string" column="CHAPTER_NAME" not-null="true"></element>
</set>
  </class>
</hibernate-mapping>

If you print the CREATE TABLE script from the database GUI tool (Squirrel, in this case), it looks something like this:

CREATE TABLE "BOOK6"."BOOK61_CHAPTER"
(
   BOOK_ID bigint NOT NULL,
   CHAPTER_NAME varchar(255) NOT NULL,
   CONSTRAINT SQL091222210546110 PRIMARY KEY (BOOK_ID,CHAPTER_NAME)
);
ALTER TABLE BOOK61_CHAPTER
ADD CONSTRAINT FK3C070E729C936CD9
FOREIGN KEY (BOOK_ID)
REFERENCES BOOK6_1(BOOK_ID);
CREATE UNIQUE INDEX SQL091222210546110 ON BOOK61_CHAPTER
(
  BOOK_ID,
  CHAPTER_NAME
);
CREATE INDEX SQL091222210548040 ON BOOK61_CHAPTER(BOOK_ID);

In JPA, the Book class has the following annotations:

@Entity
@Table (name="BOOK6_1", schema="BOOK6")
public class Book_6_1 implements Serializable{

  @Id
  @Column (name="BOOK_ID")
  @GeneratedValue (strategy=GenerationType.AUTO)
  private Long book_id;

  @Column (name="isbn")
  private String isbn;

  @Column (name="BOOK_NAME")
  private String name;

  @Column (name="price")
  private Integer price;

  @CollectionOfElements (targetElement=java.lang.String.class)
  @JoinTable(
      name="Book61_Chapter",
      schema="BOOK6",
      joinColumns=@JoinColumn(name="BOOK_ID")
  )
  @Column(name="chapter_name")
private Set chapters;
  // getters and setters
}

The CollectionOfElements annotation is used to declare what types of objects are being put in the collection that is being mapped. The JoinTable annotation provides details about the associated table and contains the join column that is used to map the two tables. The Column annotation provides the column name.

Mapping a Bag

Problem

A bag is a collection of objects that can have duplicates. It doesn't have any order. It's something like an ArrayList, but it doesn't retain its index. How do you map value types as collections of type bag? What is the difference between a bag and an idbag? How do you map using an idbag?

Solution

The Java Collections API doesn't have anything called a bag. Java developers who want to use bag-like semantics use the java.util.List implementation. Hibernate does the same by requiring the implementation to be an ArrayList. Hibernate recommends using java.util.Collection or java.util.List as the interface to map bags. The join table doesn't have a primary key when the mapping is done using the <bag> element. An idbag is similar to a bag, but it adds a primary key to the join table.

How It Works

The Book class has a property called chapters that is of type java.util.Collection. The property is initialized as an ArrayList:

public class Book6_2 implements Serializable{

  private Long book_id;
  private String isbn;
  private String name;
  private Date publishDate;
  private Integer price;
  private Collection chapters = new ArrayList();
  // getters and setters
}

In the book's Hibernate mapping file, the bag element is used to associate the chapters with the book. The association is mapped to a separate table that doesn't have a primary key. This is the impact of letting duplicate elements into the collection:

<hibernate-mapping package="com.hibernaterecipes.chapter6">
  <class name="Book6_2" table="Book6_2" schema="BOOK6">
    <id name="book_id" type="long" column="BOOK_ID" >
      <generator class="native">
      </generator>
    </id>
    <property name="isbn" type="string">
    <column name="ISBN" length="50" not-null="true" unique="true" />
    </property>
    <property name="name" type="string">
    <column name="BOOK_NAME" length="100" not-null="true" />
    </property>
    <property name="publishDate" type="date" column="PUBLISH_DATE" />
    <property name="price" type="int" column="PRICE" />
    <bag name="chapters" table="Book62_Chapter" schema="BOOK6">
      <key column="BOOK_ID"></key>
      <element type="string" column="CHAPTER_NAME" not-null="true"></element>
    </bag>

  </class>
</hibernate-mapping>

In JPA, the Book class has the following annotations:

@Entity
@Table (name="BOOK6_2", schema="BOOK6")
public class Book_6_2 implements Serializable{

  @Id
  @Column (name="BOOK_ID")
  @GeneratedValue (strategy=GenerationType.AUTO)
  private Long book_id;

  @Column (name="isbn")
  private String isbn;

  @Column (name="BOOK_NAME")
  private String name;

  @Column (name="price")
  private Integer price;

  @CollectionOfElements (targetElement=java.lang.String.class)
  @JoinTable(
      name="Book62_Chapter",
      schema="BOOK6",
      joinColumns=@JoinColumn(name="BOOK_ID")
  )
  @Column(name="chapter_name")
  private Collection chapters;
  // getters and setters
}

The CREATE TABLE query is shown here. Note that it doesn't have a primary key, and the book ID is a foreign key in the join table:

CREATE TABLE "BOOK6"."BOOK62_CHAPTER"
(
   BOOK_ID bigint NOT NULL,
   CHAPTER_NAME varchar(255) NOT NULL
);
ALTER TABLE BOOK62_CHAPTER
ADD CONSTRAINT FKD04B7D739C936CDA
FOREIGN KEY (BOOK_ID)
REFERENCES BOOK6_2(BOOK_ID);
CREATE INDEX SQL091227211955140 ON BOOK62_CHAPTER(BOOK_ID);

When you're mapping using the idbag, the Hibernate XML file has the collection-id element:

<hibernate-mapping package="com.hibernaterecipes.chapter6">
  <class name="Book6_21" table="Book6_21" schema="BOOK6">
    <id name="book_id" type="long" column="BOOK_ID" >
      <generator class="native">
      </generator>
    </id>
    <property name="isbn" type="string">
    <column name="ISBN" length="50" not-null="true" unique="true" />
    </property>
    <property name="name" type="string">
    <column name="BOOK_NAME" length="100" not-null="true" />
    </property>
    <property name="publishDate" type="date" column="PUBLISH_DATE" />
    <property name="price" type="int" column="PRICE" />
    <idbag name="chapters" table="Book621_Chapter" schema="BOOK6">
      <collection-id type="long" column="Book621_Chapter_id">
        <generator class="hilo"></generator>
      </collection-id>
      <key column="BOOK_ID"></key>
      <element type="string" column="CHAPTER_NAME" not-null="true"></element>
    </idbag>
  </class>
</hibernate-mapping>

Figure 6-2 shows the association between the BOOK table and the BOOK_CHAPTER table.

Entity-relationship drawing between BOOK and BOOK_CHAPTER with an idbag association

Figure 6-2. Entity-relationship drawing between BOOK and BOOK_CHAPTER with an idbag association

The JPA annotated Book class has the CollectionId and GenericGenerator annotations to define the primary key:

@Entity
@Table (name="BOOK6_21", schema="BOOK6")
public class Book_6_21 implements Serializable{

  @Id
  @Column (name="BOOK_ID")
  @GeneratedValue (strategy=GenerationType.AUTO)
  private Long book_id;

  @Column (name="isbn")
  private String isbn;

  @Column (name="BOOK_NAME")
  private String name;

  @Column (name="price")
  private Integer price;

  @CollectionOfElements (targetElement=java.lang.String.class)
  @JoinTable(
      name="Book621_Chapter",
      schema="BOOK6",
      joinColumns=@JoinColumn(name="BOOK_ID")
  )
  @GenericGenerator (name="hilo-gen",strategy="hilo")
  @CollectionId (columns=@Column(name = "Book621_Chapter_id"),
      type=@Type(type="long"),
      generator="hilo-gen")
  @Column(name="chapter_name")
  private Collection chapters = new ArrayList();
// getters and setters
}

The CREATE TABLE query looks something like this:

CREATE TABLE "BOOK6"."BOOK621_CHAPTER"
(
   BOOK_ID bigint NOT NULL,
   CHAPTER_NAME varchar(255) NOT NULL,
   BOOK621_CHAPTER_ID bigint PRIMARY KEY NOT NULL
);
ALTER TABLE BOOK621_CHAPTER
ADD CONSTRAINT FK574F883A8233ACD9
FOREIGN KEY (BOOK_ID)
REFERENCES BOOK6_21(BOOK_ID);
CREATE INDEX SQL091227214019670 ON BOOK621_CHAPTER(BOOK_ID);
CREATE UNIQUE INDEX SQL091227214017400 ON BOOK621_CHAPTER(BOOK621_CHAPTER_ID);

Mapping a List

Problem

A list is a list of objects. It's an ordered collection. The user of a list has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list) and search for elements in the list. How do you map value types as collections of type list?

Solution

You use the list element <list> to map the java.util.List property. The property has to be initialized as an ArrayList. The index of the list is retained, and an additional column is added to define the index.

How It Works

The Book Java class has a property called chapters that is of type java.util.List. The property is initialized as an ArrayList:

public class Book6_3 implements Serializable{

  private Long book_id;
  private String isbn;
  private String name;
  private Date publishDate;
  private Integer price;
  private List chapters;
  // getters and setters
}

The <list> element maps the chapters, and the index element defines the index of the list. The key element defines the book ID as the foreign key:

<hibernate-mapping package="com.hibernaterecipes.chapter6">
  <class name="Book6_3" table="Book6_3" schema="BOOK6">
    <id name="book_id" type="long" column="BOOK_ID" >
      <generator class="native">
      </generator>
    </id>
    <property name="isbn" type="string">
    <column name="ISBN" length="50" not-null="true" unique="true" />
    </property>
    <property name="name" type="string">
    <column name="BOOK_NAME" length="100" not-null="true" />
    </property>
    <property name="publishDate" type="date" column="PUBLISH_DATE" />
    <property name="price" type="int" column="PRICE" />
    <list name="chapters" table="Book63_Chapter" schema="BOOK6">
      <key column="BOOK_ID"></key>
      <index column="CHAPTER_INDEX"></index>
      <element type="string" column="CHAPTER_NAME" not-null="true"></element>
    </list>
  </class>
</hibernate-mapping>

In the JPA annotation mapped Book class, IndexColumn defines the index of the list. Note the annotation's Base attribute: it makes the index start from 1 or any number you wish. The default for this attribute is zero. All the other annotations are very similar to those for the set mapping:

@Entity
@Table (name="BOOK6_3", schema="BOOK6")
public class Book_6_3 implements Serializable{

  @Id
  @Column (name="BOOK_ID")
  @GeneratedValue (strategy=GenerationType.AUTO)
  private Long book_id;

  @Column (name="isbn")
  private String isbn;

  @Column (name="BOOK_NAME")
  private String name;

  @Column (name="price")
  private Integer price;

  @CollectionOfElements (targetElement=java.lang.String.class)
  @JoinTable(
      name="Book63_Chapter",
      schema="BOOK6",
      joinColumns=@JoinColumn(name="BOOK_ID")
  )
@IndexColumn(
      name="CHAPTER_INDEX",
      base=0)
  @Column(name="chapter_name")
  private List chapters;
  // getters and setters
}

The CREATE TABLE query is as follows:

CREATE TABLE "BOOK6"."BOOK63_CHAPTER"
(
   BOOK_ID bigint NOT NULL,
   CHAPTER_NAME varchar(255) NOT NULL,
   CHAPTER_INDEX int NOT NULL,
   CONSTRAINT SQL091227211951510 PRIMARY KEY (BOOK_ID,CHAPTER_INDEX)
);
ALTER TABLE BOOK63_CHAPTER
ADD CONSTRAINT   FK648FEC749C936CDB
FOREI    GN KEY (BOOK_ID)
REFERENCES BOOK6_3(BOOK_ID);
CREATE INDEX SQL091227214019790 ON BOOK63_CHAPTER(BOOK_ID);
CREATE UNIQUE INDEX SQL091227211951510 ON BOOK63_CHAPTER
(
  BOOK_ID,
  CHAPTER_INDEX
);

Mapping an Array

Problem

How do you map value types as Arrays?

Solution

An <array> has the same usage as a <list>. The only difference is that it corresponds to an array type in Java, not a java.util.List. It's seldom used unless you're mapping for legacy applications. In most cases, you should use <list> instead, because an array's size can't be increased or decreased dynamically, whereas a list's can.

How It Works

The Book Java class has a property called chapters that is of type String[]:

public class Book6_4 implements Serializable{

  private Long book_id;
  private String isbn;
  private String name;
  private Date publishDate;
  private Integer price;
  private String[] chapters;
        // getters and setters
}

You use the <array> element to map the property. All the other mapping elements are similar to those of a List mapping:

<hibernate-mapping package="com.hibernaterecipes.chapter6">
  <class name="Book6_4" table="Book6_4" schema="BOOK6">
    <id name="book_id" type="long" column="BOOK_ID" >
      <generator class="native">
      </generator>
    </id>
    <property name="isbn" type="string">
    <column name="ISBN" length="50" not-null="true" unique="true" />
    </property>
    <property name="name" type="string">
    <column name="BOOK_NAME" length="100" not-null="true" />
    </property>
    <property name="publishDate" type="date" column="PUBLISH_DATE" />
    <property name="price" type="int" column="PRICE" />
    <array name="chapters" table="Book64_Chapter" schema="BOOK6">
      <key column="BOOK_ID"></key>
      <index column="CHAPTER_INDEX"></index>
      <element type="string" column="CHAPTER_NAME" not-null="true"></element>
    </array>

  </class>
</hibernate-mapping>

The JPA annotations are exactly the same as the List mapping annotation. The type of chapters is changed from List to String[], and the create queries on the DB level are the same as for List:

@Entity
@Table (name="BOOK6_4", schema="BOOK6")
public class Book_6_4 implements Serializable{

  @Id
  @Column (name="BOOK_ID")
  @GeneratedValue (strategy=GenerationType.AUTO)
  private Long book_id;

  @Column (name="isbn")
private String isbn;

  @Column (name="BOOK_NAME")
  private String name;

  @Column (name="price")
  private Integer price;

  @CollectionOfElements (targetElement=java.lang.String.class)
  @JoinTable(
      name="Book64_Chapter",
      schema="BOOK6",
      joinColumns=@JoinColumn(name="BOOK_ID")
  )
  @IndexColumn(
      name="CHAPTER_INDEX",
      base=0)
  @Column(name="chapter_name")
  private String[] chapters;
  // getters and setters
}

Mapping a Map

Problem

How do you map a value type as a map?

Solution

A <map> is very similar to a <list>. The difference is that a map uses arbitrary keys to index the collection, rather than the integer index used in a list. A map stores its entries in key/value pairs. You can look up the value by its key, which can be of any data types. The Java type corresponding to a <map> is java.util.Map.

How It Works

The chapters property is declared to be type Map in the Book class:

public class Book6_5 implements Serializable{

  private Long book_id;
  private String isbn;
  private String name;
  private Date publishDate;
  private Integer price;
private Map chapters;
  // getters and settes
}

You use the <map> element to map the chapters. The <map-key> element defines the key for each chapter. All other elements are similar to those for the List implementation:

<hibernate-mapping package="com.hibernaterecipes.chapter6">
  <class name="Book6_5" table="Book6_5" schema="BOOK6">
    <id name="book_id" type="long" column="BOOK_ID" >
      <generator class="native">
      </generator>
    </id>
    <property name="isbn" type="string">
    <column name="ISBN" length="50" not-null="true" unique="true" />
    </property>
    <property name="name" type="string">
    <column name="BOOK_NAME" length="100" not-null="true" />
    </property>
    <property name="publishDate" type="date" column="PUBLISH_DATE" />
    <property name="price" type="int" column="PRICE" />
    <map name="chapters" table="Book65_Chapter" schema="BOOK6">
      <key column="BOOK_ID" />
      <map-key column="CHAPTER_KEY" type="string" />
      <element column="CHAPTER" type="string" length="100" />
    </map>

  </class>
</hibernate-mapping>

In the JPA, the MapKey annotation defines the key:

@Entity
@Table (name="BOOK6_5", schema="BOOK6")
public class Book_6_5 implements Serializable{
  @Id
  @Column (name="BOOK_ID")
  @GeneratedValue (strategy=GenerationType.AUTO)
  private Long book_id;

  @Column (name="isbn")
  private String isbn;

  @Column (name="BOOK_NAME")
  private String name;

  @Column (name="price")
  private Integer price;

  @CollectionOfElements (targetElement=java.lang.String.class)
  @JoinTable(name="Book65_Chapter",
schema="BOOK6",
      joinColumns=@JoinColumn(name="BOOK_ID")
  )
  @MapKey (columns=@Column(name="CHAPTER_KEY"),
      targetElement=java.lang.String.class)
  @Column(name="chapter")
  private Map chapters;
        // getters and setters
}

The CREATE TABLE query looks something like this:

CREATE TABLE "BOOK6"."BOOK65_CHAPTER"
(
   BOOK_ID bigint NOT NULL,
   CHAPTER varchar(100),
   CHAPTER_KEY varchar(255) NOT NULL,
   CONSTRAINT SQL091227211951730 PRIMARY KEY (BOOK_ID,CHAPTER_KEY)
);
ALTER TABLE BOOK65_CHAPTER
ADD CONSTRAINT FK8D18CA769C936CDD
FOREIGN KEY (BOOK_ID)
REFERENCES BOOK6_5(BOOK_ID);
CREATE INDEX SQL091227214020070 ON BOOK65_CHAPTER(BOOK_ID);
CREATE UNIQUE INDEX SQL091227211951730 ON BOOK65_CHAPTER
(
  BOOK_ID,
  CHAPTER_KEY
);

Sorting Collections

Problem

What are the possible ways to sort collections?

Solution

One way to sort a collection is to use the sorting features provided by the Java Collections Framework. The sorting occurs in the memory of the Java virtual machine (JVM) when you run Hibernate, after the data is read from the database. Note that for large collections, this kind of sorting may not be efficient. Only <set> and <map> supports this kind of sorting.

You can also sort a collection at the query level using the order-by clause. If your collection is very large, it's more efficient to sort it in the database. You can specify the order-by condition to sort this collection upon retrieval. Notice that the order-by attribute should be a SQL column, not a property name in Hibernate.

How It Works

We'll see a number of ways to sort a collection, starting with the natural order.

Using the Natural Order

To sort a collection in natural order, you define the sort attribute to be natural in the collection. Hibernate uses the compareTo() method defined in the java.lang.Comparable interface to compare the elements. Many basic data types, such as String, Integer, and Double implement this interface.

The implementation for chapters is changed from HashSet to TreeSet. In the case of a Map, the implementation class needs to be changed to TreeMap:

public class Book6_1 implements Serializable{

  private Long book_id;
  private String isbn;
  private String name;
  private Date publishDate;
  private Integer price;
  private Set chapters = new TreeSet();
  //getters and setters
}

In the Hibernate mapping file, the attribute sort is defined as natural:

<hibernate-mapping package="com.hibernaterecipes.chapter6">
  <class name="Book6_1" table="Book6_1" schema="BOOK6">
    <id name="book_id" type="long" column="BOOK_ID" >
      <generator class="native">
      </generator>
    </id>
    <property name="isbn" type="string">
    <column name="ISBN" length="50" not-null="true" unique="true" />
    </property>
    <property name="name" type="string">
    <column name="BOOK_NAME" length="100" not-null="true" />
    </property>
    <property name="publishDate" type="date" column="PUBLISH_DATE" />
    <property name="price" type="int" column="PRICE" />
    <set name="chapters" table="Book61_Chapter" schema="BOOK6" sort="natural" lazy="false">
      <key column="BOOK_ID"></key>
      <element type="string" column="CHAPTER_NAME" not-null="true"></element>
    </set>
  </class>
</hibernate-mapping>

In the JPA mapping file, you need to change the implementation to TreeSet and declare the annotation sort as natural. Also note that you disable lazy initialization. (Lazy initialization is discussed later in this chapter.)

@Entity
@Table (name="BOOK6_1", schema="BOOK6")
public class Book_6_1 implements Serializable{
  @Id
  @Column (name="BOOK_ID")
  @GeneratedValue (strategy=GenerationType.AUTO)
  private Long book_id;
  @Column (name="isbn")
  private String isbn;
  @Column (name="BOOK_NAME")
  private String name;
  @Column (name="price")
  private Integer price;
  @CollectionOfElements (targetElement=java.lang.String.class)
  @JoinTable(
      name="Book61_Chapter",
      schema="BOOK6",
      joinColumns=@JoinColumn(name="BOOK_ID")

  )
  @Column(name="chapter_name")
  @Sort (type=SortType.NATURAL)
  @LazyCollection (LazyCollectionOption.FALSE)
  private Set chapters = new TreeSet();
        //getters and setters
}

Writing Your Own Comparator

If you aren't satisfied with the natural ordering, you can write your own comparator instead by implementing the java.util.Comparator interface. The comparing logic should be put inside the overridden compare() method. To use this comparator, you pass it to the collection's sort attribute. The following is a sample implementation of the comparator:

public class ChapterComparator implements Comparator<String> {

  public int compare(String o1, String o2) {
    // if o1 and o2 don't instantiate the same class, throw an exception
    // if o1 is less than o2, return a negative number
    // if o1 is equal to o2, return a zero
    // if o1 is greater than o2, return a positive number
    if(o1.compareTo(o2)<1)
    {
      return 1;
    }else
    {
      return −1;
    }
  }
}

In the Hibernate XML mapping file, you declare the comparator implementation as the sorting mechanism:

<hibernate-mapping package="com.hibernaterecipes.chapter6">
  <class name="Book6_1" table="Book6_1" schema="BOOK6">
    <id name="book_id" type="long" column="BOOK_ID" >
      <generator class="native">
      </generator>
    </id>
    <property name="isbn" type="string">
    <column name="ISBN" length="50" not-null="true" unique="true" />
    </property>
    <property name="name" type="string">
    <column name="BOOK_NAME" length="100" not-null="true" />
    </property>
    <property name="publishDate" type="date" column="PUBLISH_DATE" />
    <property name="price" type="int" column="PRICE" />
    <set name="chapters" table="Book61_Chapter" schema="BOOK6"
      sort="com.hibernaterecipes.annotations.dao.ch6.ChapterComparator"
      lazy="false">
      <key column="BOOK_ID"></key>
      <element type="string" column="CHAPTER_NAME" not-null="true"></element>
    </set>
  </class>
</hibernate-mapping>

You also declare the comparator implementation to be the sorting mechanism in the JPA annotations:

@Entity
@Table (name="BOOK6_1", schema="BOOK6")
public class Book_6_1 implements Serializable{
  @Id
  @Column (name="BOOK_ID")
  @GeneratedValue (strategy=GenerationType.AUTO)
  private Long book_id;
  @Column (name="isbn")
  private String isbn;
  @Column (name="BOOK_NAME")
  private String name;
  @Column (name="price")
  private Integer price;
  @CollectionOfElements (targetElement=java.lang.String.class)
  @JoinTable(
      name="Book61_Chapter",
      schema="BOOK6",
      joinColumns=@JoinColumn(name="BOOK_ID")

  )
  @Column(name="chapter_name")
@Sort (type=SortType.COMPARATOR,
  comparator=com.hibernaterecipes.annotations.dao.ch6.ChapterComparator.class)
  @LazyCollection (LazyCollectionOption.FALSE)
  private Set chapters = new TreeSet();
        // getters and setters

}

Sorting in the Database

If your collection is very large, it's more efficient to sort it in the database. You can specify the order-by condition to sort the collection when it's retrieved. Note that the order-by attribute should be a SQL column, not a property name:

<hibernate-mapping package="com.hibernaterecipes.chapter6">
  <class name="Book6_1" table="Book6_1" schema="BOOK6">
    :
                :
    <set name="chapters" table="Book61_Chapter" schema="BOOK6"
      lazy="false" order-by=" CHAPTER_NAME">
      <key column="BOOK_ID"></key>
      <element type="string" column="CHAPTER_NAME" not-null="true"></element>
    </set>
  </class>
</hibernate-mapping>

You can use any SQL expression that is valid in the order by clause for this order-by attribute—for example, using commas to separate multiple columns, or using asc or desc to specify sorting in ascending or descending order. Hibernate copies the order-by attribute to the order by clause during SQL generation:

<hibernate-mapping package="com.hibernaterecipes.chapter6">
  <class name="Book6_1" table="Book6_1" schema="BOOK6">
    <id name="book_id" type="long" column="BOOK_ID" >
      <generator class="native">
      </generator>
    </id>
    <property name="isbn" type="string">
    <column name="ISBN" length="50" not-null="true" unique="true" />
    </property>
    <property name="name" type="string">
    <column name="BOOK_NAME" length="100" not-null="true" />
    </property>
    <property name="publishDate" type="date" column="PUBLISH_DATE" />
    <property name="price" type="int" column="PRICE" />
    <set name="chapters" table="Book61_Chapter" schema="BOOK6"
      lazy="false" order-by="CHAPTER_NAME desc">
      <key column="BOOK_ID"></key>
      <element type="string" column="CHAPTER_NAME" not-null="true"></element>
    </set>
  </class>
</hibernate-mapping>

In the JPA annotations, you use the OrderBy annotation to declare the column name:

@Entity
@Table (name="BOOK6_1", schema="BOOK6")
public class Book_6_1 implements Serializable{
  @Id
  @Column (name="BOOK_ID")
  @GeneratedValue (strategy=GenerationType.AUTO)
  private Long book_id;
  @Column (name="isbn")
  private String isbn;
  @Column (name="BOOK_NAME")
  private String name;
  @Column (name="price")
  private Integer price;
  @CollectionOfElements (targetElement=java.lang.String.class)
  @JoinTable(
      name="Book61_Chapter",
      schema="BOOK6",
      joinColumns=@JoinColumn(name="BOOK_ID")

  )
  @Column(name="chapter_name")
  @OrderBy (clause="chapter_name asc")
  @LazyCollection (LazyCollectionOption.FALSE)
  private Set chapters = new TreeSet();
//getters and setters
}

Using Lazy Initialization

Problem

How does lazy initialization work with collections? How do you avoid the LazyInitializationException?

Solution

An entity's mapped collection may not be needed every time the entity is loaded from the database into JVM memory. This mapped collection takes up unnecessary space in memory. To help with this, Hibernate provides the concept of lazy initialization in which associations and mapped collections aren't retrieved unless required by the user/application. The retrieval of the mapped collections is done transparently from the application. Hibernate by default has lazy initialization set to true, which saves a lot of memory usage.

Thus the collection of chapters isn't retrieved when a book is fetched. If the session is still open, you get the collection of chapters using Hibernate.initialize(book.getChapters()). And if the session isn't open, you have to load the collection when you load the owning entity; you configure this in the owning entity's mapping file by setting the property lazy to false.

How It Works

You can use the following code to retrieve a book object from the database. The collection of chapters is retrieved at the same time:

Session session = factory.openSession();
try {
  Book book = (Book) session.get(Book.class, id);
  return book;
} finally {
  session.close();
}

But when you access the chapter collection through book.getChapters() outside the session, an exception occurs:

for (Iterator iter = book.getChapters().iterator(); iter.hasNext();) {
  String chapter = (String) iter.next();
  System.out.println(chapter);
}

The reason for this exception is the lazy initialization of the collection. You can initialize it explicitly to access it outside the session:

Session session = factory.openSession();
try {
  Book book = (Book) session.get(Book.class, id);
  Hibernate.initialize(book.getChapters());
  return book;
} finally {
  session.close();
}

Or you can turn off lazy initialization for this collection. You must consider carefully before you do that, especially for a collection:

<hibernate-mapping package="com.hibernaterecipes.chapter6">
  <class name="Book6_1" table="Book6_1" schema="BOOK6">
    <id name="book_id" type="long" column="BOOK_ID" >
      <generator class="native">
      </generator>
    </id>
    <property name="isbn" type="string">
      <column name="ISBN" length="50" not-null="true" unique="true" />
    </property>
    <property name="name" type="string">
      <column name="BOOK_NAME" length="100" not-null="true" />
</property>
    <property name="publishDate" type="date" column="PUBLISH_DATE" />
    <property name="price" type="int" column="PRICE" />
    <set name="chapters" table="Book61_Chapter" schema="BOOK6"
         lazy="false" order-by="CHAPTER_NAME desc">
      <key column="BOOK_ID"></key>
      <element type="string" column="CHAPTER_NAME" not-null="true"></element>
    </set>
  </class>
</hibernate-mapping>

In JPA, it's simply as follows:

@Entity
@Table (name="BOOK6_1", schema="BOOK6")
public class Book_6_1 implements Serializable{
  @Id
  @Column (name="BOOK_ID")
  @GeneratedValue (strategy=GenerationType.AUTO)
  private Long book_id;

  @CollectionOfElements (targetElement=java.lang.String.class)
  @JoinTable(
      name="Book61_Chapter",
      schema="BOOK6",
      joinColumns=@JoinColumn(name="BOOK_ID")

  )
  @Column(name="chapter_name")
  @OrderBy (clause="chapter_name asc")
  @LazyCollection (LazyCollectionOption.FALSE)
  private Set chapters = new TreeSet();
// getters and setters
}

Summary

This chapter has shown how to map a collection of value type objects to an entity. You've seen the implementation of <set>, <bag>, <idbag>, <list>, and <map> elements and their corresponding JPA annotations. You've learned the advantage of each collection mapping element and when it's a good idea to use each of these elements. You've also seen how to sort a collection using a comparator and using the order-by clause. Finally, you've learned how and when lazy initialization should be turned off.

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

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