hibernate-orm/reference/en/modules/persistent_classes.xml

513 lines
18 KiB
XML
Raw Normal View History

<chapter id="persistent-classes">
<title>Persistent Classes</title>
<para>
Persistent classes are classes in an application that implement the entities
of the business problem (e.g. Customer and Order in an E-commerce application).
Persistent classes have, as the name implies, transient and also persistent
instance stored in the database.
</para>
<para>
Hibernate works best if these classes follow some simple rules, also known
as the Plain Old Java Object (POJO) programming model.
</para>
<sect1 id="persistent-classes-pojo">
<title>A simple POJO example</title>
<para>
Most Java applications require a persistent class representing felines.
</para>
<programlisting><![CDATA[package eg;
import java.util.Set;
import java.util.Date;
public class Cat {
private Long id; // identifier
private String name;
private Date birthdate;
private Cat mate;
private Set kittens
private Color color;
private char sex;
private float weight;
private void setId(Long id) {
this.id=id;
}
public Long getId() {
return id;
}
void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
void setMate(Cat mate) {
this.mate = mate;
}
public Cat getMate() {
return mate;
}
void setBirthdate(Date date) {
birthdate = date;
}
public Date getBirthdate() {
return birthdate;
}
void setWeight(float weight) {
this.weight = weight;
}
public float getWeight() {
return weight;
}
public Color getColor() {
return color;
}
void setColor(Color color) {
this.color = color;
}
void setKittens(Set kittens) {
this.kittens = kittens;
}
public Set getKittens() {
return kittens;
}
// addKitten not needed by Hibernate
public void addKitten(Cat kitten) {
kittens.add(kitten);
}
void setSex(char sex) {
this.sex=sex;
}
public char getSex() {
return sex;
}
}]]></programlisting>
<para>
There are four main rules to follow here:
</para>
<sect2 id="persistent-classes-pojo-accessors">
<title>Declare accessors and mutators for persistent fields</title>
<para>
<literal>Cat</literal> declares accessor methods for all its persistent fields.
Many other ORM tools directly persist instance variables. We believe
it is far better to decouple this implementation detail from the persistence
mechanism. Hibernate persists JavaBeans style properties, and recognizes method
names of the form <literal>getFoo</literal>, <literal>isFoo</literal> and
<literal>setFoo</literal>.
</para>
<para>
Properties need <emphasis>not</emphasis> be declared public - Hibernate can
persist a property with a default, <literal>protected</literal> or <literal>
private</literal> get / set pair.
</para>
</sect2>
<sect2 id="persistent-classes-pojo-constructor">
<title>Implement a default constructor</title>
<para>
<literal>Cat</literal> has an implicit default (no-argument) constructor. All
persistent classes must have a default constructor (which may be non-public) so
Hibernate can instantiate them using <literal>Constructor.newInstance()</literal>.
</para>
</sect2>
<sect2 id="persistent-classes-pojo-identifier">
<title>Provide an identifier property (optional)</title>
<para>
<literal>Cat</literal> has a property called <literal>id</literal>. This property
holds the primary key column of a database table. The property might have been called
anything, and its type might have been any primitive type, any primitive "wrapper"
type, <literal>java.lang.String</literal> or <literal>java.util.Date</literal>. (If
your legacy database table has composite keys, you can even use a user-defined class
with properties of these types - see the section on composite identifiers below.)
</para>
<para>
The identifier property is optional. You can leave it off and let Hibernate keep track
of object identifiers internally. However, for many applications it is still
a good (and very popular) design decision.
</para>
<para>
What's more, some functionality is available only to classes which declare an
identifier property:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
Cascaded updates (see "Lifecycle Objects")
</para>
</listitem>
<listitem>
<para>
<literal>Session.saveOrUpdate()</literal>
</para>
</listitem>
</itemizedlist>
<para>
We recommend you declare consistently-named identifier properties on persistent
classes. We further recommend that you use a nullable (ie. non-primitive) type.
</para>
</sect2>
<sect2 id="persistent-classes-pojo-final">
<title>Prefer non-final classes (optional)</title>
<para>
A central feature of Hibernate, <emphasis>proxies</emphasis>, depends upon the
persistent class being either non-final, or the implementation of an interface
that declares all public methods.
</para>
<para>
You can persist <literal>final</literal> classes that do not implement an interface
with Hibernate, but you won't be able to use proxies - which will limit your options
for performance tuning somewhat.
</para>
</sect2>
</sect1>
<sect1 id="persistent-classes-inheritance">
<title>Implementing inheritance</title>
<para>
A subclass must also observe the first and second rules. It inherits its
identifier property from <literal>Cat</literal>.
</para>
<programlisting><![CDATA[package eg;
public class DomesticCat extends Cat {
private String name;
public String getName() {
return name;
}
protected void setName(String name) {
this.name=name;
}
}]]></programlisting>
</sect1>
<sect1 id="persistent-classes-equalshashcode">
<title>Implementing <literal>equals()</literal> and <literal>hashCode()</literal></title>
<para>
You have to override the <literal>equals()</literal> and <literal>hashCode()</literal>
methods if you intend to mix objects of persistent classes (e.g. in a <literal>Set</literal>).
</para>
<para>
<emphasis>This only applies if these objects are loaded in two different
<literal>Session</literal>s, as Hibernate only guarantees JVM identity (<literal> a == b </literal>,
the default implementation of <literal>equals()</literal>) inside a single
<literal>Session</literal>!</emphasis>
</para>
<para>
Even if both objecs <literal>a</literal> and <literal>b</literal> are the same database row
(they have the same primary key value as their identifier), we can't guarantee that they are
the same Java instance outside of a particular <literal>Session</literal> context.
</para>
<para>
The most obvious way is to implement <literal>equals()</literal>/<literal>hashCode()</literal>
by comparing the identifier value of both objects. If the value is the same, both must
be the same database row, they are therefore equal (if both are added to a <literal>Set</literal>,
we will only have one element in the <literal>Set</literal>). Unfortunately, we can't use that
approach. Hibernate will only assign identifier values to objects that are persistent,
a newly created instance will not have any identifier value! We recommend implementing
<literal>equals()</literal> and <literal>hashCode()</literal> using
<emphasis>Business key equality</emphasis>.
</para>
<para>
Business key equality means that the <literal>equals()</literal>
method compares only the properties that form the business key, a key that would
identify our instance in the real world (a <emphasis>natural</emphasis> candidate key):
</para>
<programlisting><![CDATA[public class Cat {
...
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof Cat)) return false;
final Cat cat = (Cat) other;
if (!getName().equals(cat.getName())) return false;
if (!getBirthday().equals(cat.getBirthday())) return false;
return true;
}
public int hashCode() {
int result;
result = getName().hashCode();
result = 29 * result + getBirthday().hashCode();
return result;
}
}]]></programlisting>
<para>
Keep in mind that our candidate key (in this case a composite of name and birthday)
has to be only valid for a particular comparison operation (maybe even only in a
single use case). We don't need the stability criteria we usually apply to a real
primary key!
</para>
</sect1>
<sect1 id="persistent-classes-lifecycle">
<title>Lifecycle Callbacks</title>
<para>
Optionally, a persistent class might implement the interface
<literal>Lifecycle</literal> which provides some callbacks that allow
the persistent object to perform necessary initialization/cleanup after
save or load and before deletion or update.
</para>
<para>
<!-- TODO: add xref to interceptor -->
The Hibernate <literal>Interceptor</literal> offers a less intrusive
alternative, however.
</para>
<programlistingco>
<areaspec>
<area id="lifecycle1" coords="2 70"/>
<area id="lifecycle2" coords="3 70" />
<area id="lifecycle3" coords="4 70"/>
<area id="lifecycle4" coords="5 70" />
</areaspec>
<programlisting><![CDATA[public interface Lifecycle {
public boolean onSave(Session s) throws CallbackException;
public boolean onUpdate(Session s) throws CallbackException;
public boolean onDelete(Session s) throws CallbackException;
public void onLoad(Session s, Serializable id);
}]]></programlisting>
<calloutlist>
<callout arearefs="lifecycle1">
<para>
<literal>onSave</literal> - called just before the object is saved or
inserted
</para>
</callout>
<callout arearefs="lifecycle2">
<para>
<literal>onUpdate</literal> - called just before an object is updated
(when the object is passed to <literal>Session.update()</literal>)
</para>
</callout>
<callout arearefs="lifecycle3">
<para>
<literal>onDelete</literal> - called just before an object is deleted
</para>
</callout>
<callout arearefs="lifecycle4">
<para>
<literal>onLoad</literal> - called just after an object is loaded
</para>
</callout>
</calloutlist>
</programlistingco>
<para>
<literal>onSave()</literal>, <literal>onDelete()</literal> and
<literal>onUpdate()</literal> may be used to cascade saves and
deletions of dependent objects. This is an alternative to declaring cascaded
operations in the mapping file. <literal>onLoad()</literal> may
be used to initialize transient properties of the object from its persistent
state. It may not be used to load dependent objects since the
<literal>Session</literal> interface may not be invoked from
inside this method. A further intended usage of <literal>onLoad()</literal>,
<literal>onSave()</literal> and <literal>onUpdate()</literal> is to store a
reference to the current <literal>Session</literal> for later use.
</para>
<para>
Note that <literal>onUpdate()</literal> is not called every time the object's
persistent state is updated. It is called only when a transient object is passed
to <literal>Session.update()</literal>.
</para>
<para>
If <literal>onSave()</literal>, <literal>onUpdate()</literal> or
<literal>onDelete()</literal> return <literal>true</literal>, the operation is
silently vetoed. If a <literal>CallbackException</literal> is thrown, the operation
is vetoed and the exception is passed back to the application.
</para>
<para>
Note that <literal>onSave()</literal> is called after an identifier is assigned to
the object, except when native key generation is used.
</para>
</sect1>
<sect1 id="persistent-classes-validatable">
<title>Validatable callback</title>
<para>
If the persistent class needs to check invariants before its state is
persisted, it may implement the following interface:
</para>
<programlisting><![CDATA[public interface Validatable {
public void validate() throws ValidationFailure;
}]]></programlisting>
<para>
The object should throw a <literal>ValidationFailure</literal> if an invariant
was violated. An instance of <literal>Validatable</literal> should not change
its state from inside <literal>validate()</literal>.
</para>
<para>
Unlike the callback methods of the <literal>Lifecycle</literal> interface,
<literal>validate()</literal> might be called at unpredictable times. The
application should not rely upon calls to <literal>validate()</literal> for
business functionality.
</para>
</sect1>
<sect1 id="persistent-classes-xdoclet">
<title>Using XDOclet markup</title>
<para>
In the next chapter we will show how Hibernate mappings may be expressed using
a simple, readable XML format. Many Hibernate users prefer to embed mapping
information directly in sourcecode using XDoclet <literal>@hibernate.tags</literal>.
We will not cover this approach in this document, since strictly it is considered
part of XDoclet. However, we include the following example of the <literal>Cat</literal>
class with XDoclet mappings.
</para>
<programlisting><![CDATA[package eg;
import java.util.Set;
import java.util.Date;
/**
* @hibernate.class
* table="CATS"
*/
public class Cat {
private Long id; // identifier
private Date birthdate;
private Cat mate;
private Set kittens
private Color color;
private char sex;
private float weight;
/**
* @hibernate.id
* generator-class="native"
* column="CAT_ID"
*/
public Long getId() {
return id;
}
private void setId(Long id) {
this.id=id;
}
/**
* @hibernate.many-to-one
* column="MATE_ID"
*/
public Cat getMate() {
return mate;
}
void setMate(Cat mate) {
this.mate = mate;
}
/**
* @hibernate.property
* column="BIRTH_DATE"
*/
public Date getBirthdate() {
return birthdate;
}
void setBirthdate(Date date) {
birthdate = date;
}
/**
* @hibernate.property
* column="WEIGHT"
*/
public float getWeight() {
return weight;
}
void setWeight(float weight) {
this.weight = weight;
}
/**
* @hibernate.property
* column="COLOR"
* not-null="true"
*/
public Color getColor() {
return color;
}
void setColor(Color color) {
this.color = color;
}
/**
* @hibernate.set
* lazy="true"
* order-by="BIRTH_DATE"
* @hibernate.collection-key
* column="PARENT_ID"
* @hibernate.collection-one-to-many
*/
public Set getKittens() {
return kittens;
}
void setKittens(Set kittens) {
this.kittens = kittens;
}
// addKitten not needed by Hibernate
public void addKitten(Cat kitten) {
kittens.add(kitten);
}
/**
* @hibernate.property
* column="SEX"
* not-null="true"
* update="false"
*/
public char getSex() {
return sex;
}
void setSex(char sex) {
this.sex=sex;
}
}]]></programlisting>
</sect1>
</chapter>