Chapter 4. Inheritance and Custom Mapping

Inheritance, polymorphic associations, and polymorphic queries are supported by entities. The JPA specification says that both concrete and abstract classes can be entities and can be mapped with the Entity annotation. Non-entity classes and entity classes can extend each other. Hibernate provides the MappedSuperClass annotation (@MappedSuperclass) to enable inheritance mapping and to let you map abstract or concrete entity subclasses. The mapped superclass doesn't have its own separate table.

Hibernate provides four strategies for mapping an inheritance hierarchy:

  • Table per class hierarchy: The class hierarchy is represented in one table. A discriminator column identifies the type and the subclass.

  • Table per subclass: The superclass has a table and each subclass has a table that contains only un-inherited properties: the subclass tables have a primary key that is a foreign key of the superclass.

  • Table per concrete class with unions: the superclass can be an abstract class or even an interface. If the superclass is concrete, an additional table is required to map the properties of that class.

  • Table per concrete class with implicit polymorphism: each table contains all the properties of the concrete class and the properties that are inherited from its superclasses. Here, all the subclasses are mapped as separate entities.

This chapter discusses these strategies in detail and shows how to implement them.

The properties of all persistent entities are of certain types, such as String. Some of the important basic types supported by Hibernate are as follows:

  • integer, long, short, float, double, character, byte, boolean, yes_no, and true_false. Type mappings for Java primitives and wrapper classes.

  • string. Type mappings from java.lang.String to something like a VARCHAR.

  • date, time, and timestamp. Type mapping from java.util.Date and its subclasses to their appropriate SQL types.

Other basic mappings supported by Hibernate include calendar, calendar_date, big_decimal, big_integer, locale, timezone, currency, class, binary, text, and serializable. Hibernate also provides an API to implement your own custom types. You can use two packages, org.hibernate.type and org.hibernate. userType, to define object types. You can define your own custom types either by implementing org.hibernate.type.Type directly or by extending one of the abstract classes provided in the org.hibernate.type package; or you can implement one of the interfaces in the org.hibernate.userType package. This chapter discusses the custom mapping types available in Hibernate's org.hibernate.userType package. You go through the implementation details of the userType and CompositeUserType extensions.

Mapping Entities with Table per Class Hierarchy

Problem

How do you map entities using the table per class hierarchy strategy? And when should you use it?

Solution

To use the table per class hierarchy strategy, you need to create a single table that contains the state of the complete class hierarchy. The key is that the subclass state can't have not-null constraints. This is a disadvantage, because data integrity is compromised. Because all the state is in the same table, it's a denormalized model.

With this strategy, you get the advantage of good performance for both polymorphic and nonpolymorphic queries. The denormalization of the class hierarchy in the relational model can cause data stability and maintainability issues, so it's a good idea to run this by your DBA.

You use the audio and video section of your bookshop to learn about inheritance mapping. Suppose that your bookshop also sells CDs and DVDs. Figure 4-1 shows the class diagram of the object model you map as table per class hierarchy.

Class diagram of the object model

Figure 4-1. Class diagram of the object model

You first create a class Disc and provide a mapping definition for it:

public class Disc {
  private Long id;
  private String name;
  private Integer price;
  // Getters and Setters
}
<hibernate-mapping package="com.hibernaterecipes.chapter4.tablePerClassHierarchy">
  <class name="Disc" table="Disc_1">
    <id name="discId" type="long" column="DISC_ID">
      <generator class="native"/>
    </id>
    <discriminator column="DISC_TYPE" type="string" />
    <property name="name" type="java.lang.String" column="NAME" />
    <property name="price" type="java.lang.Integer" column="PRICE" />

  </class>
</hibernate-mapping>

You primarily sell two kinds of discs: audio discs and video discs. Each kind has different properties. From an object-oriented perspective, you should model these two kinds of discs, AudioDisc and VideoDisc, as subclasses of Disc to represent an "is-a" relationship. In Java, you use the extends keyword to define a subclass of a class. Conversely the class Disc is called the superclass or parent class of AudioDisc and VideoDisc:

public class AudioDisc extends Disc {
  private String singer;
  private Integer numOfSongs;
  // Getters and Setters
}

public class VideoDisc extends Disc {
  private String director;
  private String language;
  // Getters and Setters
}

The relationship between a subclass (such as AudioDisc or VideoDisc) to its parent class (Disc) is called inheritance. All the subclasses and their parents make up a class hierarchy. A relational model has no concept of inheritance—that means you must define a mapping mechanism to persist the inheritance relationships of your object model.

For the disc hierarchy, you can use the following query to find all the discs in your system, both audio and video. This kind of query is called a polymorphic query:

Session session = factory.openSession();
try {
  Query query = session.createQuery("from Disc");
  List discs = query.list();
  return discs;
} finally {
  session.close();
}

Suppose you want to support disc reservation for your online bookshop. You can create a class Reservation and define a many-to-one association to the Disc class. Because the concrete class of the disc may be AudioDisc or VideoDisc, and this can only be determined at runtime, this kind of association is called polymorphic association:

public class Reservation {
  private Long id;
  private Disc disc;
  private Customer customer;
  private int quantity;
}

