Chapter 3. Component Mapping

Hibernate makes it easy to employ a fine-grained domain model. That means you can have more classes than tables. In other words, you can map a single record in a table to more than one class. You do so by having one class of type Entity and the others of Value types.

Hibernate classifies objects as either entity type or value type. An object of entity type is an independent entity and has its own lifecycle. It has its own primary key and hence its own database identity. A value type doesn't have an identifier. A value type belongs to an entity. Value type objects are bound by the lifecycle of the owning entity instance. When a value type is persisted, the value type's state is persisted in the owning entity's table row. Hibernate uses the component element, and JPA has the @Embeddable and @Embedded annotations to achieve the fine-grained model. This chapter goes through the implementation details.

Implementing a Value Type as a Component

Problem

How do you create a component? How do you create a fine-grained object model to map to a single row in a relational model?

Solution

A component element (<Component>) is used to map the value type object. You get the name component from the word Composition because the component is contained within an entity. In the case of JPA, embeddable and embedded annotations are used.

How It Works

We'll look at how Hibernate and JPA solves this problem in the sections below. Both solutions, however, need a new orders table. Use the following CREATE statement to create a new table called ORDERS:

CREATE TABLE ORDERS (id bigint NOT NULL, WEEKDAY_RECIPIENT varchar(100),WEEKDAY_PHONE varchar(100),WEEKDAY_ADDRESS varchar(100), HOLIDAY_RECIPIENT varchar(100),HOLIDAY_PHONE varchar(100),HOLIDAY_ADDRESS varchar(100),PRIMARY KEY (id));

Using Hibernate XML Mapping

In the online bookshop application, a customer can place an order to purchase some books. Your staff processes the order and delivers the books. The customer can specify different recipients and contact details for different periods (weekdays and holidays).

First, you add a new persistent class Orders to the application:

package com.hibernaterecipes.chapter3;

import com.hibernaterecipes.bookstore.Book;

/**
 * @author Guruzu
 *
 */
public class Orders {

  private Long id;
  private BookCh2 book;
  private String weekdayRecipient;
  private String weekdayPhone;
  private String weekdayAddress;
  private String holidayRecipient;
  private String holidayPhone;
  private String holidayAddress;

  //getters and setters
}

Then, you create a mapping definition for this persistent class. You map the properties of this class as usual:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.hibernaterecipes.chapter3">
  <class name="Orders" table="ORDERS">
    <id name="id" type="long" column="ID">
      <generator class="native" />
    </id>
    <property name="weekdayRecipient" type="string" column="WEEKDAY_RECIPIENT" />
    <property name="weekdayPhone" type="string" column="WEEKDAY_PHONE" />
    <property name="weekdayAddress" type="string" column="WEEKDAY_ADDRESS" />
    <property name="holidayRecipient" type="string" column="HOLIDAY_RECIPIENT" />
    <property name="holidayPhone" type="string" column="HOLIDAY_PHONE" />
    <property name="holidayAddress" type="string" column="HOLIDAY_ADDRESS" />
    <many-to-one name="book" class="com.hibernaterecipes.chapter2.BookCh2"
                 column="isbn" cascade="save-update"/>
  </class>
</hibernate-mapping>

You may feel that the Orders class isn't well designed because the recipient, phone, and address properties are duplicated for weekdays and holidays. From the object-oriented perspective, you should create a class (called, say, Contact) to encapsulate them:

package com.hibernaterecipes.chapter3;

public class Contact {
  private long id;
  private String recipient;
  private String phone;
  private String address;

  // getters and setters
}

package com.hibernaterecipes.chapter3;

import com.hibernaterecipes.bookstore.Book;

public class Orders {

  private Long id;
  private Book book;
                private Contact weekdayContact;
                private Contact holidayContact;

        // getters and setters
}

Now the changes are finished for Java. But how can you modify the Hibernate mapping definition to reflect the changes? According to the techniques you've learned, you can specify Contact as a new persistent class and use a one-to-one association (the simplest way is to use a <many-to-one> association with unique="true") to associate Orders and Contact:

<hibernate-mapping package=" com.hibernaterecipes.chapter3">
  <class name="Contact" table="CONTACT">
    <id name="id" type="long" column="ID">
      <generator class="native" />
    </id>
    <property name="recipient" type="string" column="RECIPIENT" />
    <property name="phone" type="string" column="PHONE" />
    <property name="address" type="string" column="ADDRESS" />
  </class>
