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.
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.
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.
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 FK3C070E729C936CD9FOREIGN 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.
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
?
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 bag
s. 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.
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.
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);
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
?
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.
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 FK648FEC749C936CDBFOREI 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 );
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.
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 }
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
.
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 );
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.
We'll see a number of ways to sort a collection, starting with 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 }
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 }
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
}
How does lazy initialization work with collections? How do you avoid the LazyInitializationException
?
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
.
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
}
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.
18.221.249.198