How It Works

In Hibernate, the subclass element is used to create a table per class hierarchy mapping. The discriminator element is used to associate the subclasses with the superclass. The following query creates the table:

CREATE TABLE "BOOK"."DISC_1"
   (   "DISC_ID" BIGINT NOT NULL ,
         "NAME" VARCHAR(250 ) NOT NULL ,
   "PRICE" BIGINT,
   "DISC_TYPE" VARCHAR(50),
         "SINGER" VARCHAR(50),
         "NO_OF_SONGS" BIGINT,
          "DIRECTOR" VARCHAR(50),
         "LANGUAGE" VARCHAR(50),
    CONSTRAINT "DISC_1_PK" PRIMARY KEY ("DISC_ID") );

The Hibernate mapping file looks like this:

<hibernate-mapping package="com.hibernaterecipes.chapter4.tablePerClassHierarchy">
  <class name="Disc" table="Disc_1">
    <id name="discId" type="long" column="DISC_ID">
      <generator class="native"/>
    </id>
    <discriminator column="DISC_TYPE" type="string" />
    <property name="name" type="java.lang.String" column="NAME" />
    <property name="price" type="java.lang.Integer" column="PRICE" />
    <subclass name="AudioDisc" discriminator-value="AUDIO">
      <property name="singer" type="java.lang.String" column="SINGER" />
      <property name="noOfSongs" type="java.lang.Integer" column="NO_OF_SONGS" />
    </subclass>
    <subclass name="VideoDisc" discriminator-value="VIDEO">
      <property name="director" type="java.lang.String" column="DIRECTOR" />
      <property name="language" type="java.lang.String" column="LANGUAGE" />
</subclass>
  </class>
</hibernate-mapping>

The class is mapped to the top-level class, which is Disc. The persistent classes AudioDisc and VideoDisc are distinguished from each other by the discriminator, which is mapped to a column DISC_TYPE. The other properties of the superclass are mapped using the property element.

The persistent classes are mapped with the subclass element, which has a discriminator-value attribute that differentiates if from other persistent classes. So, when an AudioDisc is saved, the DISC_TYPE column for that record has the value AUDIO. Similarly, for VideoDisc records, the DISC_TYPE column holds the value VIDEO. The subclass element contains properties of the persistent class. It can also contain nested subclass elements; collection elements like list, map, and set; and a one-to-one or many-to-one element.

To implement this in JPA, you have the Disc class use the Inheritance and DiscriminatorColumn annotations as shown here:

@Entity
@Inheritance (strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="DISC_TYPE",
    discriminatorType=DiscriminatorType.STRING)
public class Disc_1 implements Serializable {

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

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

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

  //Getters  and Setters
}

This inheritance strategy is described by an inheritance type of SINGLE_TABLE; the discriminator column name and type are also defined in the parent class. Subclasses like AudioDisc use the Discriminator annotation to describe the value to be used:

@Entity
@DiscriminatorValue ("AUDIO")
public class AudioDisc_1 extends Disc_1 implements Serializable {

  private static final long serialVersionUID = 8510682776718466795L;

  @Column (name="NO_OF_SONGS")
  private Integer noOfSongs;

  @Column (name="SINGER")
  private String singer;
//Getters  and Setters
}

Similarly, the VideoDisc class is as follows:

@Entity
@DiscriminatorValue ("VIDEO")
public class VideoDisc_1 extends Disc_1 implements Serializable {

  private static final long serialVersionUID = −3637473456207740684L;

  @Column (name="DIRECTOR")
  private String director;
  @Column (name="LANGUAGE")
  private String language;

  //Getters  and Setters
}

To save an audio disc record, Hibernate executes a SQL that looks like this:

insert into Disc_1 (NAME, PRICE, SINGER, NO_OF_SONGS, DISC_TYPE, DISC_ID)
values (?, ?, ?, ?, 'AUDIO', ?)

If you query for audio discs, you use a query something like session.createQuery("from AudioDi sc").list();. Hibernate converts this into a query like the following:

select a.DISC_ID, a.NAME , a.PRICE , a.SINGER , a.NO_OF_SONGS from Disc_1 a where a.DISC_TYPE='AUDIO'

If you query for the superclass Disc using session.createQuery("from Disc").list();, the query that Hibernate executes is

select a.DISC_ID , a.NAME, a.PRICE , a.SINGER , a.NO_OF_SONGS , a.DIRECTOR, a.LANGUAGE , a.DISC_TYPE from Disc_1 a _

You can see that polymorphic queries are easy with this strategy. This approach should be your default choice for all simple problems. Also use this strategy when subclasses have few properties and inherit most of the properties. The idea is to keep the nullable columns from all the subclasses at a minimum.

Mapping Entities with Table per Subclass

Problem

How do you map entities using the table per subclass strategy? And when should you use it?

Solution

To use the table per subclass strategy, you need to create a superclass Disc (and a corresponding table) and create a table per subclass that contains only un-inherited properties. The two tables are associated with a foreign key: the subclass tables have a primary key that is a foreign key of the superclass. The advantage of this strategy is that the model is normalized.

How It Works

Begin by creating the tables using the following queries:

CREATE TABLE "BOOK"."DISC_2"
   ( "DISC_ID" BIGINT NOT NULL ,
 "NAME" VARCHAR(250 ) NOT NULL ,
 "PRICE" BIGINT,
  CONSTRAINT "DISC_2_PK" PRIMARY KEY ("DISC_ID") )

CREATE TABLE "BOOK"."AUDIO_DISC_2"
   ( "DISC_ID" BIGINT NOT NULL ,
 "SINGER" VARCHAR(50),"NO_OF_SONGS" BIGINT,
  CONSTRAINT "DISC_2_FK" FOREIGN KEY ("DISC_ID") REFERENCES DISC_2(DISC_ID))

CREATE TABLE "BOOK"."VIDEO_DISC_2"
   ( "DISC_ID" BIGINT NOT NULL ,
 "DIRECTOR" VARCHAR(50),"LANGUAGE" VARCHAR(50),
  CONSTRAINT "DISC_2_1_FK" FOREIGN KEY ("DISC_ID") REFERENCES DISC_2(DISC_ID))

In Hibernate, the superclass element is as follows:

public class Disc_2 implements Serializable {


  private static final long serialVersionUID = −5119119376751110049L;

  private Long discId;
  private String name;
  private Integer price;

  // getters and setters
}

And the audio and video subclasses look like this:

public class AudioDisc_2 extends Disc_2 implements Serializable {

  private static final long serialVersionUID = 1542177945025584005L;
  private Integer noOfSongs;
  private String singer;
  // getters and setters
}

public class VideoDisc_2 extends Disc_2 implements Serializable {


  private static final long serialVersionUID = 3052184294723526581L;
  private String director;
  private String language;
  // getters and setters
}

The mapping file is as follows:

<hibernate-mapping package="com.hibernaterecipes.chapter4.tablePerSubClass">
  <class name="Disc_2" table="Disc_2">
    <id name="discId" type="long" column="DISC_ID">
      <generator class="native"/>
    </id>
    <property name="name" type="java.lang.String" column="NAME" />
    <property name="price" type="java.lang.Integer" column="PRICE" />
    <joined-subclass name="AudioDisc_2" table="AUDIO_DISC_2">
      <key column="DISC_ID"></key>
      <property name="singer" type="java.lang.String" column="SINGER" />
      <property name="noOfSongs" type="java.lang.Integer" column="NO_OF_SONGS" />
    </joined-subclass>
    <joined-subclass name="VideoDisc_2" table="VIDEO_DISC_2">
      <key column="DISC_ID"></key>
      <property name="director" type="java.lang.String" column="DIRECTOR" />
      <property name="language" type="java.lang.String" column="LANGUAGE" />
    </joined-subclass>
  </class>
</hibernate-mapping>

The superclass Disc is defined as the root class and has a primary key of DISC_ID. The subclass is mapped using the joined-subclass element, and the joined-subclass element defines the subclass table with the attribute table. The joined-subclass classes like the AudioDisc class have a key element that provides for the association between itself and its parent.

Using JPA annotation, the superclass (Disc in this case) uses the Inheritance annotation with the inheritance strategy type of JOINED:

@Entity
@Inheritance (strategy=InheritanceType.JOINED)
public abstract class Disc_2 implements Serializable {


  private static final long serialVersionUID = 3087285416805917315L;
  @Id
  @GeneratedValue (strategy=GenerationType.AUTO)
  @Column (name="DISC_ID")
  private Long discId;
  @Column (name="NAME")
private String name;
  @Column (name="PRICE")
  private Integer price;

  //getters and settes
}

The audio and video subclasses are annotated like regular entity classes and are associated with the parent class with the Pri maryKeyJoinColumn annotation. PrimaryKeyJoinColumn is required only when the name of the column in the subclass is different from that of the superclass. When the name of the primary column is the same in both classes, Hibernate inherits the property and column from the superclass:

@Entity
@Table (name="AUDIO_DISC_2")
@PrimaryKeyJoinColumn (name="DISC_ID")
public class AudioDisc_2 extends Disc_2 implements Serializable {

  private static final long serialVersionUID = 8510682776718466795L;

  @Column (name="NO_OF_SONGS")
  private Integer noOfSongs;
  @Column (name="SINGER")
  private String singer;
        // getter and setters
}

@Entity
@Table (name="VIDEO_DISC_2")
@PrimaryKeyJoinColumn (name="DISC_ID")
public class VideoDisc_2 extends Disc_2 implements Serializable {

  private static final long serialVersionUID = −3637473456207740684L;

  @Column (name="DIRECTOR")
  private String director;
  @Column (name="LANGUAGE")
  private String language;

        // getter and setters
}

If you query for a disc, Hibernate uses an outer join to determine the associated subclasses. The query that is used is as follows; as you can see, it may not be simple—at least, not as simple as it looks from the mapping you gave Hibernate:

select a.DISC_ID , a.NAME , a.PRICE , b.SINGER , b.NO_OF_SONGS , c.DIRECTOR , c.LANGUAGE ,
case when b.DISC_ID is not null then 1
   when c.DISC_ID is not null then 2
   when a.DISC_ID is not null then 0 else −1 end as clazz_