</hibernate-mapping>

<hibernate-mapping package="com.hibernaterecipes.chapter3">
  <class name="Orders" table="ORDERS">
    ...
    <many-to-one name="weekdayContact" class="Contact" column="CONTACT_ID"
                 unique="true" />
    <many-to-one name="holidayContact" class="Contact" column="CONTACT_ID"
unique="true" />
  </class>
</hibernate-mapping>

In this case, modeling the Contact class as a standalone persistent class seems unnecessary. This is because a Contact object is meaningless when it's separated from an order object. The function of the Contact class is to provide some kind of logical grouping. The contact details are completely dependent on the Orders class. For a bookshop application, it doesn't make much sense to hold contact information as separate entities (entities that have a database identity or primary key). For this kind of requirement, where you can associate an object with a dependent object, you use what Hibernate calls components:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.hibernaterecipes.chapter3">
  <class name="Orders" table="ORDERS">
    <id name="id" type="long" column="ID">
      <generator class="native" />
    </id>
    <component name="weekdayContact" class="Contact">
      <property name="recipient" type="string" column="WEEKDAY_RECIPIENT" />
      <property name="phone" type="string" column="WEEKDAY_PHONE" />
      <property name="address" type="string" column="WEEKDAY_ADDRESS" />
    </component>
    <component name="holidayContact" class="Contact">
      <property name="recipient" type="string" column="HOLIDAY_RECIPIENT" />
      <property name="phone" type="string" column="HOLIDAY_PHONE" />
      <property name="address" type="string" column="HOLIDAY_ADDRESS" />
    </component>
  </class>
</hibernate-mapping>

No new persistent object is introduced. All the columns mapped for these components are in the same table as their parent object. Components don't have an identity, and they exist only if their parent does. They're most suitable for grouping several properties as a single object.

Using JPA Annotations

When you're using JPA annotations, for the Contact class, you need to annotate the class as Embeddable. You also map the columns to the regular default database columns:

package com.hibernaterecipes.annotations.domain;

import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;

@Embeddable
public class Contact {
private String recipient;
  private String phone;
  private String address;
  /**
   * @return the recipient
   */
  @Column (name = "WEEKDAY_RECIPIENT")
  public String getRecipient() {
    return recipient;
  }
  /**
   * @param recipient the recipient to set
   */
  public void setRecipient(String recipient) {
    this.recipient = recipient;
  }
  /**
   * @return the phone
   */
  @Column (name = "WEEKDAY_PHONE")
  public String getPhone() {
    return phone;
  }
  /**
   * @param phone the phone to set
   */
  public void setPhone(String phone) {
    this.phone = phone;
  }
  /**
   * @return the address
   */
  @Column (name = "WEEKDAY_ADDRESS")
  public String getAddress() {
    return address;
  }
  /**
   * @param address the address to set
   */
  public void setAddress(String address) {
    this.address = address;
  }
  /* (non-Javadoc)
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    return "Contact [address=" + address + ", phone=" + phone
        + ", recipient=" + recipient + "]";
}

}

For the Orders class, you annotate the weekday contact as embedded. For the holiday contact, you annotate the access as embedded and override the values provided in the Contact class as follows:

package com.hibernaterecipes.annotations.domain;

import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@org.hibernate.annotations.Entity(dynamicInsert = true, dynamicUpdate = true)
@Table (name="ORDERS")
public class Orders {

  private Long id;
  private Contact weekdayContact;
  private Contact holidayContact;
  /**
   * @return the id
   */
  @Id
  @GeneratedValue (strategy=GenerationType.AUTO)
  @Column (name="ID")
  public Long getId() {
    return id;
  }
  /**
   * @param id the id to set
   */
  public void setId(Long id) {
    this.id = id;
  }

