Java Object Model
This example application models a set of historical artifacts owned by a complex set of owners, including museums, foundations, exhibits, and individual owners. A given artifact may be owned by none, one, or many of these possible owners. Thus the example illustrates how two powerful concepts are used in Hibernate: the use of the Java collection framework to manage the ownership of artifacts by various owners, and the use of a class hierarchy to describe the various kinds of owners.
As shown in Figure 3.1, the application defines an abstract Owner class, with four possible concrete implementations. Some of these subclasses in turn may have an owner.

One or more of these possible owners may be connected to a particular Artifact, as shown in Figure 3.2.

Therefore, a given Artifact may be owned by zero or more owners of many different types. For example, a single artifact may be owned by a specific person, be on permanent loan to a museum, be managed by a foundation, and be part of two different exhibitions. This is a complex set of relationships, but not an atypical model, especially for an object-oriented developer.
Java Classes
The Java classes in this application contain several XDoclet tags. Before reviewing the Java classes in this application, a brief introduction to @ tags is required.
The @ symbol is generally used to provide an additional bit of information about a class or method for a source parser such as javadoc or XDoclet. The @hibernate. text is used to preface XDoclet Hibernate tags; all the other @ markup tags in this application are used by javadoc.
The @hibernate tags shown in Figure 3.3 are used in this application. A full list of tags can be found at http://xdoclet.sourceforge.net/tags/hibernate-tags.html. These tags correspond to the relevant HBM tags, as described in more detail in Chapter 5.
Let's start by looking at the abstract Owner class, as shown in Listing 3.1. It is superficially an ordinary Java class, but pay close attention to the various @ comment values. Note the convenient Owner.addArtifact() methodsnot necessary, but helpful.
Listing 3.1. Owner Class
package com.cascadetg.ch03;
import java.io.Serializable;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
/**
* @author Will Iverson
* @hibernate.class
* @hibernate.discriminator column="discriminator"
* @since 1.0
*/
public abstract class Owner implements Serializable
{
long id;
String name;
java.util.Set artifacts = new java.util.HashSet();
/**
* @hibernate.property
* @return Returns the name.
*/
public String getName() { return name; }
/**
* @param name The name to set.
*/
public void setName(String name) { this.name = name; }
/**
* @hibernate.set table="ownership" inverse="false"
lazy="true"
* @hibernate.collection-key column="owner_id"
* @hibernate.collection-many-to-many column="artifact_id"
class="com.cascadetg.ch03.Artifact"
* @return Returns the owners.
*/
public java.util.Set getArtifacts() { return artifacts; }
/**
* @param owners
* The owners to set.
*/
public void setArtifacts(java.util.Set artifacts)
{ this.artifacts = artifacts; }
public void addArtifact(Artifact newArtifact, boolean
already)
{
artifacts.add(newArtifact);
if(!already)
newArtifact.addOwner(this, true);
}
/** Utility function to make adding an artifact easier */
public void addArtifact(Artifact newArtifact)
{ this.addArtifact(newArtifact, true); }
/** Standard Hibernate equality override */
public boolean equals(Object other)
{
try
{
Owner castOther = (Owner)other;
return new EqualsBuilder()
.append(this.getId(), castOther.getId())
.isEquals();
} catch (Exception e) { return false; }
}
/** Standard Hibernate hash override */
public int hashCode()
{
return new
HashCodeBuilder().append(getId()).toHashCode();
}
/**
* @hibernate.id generator-class="native"
* @return Returns the id.
*/
public long getId() { return id; }
/**
* @param id The id to set.
*/
public void setId(long id) { this.id = id; }
}
Table 3.1. Reference XDoclet Tags@hibernate.class | Indicates that a *.hbm.xml file should be generated for this class. | @hibernate.subclass | Indicates that the information for this class should be generated using the subclass tag. For more information on the subclass tag, see Chapter 5. | @hibernate.discriminator | Because the application maps multiple classes to a single table, a column is needed in the table for Hibernate to store the class type. For more information, see the discriminator tag in Chapter 5. | @hibernate.property | Indicates to XDoclet that this get/set pair should be treated as a persistent property. If you don't use this flag, XDoclet won't generate a persistent property mapping in the *.hbm.xml file. | @hibernate.set | This application uses a java.util.Set to store the association between zero and one or more owners or between zero and one or more artifacts. Hibernate needs to know the table to use as an association table. This tag identifies the table. For descriptions of the inverse="false" and lazy="true" values, see the set tag in Chapter 5, and relationships in Chapter 7. | @hibernate.collection-key | This is used to store the id of the referenced artifact. |
Given the base Owner class, the first subclass, Exhibition, is shown in Listing 3.2. This class defines several additional properties over the base Owner class.
Listing 3.2. Exhibition Class
package com.cascadetg.ch03;
/**
* @author Will Iverson
* @hibernate.subclass
* @since 1.0
*/
public class Exhibition extends Owner
{
Owner owner;
String location;
java.util.Date startDate;
java.util.Date endDate;
/**
* @hibernate.property
* @return Returns the endDate.
*/
public java.util.Date getEndDate() { return endDate; }
/**
* @param endDate The endDate to set.
*/
public void setEndDate(java.util.Date endDate)
{ this.endDate = endDate; }
/**
* @hibernate.property
* @return Returns the location.
*/
public String getLocation() { return location; }
/**
* @param location The location to set.
*/
public void setLocation(String location)
{ this.location = location; }
/**
* @hibernate.many-to-one lazy="true"
* @return Returns the owner.
*/
public Owner getOwner() { return owner; }
/**
* @param owner The owner to set.
*/
public void setOwner(Owner owner) { this.owner = owner; }
/**
* @hibernate.property
* @return Returns the startDate.
*/
public java.util.Date getStartDate() { return startDate; }
/**
* @param startDate The startDate to set.
*/
public void setStartDate(java.util.Date startDate)
{ this.startDate = startDate; }
}
Listing 3.3 shows the next subclass, Foundation. This class defines several additional properties over the base Owner class. Some are similar to those described by Foundation, others are different.
Listing 3.3. Foundation Class
package com.cascadetg.ch03;
/**
* @author Will Iverson
* @hibernate.subclass
* @since 1.0
*/
public class Foundation extends Owner
{
Owner owner;
String location;
java.util.Date dateCreated;
/**
* @hibernate.property
* @return Returns the dateCreated.
*/
public java.util.Date getDateCreated()
{ return dateCreated; }
/**
* @param dateCreated The dateCreated to set.
*/
public void setDateCreated(java.util.Date dateCreated)
{ this.dateCreated = dateCreated; }
/**
* @hibernate.property
* @return Returns the location.
*/
public String getLocation()
{ return location; }
/**
* @param location The location to set.
*/
public void setLocation(String location)
{ this.location = location; }
/**
* @hibernate.many-to-one
* @return Returns the owner.
*/
public Owner getOwner() { return owner; }
/**
* @param owner The owner to set.
*/
public void setOwner(Owner owner) { this.owner = owner; }
}
Listing 3.4 shows the next subclass, Museum. Again, this class defines several additional properties over the base Owner class. Some are similar to those described by other subclasses, others are different.
Listing 3.4. Museum Class
package com.cascadetg.ch03;
/**
* @author Will Iverson
* @hibernate.subclass
* @since 1.0
*/
public class Museum extends Owner
{
String location;
java.util.Date dateOpened;
/**
* @hibernate.property
* @return Returns the dateOpened.
*/
public java.util.Date getDateOpened() { return dateOpened; }
/**
* @param dateOpened The dateOpened to set.
*/
public void setDateOpened(java.util.Date dateOpened)
{ this.dateOpened = dateOpened; }
/**
* @hibernate.property
* @return Returns the location.
*/
public String getLocation() { return location; }
/**
* @param location The location to set.
*/
public void setLocation(String location)
{ this.location = location; }
}
Listing 3.5 shows the final subclass, Person. This class also defines several additional properties over the base Owner class.
Listing 3.5. Person Class
package com.cascadetg.ch03;
import java.util.Date;
/**
* @author Will Iverson
* @hibernate.subclass
* @since 1.0
*/
public class Person extends Owner
{
Date birthDate;
Date deathDate;
/**
* @hibernate.property
* @return Returns the birthDate.
*/
public Date getBirthDate() { return birthDate; }
/**
* @param birthDate The birthDate to set.
*/
public void setBirthDate(Date birthDate)
{ this.birthDate = birthDate; }
/**
* @hibernate.property
* @return Returns the deathDate.
*/
public Date getDeathDate() { return deathDate; }
/**
* @param deathDate The deathDate to set.
*/
public void setDeathDate(Date deathDate)
{ this.deathDate = deathDate; }
}
Working with XDoclet
With Java files properly marked up, XDoclet (in conjunction with Ant, described in Chapter 2) can be used to generate the *.hbm.xml mapping files needed by Hibernate.
|
Hibernate includes a tool for generating HBM files from Java class files called MapGenerator. MapGenerator, unfortunately, does not generate complete *.hbm.xml information (but instead makes an incomplete best guess). This means that as you edit and modify your Java class files, you'll need to rerun the MapGenerator and then merge changes back into your existing *.hbm.xml file by hand. Using XDoclet lets you generate your *.hbm.xml files from your Java source, thereby eliminating the need for hand-merging. |
XDoclet can be downloaded from http://xdoclet.sourceforge.net/ (this text was written using xdoclet-bin-1.2). Once XDoclet has been downloaded, simply unzip or untar it into a directory you can easily find. You will then need to modify the xdoclet_lib_path, as shown in Listing 3.6, to point to the lib directory of the downloaded XDoclet.
The build file shown in Listing 3.6 compiles the Java source and then executes the XDoclet task to generate the *.hbm.xml files automatically. Pay close attention to the configuration of the XDoclet task.
Listing 3.6. XDoclet Build File
<?xml version="1.0"?>
<project name="ch03" default="all">
<target name="all" depends="compile,build_hbm" />
<description>Hibernate starting with Java</description>
<!-- You'll need to set these depending on your library
installation directories -->
<property name="hibernate_path"
value="C:\devenv\hibernate-2.1.2"/>
<property name="hibernate_tool_path"
value="C:\devenv\hibernate-extensions-2.1\tools"/>
<property name="xdoclet_lib_path"
value="C:\devenv\xdoclet-bin-1.2\lib"/>
<!-- Normally, build files are stored at the root of the
tree.
The builds for this book are on a per-chapter basis -->
<property name="base_dir" value="..\..\..\" />
<!-- This defines the Hibernate XDoclet task. You can
copy this verbatium, assuming you set the xdoclet
lib path above -->
<taskdef name="hibernatedoclet"
classname="xdoclet.modules.hibernate.Hibernate
DocletTask">
<classpath>
<fileset dir="${xdoclet_lib_path}">
<include name="*.jar"/>
</fileset>
</classpath>
</taskdef>
<path id="project.class.path">
<pathelement
location="${hibernate_path}\hibernate2.jar"/>
<pathelement
location="${hibernate_tool_path}\hibernate-
tools.jar"/>
<pathelement location=
"${hibernate_path}\lib\commons-collections-2.1.jar"
/>
<pathelement location=
"${hibernate_path}\lib\commons-logging-1.0.3.jar"
/>
<pathelement location=
"${hibernate_path}\lib\commons-lang-1.0.1.jar" />
<pathelement location=
"${hibernate_path}\lib\xerces-2.4.0.jar" />
<pathelement location=
"${hibernate_tool_path}\lib\jdom.jar"/>
</path>
<!-- Compiles the code using javac. If the files won't
compile, you won't be able to generate HBM files. -->
<target name="compile">
<javac srcdir="${base_dir}" destdir="${base_dir}">
<include name="**\ch03\*.java" />
<classpath refid="project.class.path"/>
</javac>
</target>
<!-- The force attribute causes the system to regenerate the
files every time the task is run.
Merge dir can be used to specify text that should be
automatically included with the generated HBM files.
If you don't specify the version as 2.0, you will get
out-of-date v1.x HBM files -->
<target name="build_hbm"
description="builds_hbm_file" depends="compile">
<hibernatedoclet
destdir="${base_dir}"
excludedtags="@version,@author,@todo"
force="true"
mergedir="${base_dir}"
verbose="true">
<fileset dir="${base_dir}">
<include name="**\ch03\*.java" />
</fileset>
<hibernate version="2.0"/>
</hibernatedoclet>
</target>
</project>
|