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 a
nnotation (@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.
How do you map entities using the table per class hierarchy strategy? And when should you use it?
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.
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; }
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 c
olumn 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.
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.
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.
public abstract class Disc_2 implements Serializable { private static final longJOINED
)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 longserialVersionUID
= 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 longserialVersionUID
= −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.
How do you map entities using the table per concrete class strategy? And when should you use it?
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.
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 longserialVersionUID
= −5119119376751110049L; private Long discId; private String name; private Integer price; //getters and setters } public class AudioDisc3 extends Disc_3 implements Serializable { private static final longserialVersionUID
= −8314602929677976050L; private Integer noOfSongs; private String singer; //getters and setters } public class VideoDisc_3 extends Disc_3 implements Serializable { private static final longserialVersionUID
= −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 longserialVersionUID
= 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 longserialVersionUID
= 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).
What are the various extensions that Hibernate provides to implement custom mapping types? How do you create UserType
custom mapping?
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.
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) */ @Overridepublic Object assemble(Serializable cached, Object owner)
throws HibernateException {
return deepCopy(cached); } /* (non-Javadoc) * @see org.hibernate.usertype.UserType#deepCopy(java.lang.Object) */ @Overridepublic 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) */ @Overridepublic 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) */ @Overridepublic 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) */ @Overridepublic int hashCode(Object PhoneCh4_4) throws HibernateException {
assert (PhoneCh4_4 != null); return PhoneCh4_4.hashCode(); } /* (non-Javadoc) * @see org.hibernate.usertype.UserType#isMutable() */ @Overridepublic boolean isMutable() {
return true; } /* (non-Javadoc) * @see org.hibernate.usertype.UserType#nullSafeGet(java.sql.ResultSet, java.lang.String[], java.lang.Object) */ @Overridepublic 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) */ @Overridepublic 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) */ @Overridepublic Object replace(Object original, Object target, Object owner)
throws HibernateException {
return deepCopy(original); } /* (non-Javadoc) * @see org.hibernate.usertype.UserType#returnedClass() */ @Overridepublic Class returnedClass() {
return com.hibernaterecipes.chapter4.custommappings.PhoneCh4_4.class; } /* (non-Javadoc) * @see org.hibernate.usertype.UserType#sqlTypes() */ @Overridepublic 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.
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?
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.
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) */ @Overridepublic Object assemble(Serializable cached, SessionImplementor implementor,
Object obj) throws HibernateException {
return deepCopy(cached); } /* (non-Javadoc) * @see org.hibernate.usertype.CompositeUserType#deepCopy(java.lang.Object) */ @Overridepublic 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) */ @Overridepublic 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) */ @Overridepublic 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() */ @Overridepublic String[] getPropertyNames() {
return new String[]{"areaCode", "telNo"}; } /* (non-Javadoc) * @see org.hibernate.usertype.CompositeUserType#getPropertyTypes() */ @Overridepublic Type[] getPropertyTypes() {
return new Type[] {Hibernate.STRING, Hibernate.STRING}; } /* (non-Javadoc) * @see org.hibernate.usertype.CompositeUserType#getPropertyValue(java.lang.Object, int) */ @Overridepublic 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) */ @Overridepublic int hashCode(Object phone) throws HibernateException {
assert (phone != null); return phone.hashCode();
} /* (non-Javadoc) * @see org.hibernate.usertype.CompositeUserType#isMutable() */ @Overridepublic 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) */ @Overridepublic 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) */ @Overridepublic 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() */ @Overridepublic 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) */ @Overridepublic 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; } }
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 |
|
|
|
|
Table per subclass |
|
|
|
|
Table per concrete class with unions |
|
|
|
|
Table per concrete class with implicit polymorphism |
| 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 |
3.146.176.88