  /**
   * @return the weekdayContact
   */
  @Embedded
  public Contact getWeekdayContact() {
    return weekdayContact;
  }
  /**
* @param weekdayContact the weekdayContact to set
   */
  public void setWeekdayContact(Contact weekdayContact) {
    this.weekdayContact = weekdayContact;
  }
  /**
   * @return the holidayContact
   */
  @Embedded
  @AttributeOverrides({@AttributeOverride(name="recipient",
                column=@Column(name="HOLIDAY_RECIPIENT")),
  @AttributeOverride(name="phone",
                column=@Column(name="HOLIDAY_PHONE")),
  @AttributeOverride(name="address",
                column=@Column(name="HOLIDAY_ADDRESS"))})

  public Contact getHolidayContact() {
    return holidayContact;
  }
  /**
   * @param holidayContact the holidayContact to set
   */
  public void setHolidayContact(Contact holidayContact) {
    this.holidayContact = holidayContact;
  }
  /* (non-Javadoc)
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    return "Orders [holidayContact=" + holidayContact + ", id=" + id
        + ", weekdayContact=" + weekdayContact + "]";
  }


}

Nesting Components

Problem

How do you nest a component within a component?

Solution

Components can be defined to be nested—that is, embedded within other components. As per the JPA specification, support for only one level of embedding is required.

How It Works

You can define the Phone property as a component and embed it in the contact component:

package com.hibernaterecipes.chapter3;

public class Phone {

  private String areaCode;
  private String telNo;

  // getters and setters
}

Change the phone from type String to type Phone.

package com.hibernaterecipes.chapter3;

public class Contact {
  private String recipient;
  private Phone phone;
  private String address;

  // getters and setters
}

Also create a new XML file names Orders.xml, and add the nested component as show here:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.hibernaterecipes.chapter3">
  <class name="Orders" table="BOOK_ORDERS">
    <id name="id" type="long" column="ID">
      <generator class="native" />
    </id>
    <component name="weekdayContact" class="Contact">
      <property name="recipient" type="string" column="WEEKDAY_RECIPIENT" />
      <component name="phone" class="Phone">
        <property name="areaCode" type="string" column="WEEKDAY_AREACODE" />
        <property name="telNo" type="string" column="WEEKDAY_TELEPHONE" />
      </component>
      <property name="address" type="string" column="WEEKDAY_ADDRESS" />
    </component>
    <component name="holidayContact" class="Contact">
      <property name="recipient" type="string" column="HOLIDAY_RECIPIENT" />
      <component name="phone" class="Phone">
        <property name="areaCode" type="string" column="HOLIDAY_AREACODE" />
        <property name="telNo" type="string" column="HOLIDAY_TELEPHONE" />
      </component>
<property name="address" type="string" column="HOLIDAY_ADDRESS" />
    </component>
  </class>
</hibernate-mapping>

And now, add this new XML to the Hibernate XML mapping file. You can use the nested components as shown here in the main method:

package com.hibernaterecipes.chapter3;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.stat.Statistics;

public class Launch {
  private static SessionFactory sessionFactory;

  public static Session getSession() {
    if(sessionFactory == null)
    {
      sessionFactory = new Configuration().configure()
      .buildSessionFactory();
    }
    Session hibernateSession = sessionFactory.openSession();
    return hibernateSession;
  }
  public static void main(String[] args) {
    Session session = getSession();
    Statistics stats = sessionFactory.getStatistics();
    stats.setStatisticsEnabled(true);
    Transaction tx = session.beginTransaction();
    Orders ord = new Orders();
    Phone wdPhn = new Phone();
    Phone hlPhn = new Phone();
    wdPhn.setAreaCode("480");
    wdPhn.setTelNo("5463152");
    hlPhn.setAreaCode("702");
    hlPhn.setTelNo("5643569");
    Contact cnt = new Contact();
    Contact weekDayCnt = new Contact();
    cnt.setAddress("132,vacation street, Miami, Fl - 23232");
    cnt.setPhone(wdPhn);
    cnt.setRecipient("John Doe 1");
    weekDayCnt.setRecipient("John Doe");
    weekDayCnt.setAddress("512364, Permanent home, Scottsdale,  AZ - 85254");
    weekDayCnt.setPhone(hlPhn);
    ord.setWeekdayContact(weekDayCnt);
    ord.setHolidayContact(cnt);
    session.save(ord);
    tx.commit();
stats.getSessionOpenCount();
    stats.logSummary();
    session.close();

  }

}

Adding References in Components

Problem

How do you add a reference to a component's parent object? How do you provide associations within a component?

Solution

You can add a reference to the parent object by using the <parent> tag. The component tag allows for many-to-one and one-to-one associations with other tables.

How It Works

A component can have a reference to its parent object through a <parent> mapping:

public class Contact {
  private Orders order;
  private String recipient;
  private Phone phone;
  private String address;
  // Getters and Setters
}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.hibernaterecipes.chapter3">
  <class name="Orders" table="ORDERS">
    <id name="id" type="long" column="ID">
      <generator class="native" />
    </id>
    <component name="weekdayContact" class="Contact">
      <parent name="order" />
      <property name="recipient" type="string" column="WEEKDAY_RECIPIENT" />
      <property name="phone" type="string" column="WEEKDAY_PHONE" />
      <property name="address" type="string" column="WEEKDAY_ADDRESS" />
    </component>
<component name="holidayContact" class="Contact">
      <parent name="order" />
      <property name="recipient" type="string" column="HOLIDAY_RECIPIENT" />
      <property name="phone" type="string" column="HOLIDAY_PHONE" />
      <property name="address" type="string" column="HOLIDAY_ADDRESS" />
    </component>
  </class>
</hibernate-mapping>

In JPA, you add the reference to the parent entity and annotate the accessor method with @Parent:

package com.hibernaterecipes.annotations.domain;

import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;

import org.hibernate.annotations.Parent;

@Embeddable
public class Contact {

  private String recipient;
  private String phone;
  private String address;
  private Orders order;

  @Parent
  public Orders getOrder() {
    return order;
  }

  // other getters and setters
}

A component can be used to group not only normal properties, but also many-to-one and one-to-one associations. Suppose you want to associate the address of an order to the address in your customer database. To do this, create an address table using the following query:

CREATE TABLE ADDRESS (id bigint NOT NULL,STREET_ADDRESS_1 varchar(100),STREET_ADDRESS_2 varchar(100),CITY varchar(100),STATE varchar(2),ZIP_CODE INT,PRIMARY KEY (id))

Now, create the entity class and the Hibernate mapping XML file:

package com.hibernaterecipes.chapter3;

public class Address {
  private Long id;
  private String address1;
  private String address2;
  private String city;
private String state;
  private Integer zipCode;

  // getters and setters
}


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.hibernaterecipes.chapter3">
  <class name="Address" table="ADDRESS">
    <id name="id" type="long" column="ID">
      <generator class="native" />
    </id>
    <property name="address1" type="string" column="STREET_ADDRESS_1" />
    <property name="address2" type="string" column="STREET_ADDRESS_2" />
    <property name="city" type="string" column="CITY" />
    <property name="state" type="string" column="STATE" />
    <property name="zipCode" type="integer" column="ZIP_CODE" />
  </class>
</hibernate-mapping>

And now, edit the Contact class as follows:

package com.hibernaterecipes.chapter3;

public class Contact {
  private String recipient;
  private Phone phone;
  private Address address;

        // getters and setters
}

Change the Orders XML mapping file to include the association:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.hibernaterecipes.chapter3">
  <class name="Orders" table="ORDERS">
    <id name="id" type="long" column="ID">
      <generator class="native" />
    </id>
    <component name="weekdayContact" class="Contact">
      <property name="recipient" type="string" column="WEEKDAY_RECIPIENT" />
      <component name="phone" class="Phone">
        <property name="areaCode" type="string" column="WEEKDAY_AREACODE" />
<property name="telNo" type="string" column="WEEKDAY_TELEPHONE" />
      </component>
      <many-to-one name="address" class="Address" column="WEEKDAY_ADDRESS_ID" />
    </component>
    <component name="holidayContact" class="Contact">
      <property name="recipient" type="string" column="HOLIDAY_RECIPIENT" />
      <component name="phone" class="Phone">
        <property name="areaCode" type="string" column="HOLIDAY_AREACODE" />
        <property name="telNo" type="string" column="HOLIDAY_TELEPHONE" />
      </component>
      <many-to-one name="address" class="Address" column="HOLIDAY_ADDRESS_ID" />
    </component>
  </class>
</hibernate-mapping>

Mapping a Collection of Components

Problem

Does Hibernate support mapping a collection of dependent objects? How do you map a collection of components?

Solution

Hibernate provides <composite-element> for mapping a collection of components. The collection elements/tags <set>, <list>, <map>, <bag>, and <idbag> can accommodate the <composite-element> to map a collection of dependent objects.

How It Works

Suppose you need to support a more flexible contact mechanism for book orders. A customer can specify several contact points for a book delivery, because they may not sure which one is most suitable for a specified time period. Your staff tries these contact points one by one when they deliver books. You can use a java.util.Set to hold all the contact points for an order:

public class Orders {
private Contact weekdayContact;
private Contact holidayContact;
private Set contacts;
// Getters and Setters
}

You need to create a separate table as follows:

CREATE TABLE ORDERS_CONTACT (ORDER_ID bigint NOT NULL, RECIPIENT varchar(100),AREACODE varchar(100),TELEPHONE varchar(100),ADDRESS varchar(100))

To map many contact points for an order in Hibernate, you can use a collection of components. You use <composite-element> to define the components in a collection. For simplicity, you first roll back your Contact class to its original form:

public class Contact {
  private String recipient;
  private String phone;
  private String address;
  // Getters and Setters
  // equals and hashCode implementation
}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.hibernaterecipes.chapter3">
  <class name="Orders" table="ORDERS">
    <id name="id" type="long" column="ID">
      <generator class="native" />
    </id>
    <set name="contacts" table="ORDERS_CONTACT">
      <key column="ORDER_ID" />
      <composite-element class="Contact">
        <property name="recipient" type="string" column="RECIPIENT" />
        <property name="phone" type="string" column="TELEPHONE" />
        <property name="address" type="string" column="ADDRESS" />
      </composite-element>
    </set>
  </class>
</hibernate-mapping>

In this implementation, you use the <set> tag. Because java.util.Set doesn't allow for duplicate elements, you need to implement the equals() and hashCode() methods. The database's primary key shouldn't be the only property used for comparison in the implementation of equals() and hashCode(). If the database primary key is the only property used in the implementation of equals() and hashCode(), then users can't add multiple contacts to the set, because the primary key will be null before it's persisted. It's recommended that you use other properties as well to obtain uniqueness among objects. Hence, proper implementation of equals() and hashCode() is required to ensure that duplicate elements aren't added. Note that <composite-element> doesn't allow null values when the enclosing collection tag is a <set> because for delete operations, Hibernate needs to identify each composite element to be able to delete that particular element.

In additional to normal properties, you can define nested components and associations in a collection. You use <nested-composite-element> to define nested components in a collection. Let's perform the necessary changes to the Contact class first:

public class Contact {
  private String recipient;
  private Phone phone;
  private Address address;
// Getters and Setters
}
<hibernate-mapping package=" com.hibernaterecipes.chapter3">
  <class name="Orders" table="ORDERS">
    <set name="contacts" table="ORDERS_CONTACT">
      <key column="ORDER_ID" />
      <composite-element class="Contact">
        <property name="recipient" type="string" column="RECIPIENT" />
        <property name="phone" type="string" column="PHONE" />
        <nested-composite-element name="phone" class="Phone">
          <property name="areaCode" type="string" column="PHONE_AREA_CODE" />
          <property name="telNo" type="string" column="PHONE_TEL_NO" />
        </nested-composite-element>
        <property name="address" type="string" column="ADDRESS" />
        <many-to-one name="address" class="Address" column="ADDRESS_ID" />
      </composite-element>
    </set>
  </class>
</hibernate-mapping>

In JPA, change the Orders class as shown here:

package com.hibernaterecipes.annotations.domain;

import java.io.Serializable;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.annotations.CollectionOfElements;
import org.hibernate.annotations.Target;

@Entity
@org.hibernate.annotations.Entity(dynamicInsert = true, dynamicUpdate = true)
@Table (name="ORDERS")
public class Orders implements Serializable{

  private Long id;
  private Set<Contact> contacts;

  /**
   * @return the id
   */
@Id
  @GeneratedValue (strategy=GenerationType.AUTO)
  @Column (name="ID")
  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }
  /**
   * @return the contacts
   */
  @CollectionOfElements (targetElement=Contact.class,fetch=FetchType.EAGER)
  @JoinTable (name = "ORDERS_CONTACT" ,
      joinColumns = @JoinColumn(name="ORDER_ID"))
  public Set<Contact> getContacts() {
    return contacts;
  }

  public void setContacts(Set<Contact> contacts) {
    this.contacts = contacts;
  }
  /* (non-Javadoc)
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    return "Orders [contacts=" + contacts + ", id=" + id + "]";
  }
}

The Contact table must be provided with the new table name. Also note that you still don't have a primary key for the new table ORDERS_CONTACT:

package com.hibernaterecipes.annotations.domain;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Table;
import org.hibernate.annotations.Parent;

@Embeddable
@Table (name="ORDERS_CONTACT")
public class Contact implements Serializable{

  private String recipient;
  private String phone;
  private String address;
  private Orders order;
/**
   * @return the recipient
   */
  @Column (name = "RECIPIENT")
  public String getRecipient() {
    return recipient;
  }
  /**
   * @param recipient the recipient to set
   */
  public void setRecipient(String recipient) {
    this.recipient = recipient;
  }
  /**
   * @return the phone
   */
  @Column (name = "TELEPHONE")
  public String getPhone() {
    return phone;
  }
  /**
   * @param phone the phone to set
   */
  public void setPhone(String phone) {
    this.phone = phone;
  }
  /**
   * @return the address
   */
  @Column (name = "ADDRESS")
  public String getAddress() {
    return address;
  }
  /**
   * @param address the address to set
   */
  public void setAddress(String address) {
    this.address = address;
  }

  /**
   * @return the order
   */
  @Parent
  public Orders getOrder() {
    return order;
  }
  /**
   * @param order the order to set
   */
  public void setOrder(Orders order) {
    this.order = order;
}
  /* (non-Javadoc)
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    return "Contact [address=" + address + ", phone=" + phone
        + ", recipient=" + recipient + "]";
  }
}

Using Components as Keys to a Map

Problem

Normally, the key to a map can be a String, a Long, an Integer, and so on. But what if you need to implement your own map key? Does Hibernate allow you to map a component class as a key to a map? If so, how do you map components as keys to a map?

Solution

Hibernate provides <composite-map-key>, which lets you use components as map keys.

How It Works

Suppose you want to extend your collection of contact points to be of java.util.Map type. You can use date periods as the keys of the map. You create a class Period to encapsulate the start date and end date of a period, and you use that as your map's key type. You then define this period as a component in the Orders class. The customer can now specify the most suitable contact point for a particular date period:

public class Period {
  private Date startDate;
  private Date endDate;
  // Getters and Setters
}
<hibernate-mapping package="com.metaarchit.bookshop">
  <class name="Orders" table="ORDERS">
...
    <map name="contacts" table="ORDERS_CONTACT">
      <key column="ORDER_ID" />
      <composite-map-key class="Period">
        <key-property name="startDate" type="date" column="START_DATE" />
        <key-property name="endDate" type="date" column="END_DATE" />
      </composite-map-key>
      <composite-element class="Contact">
        <property name="recipient" type="string" column="RECIPIENT" />
<nested-composite-element name="phone" class="Phone">
          <property name="areaCode" type="string" column="PHONE_AREA_CODE" />
          <property name="telNo" type="string" column="PHONE_TEL_NO" />
        </nested-composite-element>
        <many-to-one name="address" class="Address" column="ADDRESS_ID" />
      </composite-element>
    </map>
  </class>
</hibernate-mapping>

For the map-key component to work properly, you need to override the equals() and hashCode() methods of the component class:

public class Period {
  ...
  public boolean equals(Object obj) {
    if (!(obj instanceof Period)) return false;
    Period other = (Period) obj;
    return new EqualsBuilder().append(startDate, other.startDate)
      .append(endDate, other.endDate)
      .isEquals();
  }
  public int hashCode() {
    return new HashCodeBuilder().append(startDate)
      .append(endDate)
      .toHashCode();
  }
}

Summary

In this chapter, you've learned a way to achieve a finer-grained object model by using value types as components. You've effectively created an object graph for use in an application, but it's represented in the database as a single record in a table. You've also seen a way to bridge the gap between relational model and object model by using simple value types. You've learned how to nest components within other components and how to add the parent reference in a component. And you've seen how to map a collection of components in an entity and use components as key to a java.util.Map.

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

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