479 lines
18 KiB
XML
479 lines
18 KiB
XML
|
<chapter id="persistent-classes" revision="2">
|
||
|
<title>Clases Persistentes</title>
|
||
|
|
||
|
<para>
|
||
|
Clases presistentes son clases en una aplicación que implementan las
|
||
|
entidades del problema de negocio (por ejemplo, Customer y Order en una
|
||
|
aplicación de comercio electrónico). No todas las instancias de una
|
||
|
clase persistente se considera que estén en el estado persistente,
|
||
|
una instancia puede en cambio ser transitoria o estar separada.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Hibernate funciona mejor si las clases siguen algunas simples reglas, también
|
||
|
conocidas como el modelo de programación de Viejas Clases Java Planas
|
||
|
(Plain Old Java Object o POJO). Sin embargo, ninguna de estas reglas son
|
||
|
requerimientos rígidos. En cambio, Hibernate3 asume muy poco acerca de
|
||
|
la naturaleza de tus objetos persistentes. Puedes expresar un modelo de dominio en
|
||
|
otras formas: usando árboles de instancias de <literal>Map</literal>,
|
||
|
por ejemplo.
|
||
|
</para>
|
||
|
|
||
|
<sect1 id="persistent-classes-pojo">
|
||
|
<title>Un ejemplo simple de POJO</title>
|
||
|
|
||
|
<para>
|
||
|
La mayoría de aplicaciones Java requieren una clase
|
||
|
representando felinos.
|
||
|
</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>
|
||
|
Aquí hay cuatro reglas principales a seguir:
|
||
|
</para>
|
||
|
|
||
|
<sect2 id="persistent-classes-pojo-constructor" revision="1">
|
||
|
<title>Implementa un constructor sin argumentos</title>
|
||
|
|
||
|
<para>
|
||
|
<literal>Cat</literal> tiene un contructor sin argumentos. Todas las clases persistentes
|
||
|
deben tener un constructor por defecto (que puede no ser público) de modo que Hibernate
|
||
|
pueda instanciarlas usando <literal>Constructor.newInstance()</literal>. Recomendamos fuertemente tener
|
||
|
un constructor por defecto con al menos visibilidad de <emphasis>package</emphasis> para la
|
||
|
generación de proxies en tiempo de ejecución en Hibernate.
|
||
|
</para>
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="persistent-classes-pojo-identifier" revision="2">
|
||
|
<title>Provee una propiedad identificadora (opcional)</title>
|
||
|
|
||
|
<para>
|
||
|
<literal>Cat</literal> tiene una propiedad llamada <literal>id</literal>. Esta
|
||
|
propiedad mapea a la columna clave primaria de la tabla de base de datos. La propiedad
|
||
|
podría llamarse cualquierCosa, y su tipo podría haber sido cualquier tipo
|
||
|
primitivo, cualquier tipo de "envoltura" primitivo, <literal>java.lang.String</literal>
|
||
|
o <literal>java.util.Date</literal>. (Si tu tabla de base de datos heredada tiene claves
|
||
|
compuestas, puedes incluso usar una clase definida por el usuario con propiedades de
|
||
|
estos tipos, ver la sección sobre identificadores compuestos luego.)
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
La propiedad identificadora es estrictamente opcional. Puedes olvidarla y dejar que Hibernate
|
||
|
siga internamente la pista de los identificadores del objeto. Sin embargo, no recomendamos esto.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
De hecho, alguna funcionalidad está disponible sólo para clases que
|
||
|
declaran una propiedad identificadora:
|
||
|
</para>
|
||
|
|
||
|
<itemizedlist spacing="compact">
|
||
|
<listitem>
|
||
|
<para>
|
||
|
Reasociación transitiva de objetos separados (actualizaciones o
|
||
|
fusiones en cascada) - ver <xref linkend="objectstate-transitive"/>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
<literal>Session.saveOrUpdate()</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
<literal>Session.merge()</literal>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
|
||
|
<para>
|
||
|
Recomendamos que declares propiedades identificadoras nombradas-consistentemente
|
||
|
en clases persistentes. Mas aún, recomendamos que uses un tipo nulable
|
||
|
(es decir, no primitivo).
|
||
|
</para>
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="persistent-classes-pojo-final">
|
||
|
<title>Prefiere las clases no finales (opcional)</title>
|
||
|
<para>
|
||
|
Un aspecto central de Hibernate, <emphasis>proxies</emphasis>, depende de que
|
||
|
las clases persistentes sean ya no finales, o sean ya la implementación
|
||
|
de una interface que declare todos los métodos públicos.
|
||
|
</para>
|
||
|
<para>
|
||
|
Puedes persistir con Hibernate clases <literal>final</literal> que no implementen una
|
||
|
interface, pero no serás capaz de usar proxies para recuperación perezosa
|
||
|
de asociaciones, lo que limitará tus opciones para afinar el rendimiento.
|
||
|
</para>
|
||
|
<para>
|
||
|
Debes también evitar declarar métodos <literal>public final</literal>
|
||
|
en clases non-final. Si quieres usar una clase con un método <literal>public
|
||
|
final</literal>, debes deshabilitar explícitamente el uso de proxies estableciendo
|
||
|
<literal>lazy="false"</literal>.
|
||
|
</para>
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="persistent-classes-pojo-accessors" revision="2">
|
||
|
<title>Declara métodos de acceso y modificación para los campos persistentes (opcional)</title>
|
||
|
<para>
|
||
|
<literal>Cat</literal> declara métodos de acceso para todos sus campos persistente.
|
||
|
Muchas otras herramientas ORM persisten directamente variables de instancia. Creemos que
|
||
|
es mejor proveer una indirección entre el esquema relacional y las estructuras internas de la clase.
|
||
|
Por defecto, Hibernate persiste propiedades del estilo JavaBeans, y reconoce nombres de método
|
||
|
de la forma <literal>getFoo</literal>, <literal>isFoo</literal> y <literal>setFoo</literal>.
|
||
|
Puedes cambiar a acceso directo a campos para propiedades en particular, de ser necesario.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Las propiedades <emphasis>no</emphasis> necesitan ser declaradas públicas. Hibernate puede
|
||
|
persistir una propiedad con un par get / set <literal>protected</literal> o <literal>private</literal>.
|
||
|
</para>
|
||
|
</sect2>
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="persistent-classes-inheritance">
|
||
|
<title>Implementando herencia</title>
|
||
|
|
||
|
<para>
|
||
|
Una subclase puede a su vez observar la primera y segunda regla. Hereda su
|
||
|
propiedad identificadora de la superclase, <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>Implementando <literal>equals()</literal> y <literal>hashCode()</literal></title>
|
||
|
|
||
|
|
||
|
<para>
|
||
|
Tienes que sobrescribir los métodos <literal>equals()</literal> y <literal>hashCode()</literal>
|
||
|
si :
|
||
|
</para>
|
||
|
<itemizedlist spacing="compact">
|
||
|
<listitem>
|
||
|
<para>
|
||
|
piensas poner instancias de clases persistentes en un <literal>Set</literal>
|
||
|
(la forma recomendada de representar asociaciones multivaluadas)
|
||
|
<emphasis>y</emphasis>
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
piensas usar reasociación de instancias separadas.
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
|
||
|
<para>
|
||
|
Hibernate garantiza la equivalencia de identidad persistente (fila de base de datos) y
|
||
|
identidad Java sólo dentro del ámbito de una sesión en particular.
|
||
|
De modo que en el momento que mezclamos instancias recuperadas en sesiones diferentes,
|
||
|
debemos implementar <literal>equals()</literal> y <literal>hashCode()</literal> si
|
||
|
deseamos tener una semántica significativa de <literal>Set</literal>s.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
La forma más obvia es implementar <literal>equals()</literal>/<literal>hashCode()</literal>
|
||
|
comparando el valor identificador de ambos objetos. Si el valor es el mismo, ambos deben ser
|
||
|
la misma fila de base de datos, por lo tanto son iguales (si ambos son agregados a un
|
||
|
<literal>Set</literal>, sólo tendremos un elemento en el <literal>Set</literal>).
|
||
|
Desafortunadamente, no podemos usar este enfoque con identificadores generados! Hibernate sólo
|
||
|
asignará valores identificadores a objetos que son persistentes, una instancia recién
|
||
|
creada no tendrá ningún valor identificador! Además, si una instancia no está
|
||
|
salvada y está actualmente en un <literal>Set</literal>, salvarla asignará un
|
||
|
valor identificador al objeto. Si <literal>equals()</literal> and <literal>hashCode()</literal>
|
||
|
están basados en el valor identificador, el código hash podría cambiar,
|
||
|
rompiendo el contrato de <literal>Set</literal>. Ver el sitio web de Hibernate para una
|
||
|
discusión completa de este problema. Observa que esto no es una incidencia de Hibernate,
|
||
|
sino la semántica normal de Java de identidad de objeto e igualdad.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Recomendamos implementar <literal>equals()</literal> y <literal>hashCode()</literal>
|
||
|
usando <emphasis>igualdad de clave de negocio (Business key equality)</emphasis>.
|
||
|
Igualdad de clave de negocio significa que el método <literal>equals()</literal>
|
||
|
compara sólo las propiedades que forman la clave de negocio, una clave que podría
|
||
|
identificar nuestra instancia en el mundo real (una clave candidata
|
||
|
<emphasis>natural</emphasis>):
|
||
|
</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>
|
||
|
Nota que una clave de negocio no tiene que ser tan sólida como
|
||
|
una clave primaria candidata de base de datos (ver
|
||
|
<xref linkend="transactions-basics-identity"/>). Las propiedades inmutables o
|
||
|
únicas son usualmente buenas candidatas para una clave de negocio.
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="persistent-classes-dynamicmodels">
|
||
|
<title>Modelos dinámicos</title>
|
||
|
|
||
|
<para>
|
||
|
<emphasis>Ten en cuenta que las siguientes funcionalidades están
|
||
|
consideradas actualmente experimentales y pueden cambiar en el futuro
|
||
|
cercano.</emphasis>
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Las entidades persistentes no necesariamente tienen que estar representadas
|
||
|
como clases POJO o como objetos JavaBean en tiempo de ejecución. Hibernate
|
||
|
soporta además modelos dinámicos (usando <literal>Map</literal>s de
|
||
|
<literal>Map</literal>s en tiempo de ejecución) y la representación
|
||
|
de entidades como árboles de DOM4J. Con este enfoque no escribes clases
|
||
|
persistentes, sólo ficheros de mapeo.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Por defecto, Hibernate funciona en modo POJO normal. Puedes establecer una
|
||
|
representación de entidad por defecto para una <literal>SessionFactory</literal>
|
||
|
en particular usando la opción de configuración
|
||
|
<literal>default_entity_mode</literal>
|
||
|
(ver <xref linkend="configuration-optional-properties"/>).
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Los siguientes ejemplos demuestran la representación usando
|
||
|
<literal>Map</literal>s. Primero, en el fichero de mapeo,
|
||
|
tiene que declararse un <literal>entity-name</literal> en vez de
|
||
|
(o como agregado a) un nombre de clase:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<hibernate-mapping>
|
||
|
|
||
|
<class entity-name="Customer">
|
||
|
|
||
|
<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="organization"
|
||
|
column="ORGANIZATION_ID"
|
||
|
class="Organization"/>
|
||
|
|
||
|
<bag name="orders"
|
||
|
inverse="true"
|
||
|
lazy="false"
|
||
|
cascade="all">
|
||
|
<key column="CUSTOMER_ID"/>
|
||
|
<one-to-many class="Order"/>
|
||
|
</bag>
|
||
|
|
||
|
</class>
|
||
|
|
||
|
</hibernate-mapping>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Ten en cuenta que aunque las asociaciones se declaran usando nombres
|
||
|
de clase objetivo, el tipo objetivo de una asociación puede
|
||
|
ser además una entidad dinámica en vez de un POJO.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Después de establecer el modo de entidad por defecto a
|
||
|
<literal>dynamic-map</literal> para la <literal>SessionFactory</literal>,
|
||
|
podemos trabajar en tiempo de ejecución con <literal>Map</literal>s
|
||
|
de <literal>Map</literal>s:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Session s = openSession();
|
||
|
Transaction tx = s.beginTransaction();
|
||
|
Session s = openSession();
|
||
|
|
||
|
// Create a customer
|
||
|
Map david = new HashMap();
|
||
|
david.put("name", "David");
|
||
|
|
||
|
// Create an organization
|
||
|
Map foobar = new HashMap();
|
||
|
foobar.put("name", "Foobar Inc.");
|
||
|
|
||
|
// Link both
|
||
|
david.put("organization", foobar);
|
||
|
|
||
|
// Save both
|
||
|
s.save("Customer", david);
|
||
|
s.save("Organization", foobar);
|
||
|
|
||
|
tx.commit();
|
||
|
s.close();]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Las ventajas de un mapeo dinámico es rápido tiempo de ciclo
|
||
|
de prototipado sin la necesidad de implementación de clases de entidad.
|
||
|
Sin embargo, pierdes chequeo de tipos en tiempo de compilación y
|
||
|
muy probablemente tratarás con muchas excepciones en tiempo de ejecución.
|
||
|
Gracias al mapeo de Hibernate, el esquema de base de datos puede estar facilmente
|
||
|
sano y normalizado, permitiendo agregar una implementación apropiada del
|
||
|
modelo de dominio más tarde.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Los modos de representación de entidad pueden ser establecidos
|
||
|
por <literal>Session</literal>:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Session dynamicSession = pojoSession.getSession(EntityMode.MAP);
|
||
|
|
||
|
// Create a customer
|
||
|
Map david = new HashMap();
|
||
|
david.put("name", "David");
|
||
|
dynamicSession.save("Customer", david);
|
||
|
...
|
||
|
dynamicSession.flush();
|
||
|
dynamicSession.close()
|
||
|
...
|
||
|
// Continue on pojoSession
|
||
|
]]></programlisting>
|
||
|
|
||
|
|
||
|
<para>
|
||
|
Por favor, ten en cuenta que la llamada a <literal>getSession()</literal>
|
||
|
usando un <literal>EntityMode</literal> está en la API de
|
||
|
<literal>Session</literal>, no en la de <literal>SessionFactory</literal>.
|
||
|
De esta forma, la nueva <literal>Session</literal> comparte la conexión
|
||
|
JDBC, transacción y otra información de contexto. Esto significa
|
||
|
que no tienes que llamar a <literal>flush()</literal> ni a <literal>close()</literal>
|
||
|
en la <literal>Session</literal> secundaria, y tembién dejar el manejo
|
||
|
de la transacción y de la conexión a la unidad de trabajo primaria.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Puede encontrarse más información sobre las capacidades de
|
||
|
representación XML en <xref linkend="xml"/>.
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<para>
|
||
|
PORHACER: Documentar el framework de extensiones del usuario en los paquetes
|
||
|
de propiedad y proxies.
|
||
|
</para>
|
||
|
|
||
|
</chapter>
|
||
|
|