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

391 lines
14 KiB
XML

<chapter id="persistent-classes" revision="2">
<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).
Not all instances of a persistent class are considered to be in the persistent
state - an instance may instead be transient or detached.
</para>
<para>
Hibernate works best if these classes follow some simple rules, also known
as the Plain Old Java Object (POJO) programming model. However none of these
rules are hard requirements. Indeed, Hibernate3 assumes very little about
the nature of your persistent objects. You may express a domain model in other
ways: using trees of <literal>Map</literal> instances, for example.
</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 Date birthdate;
private Color color;
private char sex;
private float weight;
private int litterId;
private Cat mother;
private Set kittens = new HashSet();
private void setId(Long id) {
this.id=id;
}
public Long getId() {
return id;
}
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 setSex(char sex) {
this.sex=sex;
}
public char getSex() {
return sex;
}
void setLitterId(int id) {
this.litterId = id;
}
public int getLitterId() {
return litterId;
}
void setMother(Cat mother) {
this.mother = mother;
}
public Cat getMother() {
return mother;
}
void setKittens(Set kittens) {
this.kittens = kittens;
}
public Set getKittens() {
return kittens;
}
// addKitten not needed by Hibernate
public void addKitten(Cat kitten) {
kitten.setMother(this);
kitten.setLitterId( kittens.size() );
kittens.add(kitten);
}
}]]></programlisting>
<para>
There are four main rules to follow here:
</para>
<sect2 id="persistent-classes-pojo-accessors" revision="1">
<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>. You may however switch to direct field access for
particular properties, if needed.
</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" revision="1">
<title>Implement a no-argument constructor</title>
<para>
<literal>Cat</literal> has a 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>.
We recommend having a constructor with at least <emphasis>package</emphasis>
visibility for runtime proxy generation in Hibernate.
</para>
</sect2>
<sect2 id="persistent-classes-pojo-identifier" revision="2">
<title>Provide an identifier property (optional)</title>
<para>
<literal>Cat</literal> has a property called <literal>id</literal>. This property
maps to 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 later.)
</para>
<para>
The identifier property is strictly optional. You can leave them off and let Hibernate
keep track of object identifiers internally. We do not recommend this, however.
</para>
<para>
In fact, some functionality is available only to classes which declare an
identifier property:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
Transitive reattachment for detached objects (cascade update or cascade
merge) - see <xref linkend="objectstate-transitive"/>
</para>
</listitem>
<listitem>
<para>
<literal>Session.saveOrUpdate()</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Session.merge()</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 for lazy association fetching -
which will limit your options for performance tuning.
</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 the superclass, <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" revision="1">
<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
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
intend to put instances of persistent classes in a <literal>Set</literal>
(the recommended way to represent many-valued associations)
<emphasis>and</emphasis>
</para>
</listitem>
<listitem>
<para>
intend to use reattachment of detached instances
</para>
</listitem>
</itemizedlist>
<para>
Hibernate guarantees equivalence of persistent identity (database row) and Java identity
only inside a particular session scope. So as soon as we mix instances retrieved in
different sessions, we must implement <literal>equals()</literal> and
<literal>hashCode()</literal> if we wish to have meaningful semantics for
<literal>Set</literal>s.
</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 with generated identifiers! Hibernate will only assign identifier values to objects
that are persistent, a newly created instance will not have any identifier value! Furthermore,
if an instance is unsaved and currently in a <literal>Set</literal>, saving it will assign
an identifier value to the object. If <literal>equals()</literal> and <literal>hashCode()</literal>
are based on the identifier value, the hash code would change, breaking the contract of the
<literal>Set</literal>. See the Hibernate website for a full discussion of this problem. Note
that this is not a Hibernate issue, but normal Java semantics of object identity and equality.
</para>
<para>
We recommend implementing <literal>equals()</literal> and <literal>hashCode()</literal>
using <emphasis>Business key equality</emphasis>. 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 ( !cat.getLitterId().equals( getLitterId() ) ) return false;
if ( !cat.getMother().equals( getMother() ) ) return false;
return true;
}
public int hashCode() {
int result;
result = getMother().hashCode();
result = 29 * result + getLitterId();
return result;
}
}]]></programlisting>
<para>
Note that a business key does not have to be as solid as a database
primary key candidate (see <xref linkend="transactions-basics-identity"/>).
Immutable or unique properties are usually good
candidates for a business key.
</para>
</sect1>
<sect1 id="persistent-classes-dynamic" revision="2">
<title>Dynamic models</title>
<para>
Hibernate also supports dynamic domain models, using <literal>Map</literal>s of
<literal>Map</literal>s. With this approach, you don't write persistent classes,
a Hibernate mapping file for each "entity" is sufficient:
</para>
<programlisting><![CDATA[<hibernate-mapping>
<class entity-name="TestMap">
<id name="id"
type="long"
column="ID">
<generator class="sequence"/>
</id>
<property name="name"
column="NAME"
type="string"/>
<property name="address"
column="ADDRESS"
type="string"/>
<many-to-one name="parent"
column="PARENT_ID"
class="TestMap"/>
<bag name="children"
inverse="true"
lazy="false"
cascade="all">
<key column="PARENT_ID"/>
<one-to-many class="TestMap"/>
</bag>
</class>
</hibernate-mapping>]]></programlisting>
<para>
At runtime, you just instantiate <literal>HashMap</literal>s and use
the Hibernate entity name to refer to a particular type:
</para>
<programlisting><![CDATA[Session s = openSession();
Transaction t = s.beginTransaction();
Map parent = new HashMap();
parent.put("name", "foo");
parent.put("address", "bar");
Map child = new HashMap();
child.put("name", "fooTwo");
child.put("address", "barTwo");
child.put("parent", parent);
s.save("TestMap", parent);
t.commit();
s.close();]]></programlisting>
<para>
The advantages of a dynamic mapping are quick turnaround time for prototyping
without the need for entity class implementation. However, you lose compile-time
type checking and will very likely deal with many exceptions at runtime. Thanks
to the Hibernate mapping, the database schema can easily be normalized and sound,
allowing to add a proper domain model implementation on top later on.
</para>
</sect1>
<para>
TODO: Document user-extension framework in the property and proxy packages
</para>
</chapter>