from Disc_2 a
left outer join AUDIO_DISC_2 b on a.DISC_ID=b.DISC_ID
left outer join VIDEO_DISC_2 c on a.DISC_ID=c.DISC_ID

To query the subclasses, Hibernate uses the following inner join:

select b.DISC_ID , a.NAME , a.PRICE , b.SINGER  b.NO_OF_SONGS
from AUDIO_DISC_2 b inner join Disc_2 a
on b.DISC_ID=a.DISC_ID

You should use this strategy when there is a requirement to use polymorphic associations and polymorphic queries. The subclasses' state varies a great deal: that is, the properties that one subclass holds are very different from the properties that other subclasses hold. Also, when the class hierarchy is spread across many classes (depth of inheritance), this strategy can have an impact on performance.

Mapping Entities with Table per Concrete Class

Problem

How do you map entities using the table per concrete class strategy? And when should you use it?

Solution

You can use the table per concrete class strategy two ways:

  • Table per concrete class with implicit polymorphism

  • Table per concrete class with unions

Using table per concrete class with implicit polymorphism, the table contains all the properties of the concrete class and the properties that are inherited from its superclasses. All subclasses are mapped as separate entities. You should use this strategy when the requirement for polymorphic association or polymorphic query is minimal. Another issue with this strategy is that columns are duplicated; this can cause maintenance problems because a change to the superclass requires you to change multiple tables. The mapping is similar to a regular entity, so you don't work through an example of it.

Using table per concrete class with unions, the superclass can be an abstract class or even an interface. The subclasses are mapped using the union-subclass element. If the superclass is concrete, an additional table is required to map the properties of that class.

How It Works

You begin by creating the tables using the following Data Definition Language (DDL):

CREATE TABLE "BOOK"."AUDIO_DISC_3"
   ( "DISC_ID" BIGINT NOT NULL ,
 "NAME" VARCHAR(250 ) NOT NULL ,
 "PRICE" BIGINT,
 "SINGER" VARCHAR(50),"NO_OF_SONGS" BIGINT,
CONSTRAINT "DISC_3_0_PK" PRIMARY KEY ("DISC_ID") )

CREATE TABLE "BOOK"."VIDEO_DISC_3"
   ( "DISC_ID" BIGINT NOT NULL ,
 "NAME" VARCHAR(250 ) NOT NULL ,
 "PRICE" BIGINT,
 "DIRECTOR" VARCHAR(50),"LANGUAGE" VARCHAR(50),
  CONSTRAINT "DISC_3_1_PK" PRIMARY KEY ("DISC_ID") )

The Disc, AudioDisc, and VideoDisc classes are as follows:

public abstract class Disc_3 implements Serializable {
  private static final long serialVersionUID = −5119119376751110049L;
  private Long discId;
  private String name;
  private Integer price;
  //getters and setters
}

public class AudioDisc3 extends Disc_3 implements Serializable {
  private static final long serialVersionUID = −8314602929677976050L;
  private Integer noOfSongs;
  private String singer;
  //getters and setters
}

public class VideoDisc_3 extends Disc_3 implements Serializable {
  private static final long serialVersionUID = −6857479057343664829L;
  private String director;
  private String language;
  //getters and setters
}

Note that the Disc_3 class is an abstract class. The Hibernate mapping file is as follows:

<hibernate-mapping package="com.hibernaterecipes.chapter4.tablePerClassHierarchy">
  <class name="Disc_3" abstract="true">
    <id name="discId" type="long" column="DISC_ID">
      <generator class="native"/>
    </id>
    <property name="name" type="java.lang.String" column="NAME" />
    <property name="price" type="java.lang.Integer" column="PRICE" />
    <union-subclass name="AudioDisc_3" table="AUDIO_DISC_3">
      <property name="singer" type="java.lang.String" column="SINGER" />
      <property name="noOfSongs" type="java.lang.Integer" column="NO_OF_SONGS" />
    </union-subclass>
    <union-subclass name="VideoDisc_3" table="VIDEO_DISC_3">
      <property name="director" type="java.lang.String" column="DIRECTOR" />
      <property name="language" type="java.lang.String" column="LANGUAGE" />
    </union-subclass>
  </class>
</hibernate-mapping>

The superclass Disc_3 must be declared abstract="true". If you choose not to have an abstract superclass, then you need a separate table for the superclass properties. The primary key is shared among all the concrete classes in the hierarchy. The primary key and inherited properties are mapped with the root class element. This way, duplication of inherited properties in each subclass isn't required. The concrete class is mapped to a single table the way AudioDisc_3 is mapped to the AUDIO_DISC_3 table; it inherits the superclass properties and the identifier. The concrete class is mapped using the union-subclass element.

The JPA specifications state that the table per concrete class is optional. However, Hibernate provides the implementation. In JPA annotations, you have the superclass Disc (Disc_4 in the following case) use the Inheritance annotation and the TABLE_PER_CLASS inheritance type strategy:

@Entity
@Inheritance (strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class Disc_4 implements Serializable {

  private static final long serialVersionUID = 3087285416805917315L;

  @Id
  @GeneratedValue (strategy=GenerationType.AUTO)
  @Column (name="DISC_ID")
  private Long discId;
  @Column (name="NAME")
  private String name;
  @Column (name="PRICE")
  private Integer price;

  //getters and setters
}

All the concrete class has to do is to declare itself as an Entity with the Entity annotation:

@Entity
@Table (name="AUDIO_DISC_4")
public class AudioDisc_4 extends Disc_4 implements Serializable {

  private static final long serialVersionUID = 8510682776718466795L;

  @Column (name="NO_OF_SONGS")
  private Integer noOfSongs;
  @Column (name="SINGER")
  private String singer;
  //getters and setters
}

Because the mapping is table per concrete class, the query created is for direct concrete class. If you need a polymorphic querying capability, you must make the superclass a persistent class (have a table of its own).

Custom Mappings

Problem

What are the various extensions that Hibernate provides to implement custom mapping types? How do you create UserType custom mapping?

Solution

Hibernate provides an interface that you can implement to define your own custom mapping types. The extensions are listed in the org.hibernate.usertype package and are as follows:

  • CompositeUserType: This user type exposes the internals of the value type to Hibernate, which allows you to query for specific internal properties.

  • EnhancedUserType: This is used when marshalling of value types to and from XML is required. It lets you use the custom type in identifier and discriminator mappings.

  • LoggableUserType: This type is used when you want custom logging of the corresponding value types.

  • ParameterizedType: You can set parameters for a type by using a nested type element for the property element in the mapping file or by defining a typedef.

  • UserCollectionType: Use this type when you want to implement a custom collection mapping type and when you have to persist a non-JDK type collection.

  • UserType: This is a basic extension point. It provides a basic framework to implement custom loading and storing of value type instances.

  • UserVersionType: This type is used for a version property.

How It Works

Let's say your object model has a phone object. The phone object has two properties: areaCode and telNo. The database has a single column for this phone object, of the format areaCode-telNo. It's recommended by Hibernate that the phone class be immutable, which means you need to make the properties final and have a constructor that sets these properties on initialization. Using an immutable class makes the implementation easier.

The following example demonstrates the implementation for a mutable class:

public class PhoneCh4_4 {

  private String areaCode;
  private String telNo;
  // getters and setters
}

You can write your own phone type that implements the UserType interface. The implementation, shown in a moment, uses the following methods:

  • Assemble: Reconstructs an object from the cacheable representation. If the object is immutable, then you can return the cached object. Mutable objects are expected to at least perform a deep copy.

  • Disassemble: Called when Hibernate is storing the object as a serialized object in second-level memory or cache. (Caching in discussed further in Chapter 12.) For immutable objects, you typecast the object as serializable and return it. For mutable objects, you should return a deep copy. Also, it may not as simple if the mutable object has other associations. Associations should be cached as identifier values.

  • deepCopy: Returns the persistent state of the object. For immutable objects, it can return the object itself.

  • Equals: Checks the equality of two instances for persistent state.

  • hashCode: Performs a hash-code implementation for the instance.

  • isMutable: Specifies whether objects of this type are immutable.

  • nullSafeGet: Retrieves the object from the JDBC resultset.

  • nullSafeSet: Writes the object property value to the JDBC prepared statement.

  • replace: Used when you're merging with a detached instance. For immutable objects, you can return the first parameter (original). For mutable objects, you return a copy of the first parameter. A recursive copy should be performed if the mutable objects have component fields.

  • returnedClass: Specifies the class returned by the nullSafeGet method.

  • sqlTypes: Returns the SQL types for the columns mapped by this type. The types are indentified as codes, and codes are defined in java.sql.Types.

The implementation is as follows:

package com.hibernaterecipes.chapter4.custommappings;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.StringTokenizer;

import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.type.SerializationException;
import org.hibernate.usertype.UserType;

import com.hibernaterecipes.chapter4.custommappings.PhoneCh4_4;

/**
* @author Guruzu
 *
 */
public class PhoneUserType implements UserType {

  /* (non-Javadoc)
   * @see org.hibernate.usertype.UserType#assemble(java.io.Serializable, java.lang.Object)
   */
  @Override
  public Object assemble(Serializable cached, Object owner)
      throws HibernateException {
    return deepCopy(cached);
  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.UserType#deepCopy(java.lang.Object)
   */
  @Override
  public Object deepCopy(Object value) throws HibernateException {
    if(value==null)
      return null;
    PhoneCh4_4 phoneValue = (PhoneCh4_4)value;
    PhoneCh4_4 phoneCopied = new PhoneCh4_4();
    phoneCopied.setAreaCode(phoneValue.getAreaCode());
    phoneCopied.setTelNo(phoneValue.getTelNo());
    return phoneCopied;
  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.UserType#disassemble(java.lang.Object)
   */
  @Override
  public Serializable disassemble(Object value) throws HibernateException {
    Object deepCopy = deepCopy(value);

    if (!(deepCopy instanceof Serializable)) {
      throw new SerializationException(value.getClass().getName()
         + " is not serializable ",null);
    }

    return (Serializable) deepCopy;
  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.UserType#equals(java.lang.Object, java.lang.Object)
   */
  @Override
  public boolean equals(Object x, Object y) throws HibernateException {
    if(x == y)
      return true;
if(x ==null || y == null)
      return false;

    return x.equals(y);
  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.UserType#hashCode(java.lang.Object)
   */
  @Override
  public int hashCode(Object PhoneCh4_4) throws HibernateException {
    assert (PhoneCh4_4 != null);

    return PhoneCh4_4.hashCode();
  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.UserType#isMutable()
   */
  @Override
  public boolean isMutable() {

    return true;
  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.UserType#nullSafeGet(java.sql.ResultSet, java.lang.String[], java.lang.Object)
   */
  @Override
  public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner)
      throws HibernateException, SQLException {
    String completeTelNo = resultSet.getString(names[0]);

    if(resultSet.wasNull()) {
      return null;
    }
    String areaCode = null;
    String telNo = null;
    StringTokenizer st = new StringTokenizer(completeTelNo,"-");
     while (st.hasMoreTokens()) {
       if(areaCode == null)
       {
         areaCode = st.nextToken();
       }else
       {
         telNo = st.nextToken();
       }
     }
    PhoneCh4_4 phn = new PhoneCh4_4();
    phn.setAreaCode(areaCode);
phn.setTelNo(telNo);
    return phn;

  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.UserType#nullSafeSet(java.sql.PreparedStatement, java.lang.Object, int)
   */
  @Override
  public void nullSafeSet(PreparedStatement statement, Object value, int index)
      throws HibernateException, SQLException {

    if(value == null) {
      statement.setNull(index, Hibernate.STRING.sqlType());
    } else {
      PhoneCh4_4 phone = (PhoneCh4_4)value;

      statement.setString(index, phone.convertToCompleteTelNum());
    }

  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.UserType#replace(java.lang.Object, java.lang.Object, java.lang.Object)
   */
  @Override
  public Object replace(Object original, Object target, Object owner)
      throws HibernateException {

    return deepCopy(original);
  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.UserType#returnedClass()
   */
  @Override
  public Class returnedClass() {

    return com.hibernaterecipes.chapter4.custommappings.PhoneCh4_4.class;
  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.UserType#sqlTypes()
   */
  @Override
  public int[] sqlTypes() {

    return new int [] {java.sql.Types.VARCHAR};
}

}

Now that you've completed the implementation of the UserType interface, the Hibernate mapping file for the order class is as follows:

<hibernate-mapping package="com.hibernaterecipes.chapter4.custommappings">
  <class name="OrdersCh4_4" 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="com.hibernaterecipes.chapter4.custommappings.PhoneUserType"
              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" />
  </class>
</hibernate-mapping>

In JPA annotations, the order class uses the annotation org.hibernat e.annotations.Type and defines the type to be PhoneUserType:

@Entity (name="OrderCh4_4")
@Table (name="ORDERS")
public class OrdersCh4_4 {

  @Id
  @GeneratedValue (strategy=GenerationType.AUTO)
  @Column (name="ID")
  private Long id;
  //private Book book;
  @Column (name="WEEKDAY_RECIPIENT")
  private String weekdayRecipient;

  @org.hibernate.annotations.Type
  (type = "com.hibernaterecipes.annotations.custommapping.PhoneUserType")
  @Column (name="WEEKDAY_PHONE")
  private PhoneCh4_4 weekdayPhone;

  @Column (name="WEEKDAY_ADDRESS")
  private String weekdayAddress;
  @Column (name="HOLIDAY_RECIPIENT")
  private String holidayRecipient;
  @org.hibernate.annotations.Type
  (type = "com.hibernaterecipes.annotations.custommapping.PhoneUserType")
  @Column (name="HOLIDAY_PHONE")
  private PhoneCh4_4 holidayPhone;
@Column (name="HOLIDAY_ADDRESS")
  private String holidayAddress;

  // getters and setters
}

You can use this UserTy pe to handle issues that accompany legacy databases. It can also perform validation or talk to an LDAP directory. The implementation doesn't support proper querying, which means you can't create a criteria object and search for all orders that have a specific area code. In addition, to cache an object of type UserType, the object must be serializable.

CompositeUserType Mappings

Problem

What if you want to query for just a telephone number or get all phone numbers that are within a single area code? You implemented the UserType interface to map to a single database column, which makes it less flexible to query in such scenarios. The CompositeUserType interface lets you map properties to multiple columns. How do you create the CompositeUserType custom mapping?

Solution

You need to implement the CompositeUserType interface to get the power of Hibernate queries. By implementing the CompositeUserType interface, the properties of the UserType are exposed to Hibernate queries. Here, you map the properties of the phone object to two separate columns: one for the area code and one for the telephone number.

How It Works

The implementation of CompositeUserType has methods very similar to those of UserType. It doesn't have the sqlTypes() method. The implementation is as follows:

package com.hibernaterecipes.chapter4.custommappings;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.type.SerializationException;
import org.hibernate.type.Type;
import org.hibernate.usertype.CompositeUserType;
import com.hibernaterecipes.chapter4.custommappings.PhoneCh4_4;

/**
 * @author Guruzu
 *
 */
public class PhoneCompositeUserType implements CompositeUserType {

  /* (non-Javadoc)
* @see org.hibernate.usertype.CompositeUserType#assemble(java.io.Serializable, org.hibernate.engine.SessionImplementor, java.lang.Object)
   */
  @Override
  public Object assemble(Serializable cached, SessionImplementor implementor,
      Object obj) throws HibernateException {
    return deepCopy(cached);
  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.CompositeUserType#deepCopy(java.lang.Object)
   */
  @Override
  public Object deepCopy(Object value) throws HibernateException {
    if(value==null)
      return null;
    PhoneCh4_4 phoneValue = (PhoneCh4_4)value;
    PhoneCh4_4 phoneCopied = new PhoneCh4_4();
    phoneCopied.setAreaCode(phoneValue.getAreaCode());
    phoneCopied.setTelNo(phoneValue.getTelNo());
    return phoneCopied;
  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.CompositeUserType#disassemble(java.lang.Object, org.hibernate.engine.SessionImplementor)
   */
  @Override
  public Serializable disassemble(Object value, SessionImplementor implementor)
      throws HibernateException {
    Object deepCopy = deepCopy(value);

    if (!(deepCopy instanceof Serializable)) {
      throw new SerializationException(value.getClass().getName()
         + " is not serializable ",null);
    }

    return (Serializable) deepCopy;
  }

  /* (non-Javadoc)
* @see org.hibernate.usertype.CompositeUserType#equals(java.lang.Object, java.lang.Object)
   */
  @Override
  public boolean equals(Object x, Object y) throws HibernateException {
    if(x == y)
      return true;

    if(x ==null || y == null)
      return false;

    return x.equals(y);
  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.CompositeUserType#getPropertyNames()
   */
  @Override
  public String[] getPropertyNames() {
    return new String[]{"areaCode", "telNo"};
  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.CompositeUserType#getPropertyTypes()
   */
  @Override
  public Type[] getPropertyTypes() {
    return new Type[] {Hibernate.STRING, Hibernate.STRING};
}

  /* (non-Javadoc)
   * @see org.hibernate.usertype.CompositeUserType#getPropertyValue(java.lang.Object, int)
   */
  @Override
  public Object getPropertyValue(Object component, int property)
      throws HibernateException {
    PhoneCh4_4 phone = (PhoneCh4_4)component;
    if(property == 0)
      return phone.getAreaCode();
    else
      return phone.getTelNo();
  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.CompositeUserType#hashCode(java.lang.Object)
   */
  @Override
  public int hashCode(Object phone) throws HibernateException {
    assert (phone != null);

    return phone.hashCode();
}

  /* (non-Javadoc)
   * @see org.hibernate.usertype.CompositeUserType#isMutable()
   */
  @Override
  public boolean isMutable() {
    return true;
  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.CompositeUserType#nullSafeGet(java.sql.ResultSet, java.lang.String[], org.hibernate.engine.SessionImplementor, java.lang.Object)
   */
  @Override
  public Object nullSafeGet(ResultSet resultSet, String[] names,
      SessionImplementor session, Object owner) throws HibernateException,
      SQLException {
    String areaCode = resultSet.getString(names[0]);
    if(resultSet.wasNull()) {
      return null;
    }
    String telNo = resultSet.getString(names[1]);


    return new PhoneCh4_4(areaCode, telNo);
}
/* (non-Javadoc)
   * @see org.hibernate.usertype.CompositeUserType#nullSafeSet(java.sql.PreparedStatement, java.lang.Object, int, org.hibernate.engine.SessionImplementor)
   */
  @Override
  public void nullSafeSet(PreparedStatement statement, Object value, int index,
      SessionImplementor session) throws HibernateException, SQLException {
    if(value == null) {
      statement.setNull(index, Hibernate.STRING.sqlType());
      statement.setNull(index+1, Hibernate.STRING.sqlType());
    } else {
      PhoneCh4_4 phone = (PhoneCh4_4)value;
      statement.setString(index, phone.getAreaCode());
      statement.setString(index+1, phone.getTelNo());
    }

  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.CompositeUserType#replace(java.lang.Object, java.lang.Object, org.hibernate.engine.SessionImplementor, java.lang.Object)
   */
  @Override
public Object replace(Object original, Object target, SessionImplementor owner,
      Object arg3) throws HibernateException {

    return deepCopy(original);
  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.CompositeUserType#returnedClass()
   */
  @Override
  public Class returnedClass() {
    return com.hibernaterecipes.chapter4.custommappings.PhoneCh4_4.class;
  }

  /* (non-Javadoc)
   * @see org.hibernate.usertype.CompositeUserType#setPropertyValue(java.lang.Object, int, java.lang.Object)
   */
  @Override
  public void setPropertyValue(Object component, int index, Object value)
      throws HibernateException {
    String columnValue = (String) value;

    PhoneCh4_4 phone = (PhoneCh4_4)component;
    if(index == 0)
    {
      phone.setAreaCode(columnValue);
    }else if (index == 1)
    {
      phone.setTelNo(columnValue);
    }else
    {
      throw new IllegalArgumentException("Unknown Property - "+index);
    }
  }
}

The nullSafetGet and nullSafetSet methods are updated to handle two properties. The following methods are new:

  • getPropertyNames: Specifies the property names to use in a query. Obviously, they have to match the name of the object property.

  • getPropertyValue: Gets the property value.

  • setPropertyValue: Sets the property value.

The Hibernate mapping for the order class is now as follows:

<hibernate-mapping package="com.hibernaterecipes.chapter4.custommappings">
  <class name="OrdersCh4_5" table="BOOK_ORDERS_2">
    <id name="id" type="long" column="ID">
      <generator class="native" />
</id>
    <component name="weekdayContact" class="ContactCh4_5">
      <property name="recipient" type="string" column="WEEKDAY_RECIPIENT" />
      <property name="phone"
                type="com.hibernaterecipes.chapter4.custommappings.PhoneCompositeUserType">
        <column name="WEEKDAY_AREACODE"></column>
        <column name="WEEKDAY_TELEPHONE"></column>
      </property>
      <many-to-one name="address" class="com.hibernaterecipes.chapter3.Address"
                   column="WEEKDAY_ADDRESS_ID" />
    </component>
    <component name="holidayContact" class="ContactCh4_5">
      <property name="recipient" type="string" column="HOLIDAY_RECIPIENT" />
      <property name="phone"
                type="com.hibernaterecipes.chapter4.custommappings.PhoneCompositeUserType">
        <column name="HOLIDAY_AREACODE"></column>
        <column name="HOLIDAY_TELEPHONE"></column>
      </property>
      <many-to-one name="address" class="com.hibernaterecipes.chapter3.Address"
                   column="HOLIDAY_ADDRESS_ID" />
    </component>
  </class>
</hibernate-mapping>

The phone object now maps to two columns: one to store the area code and the second to store the telephone number.

Using JPA annotations, the orders class is as follows:

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

  private Long id;
  private ContactCh4_5 weekdayContact;
  private ContactCh4_5 holidayContact;

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

  public void setId(Long id) {
    this.id = id;
  }


  @Embedded
  @AttributeOverrides (
{@AttributeOverride(name="recipient",column=@Column(name="WEEKDAY_RECIPIENT")),
    @AttributeOverride(name="phone.areaCode",column=@Column(name="WEEKDAY_AREACODE")),
    @AttributeOverride(name="phone.telNo",column=@Column(name="WEEKDAY_TELEPHONE")),
    @AttributeOverride(name="address",column=@Column(name="ADDRESS"))}
  )
  public ContactCh4_5 getWeekdayContact() {
    return weekdayContact;
  }

  public void setWeekdayContact(ContactCh4_5 weekdayContact) {
    this.weekdayContact = weekdayContact;
  }

  @Embedded
  @AttributeOverrides (
   {@AttributeOverride(name="recipient",column=@Column(name="HOLIDAY_RECIPIENT")),
    @AttributeOverride(name="phone.areaCode",column=@Column(name="HOLIDAY_AREACODE")),
    @AttributeOverride(name="phone.telNo",column=@Column(name="HOLIDAY_TELEPHONE")),
    @AttributeOverride(name="address",column=@Column(name="HOLIDAY_ADDRESS"))}
  )

  public ContactCh4_5 getHolidayContact() {
    return holidayContact;
  }

  public void setHolidayContact(ContactCh4_5 holidayContact) {
    this.holidayContact = holidayContact;
  }



}

Summary

In this chapter, you learned the four basic inheritance strategies: table per class hierarchy, table per subclass, table per concrete class with unions, and table per concrete class with implicit polymorphism.

You saw the implementation of these four inheritance mapping strategies and learned how to choose the appropriate strategy. Table 4-1 summarizes the relationship between the inheritance strategies and polymorphic associations.

Table 4-1. Polymorphic Associations Supported by Various Inheritance Strategies

Inheritance Strategy

Polymorphic Many-to-One

Polymorphic One-to-One

Polymorphic One-to-Many

Polymorphic Many-to-Many

Table per class hierarchy

<many-to-one>

<one-to-one>

<one-to-many>

<many-to-many>

Table per subclass

<many-to-one>

<one-to-one>

<one-to-many>

<many-to-many>

Table per concrete class with unions

<many-to-one>

<one-to-one>

<one-to-many> (for Inverse="true" only)

<many-to-many>

Table per concrete class with implicit polymorphism

<any>

Not supported

Not supported

<many-to-any>

You've seen how inheritance mapping enables polymorphic queries; Table 4-2 summarizes the polymorphic query capabilities supported by various inheritance strategies. You've also learned about the basic data types that Hibernate supports and the implementation of some custom data types.

Table 4-2. Polymorphic Queries Supported by Various Inheritance Strategies

Inheritance Strategy

Polymorphic load()/get()

Polymorphic Queries

Polymorphic Joins

Outer Join Fetching

Table per Class hierarchy

Supported

Supported

Supported

Supported

Table per subclass

Supported

Supported

Supported

Supported

Sub class

    

Table per concrete class with unions

Supported

Supported

Supported

Supported

Table per concrete class with implicit polymorphism

Supported

Supported

Not supported

Not supported

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

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