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

479 lines
18 KiB
XML

<chapter id="persistent-classes" revision="2">
<title>Clases Persistentes</title>
<para>
Clases presistentes son clases en una aplicaci&#x00f3;n que implementan las
entidades del problema de negocio (por ejemplo, Customer y Order en una
aplicaci&#x00f3;n de comercio electr&#x00f3;nico). No todas las instancias de una
clase persistente se considera que est&#x00e9;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&#x00e9;n
conocidas como el modelo de programaci&#x00f3;n de Viejas Clases Java Planas
(Plain Old Java Object o POJO). Sin embargo, ninguna de estas reglas son
requerimientos r&#x00ed;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 &#x00e1;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&#x00ed;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&#x00ed; 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&#x00fa;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&#x00f3;n de proxies en tiempo de ejecuci&#x00f3;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&#x00ed;a llamarse cualquierCosa, y su tipo podr&#x00ed;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&#x00f3;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&#x00e1; disponible s&#x00f3;lo para clases que
declaran una propiedad identificadora:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
Reasociaci&#x00f3;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&#x00fa;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&#x00f3;n
de una interface que declare todos los m&#x00e9;todos p&#x00fa;blicos.
</para>
<para>
Puedes persistir con Hibernate clases <literal>final</literal> que no implementen una
interface, pero no ser&#x00e1;s capaz de usar proxies para recuperaci&#x00f3;n perezosa
de asociaciones, lo que limitar&#x00e1; tus opciones para afinar el rendimiento.
</para>
<para>
Debes tambi&#x00e9;n evitar declarar m&#x00e9;todos <literal>public final</literal>
en clases non-final. Si quieres usar una clase con un m&#x00e9;todo <literal>public
final</literal>, debes deshabilitar expl&#x00ed;citamente el uso de proxies estableciendo
<literal>lazy="false"</literal>.
</para>
</sect2>
<sect2 id="persistent-classes-pojo-accessors" revision="2">
<title>Declara m&#x00e9;todos de acceso y modificaci&#x00f3;n para los campos persistentes (opcional)</title>
<para>
<literal>Cat</literal> declara m&#x00e9;todos de acceso para todos sus campos persistente.
Muchas otras herramientas ORM persisten directamente variables de instancia. Creemos que
es mejor proveer una indirecci&#x00f3;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&#x00e9;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&#x00fa;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&#x00e9;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&#x00f3;n de instancias separadas.
</para>
</listitem>
</itemizedlist>
<para>
Hibernate garantiza la equivalencia de identidad persistente (fila de base de datos) y
identidad Java s&#x00f3;lo dentro del &#x00e1;mbito de una sesi&#x00f3;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&#x00e1;ntica significativa de <literal>Set</literal>s.
</para>
<para>
La forma m&#x00e1;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&#x00f3;lo tendremos un elemento en el <literal>Set</literal>).
Desafortunadamente, no podemos usar este enfoque con identificadores generados! Hibernate s&#x00f3;lo
asignar&#x00e1; valores identificadores a objetos que son persistentes, una instancia reci&#x00e9;n
creada no tendr&#x00e1; ning&#x00fa;n valor identificador! Adem&#x00e1;s, si una instancia no est&#x00e1;
salvada y est&#x00e1; actualmente en un <literal>Set</literal>, salvarla asignar&#x00e1; un
valor identificador al objeto. Si <literal>equals()</literal> and <literal>hashCode()</literal>
est&#x00e1;n basados en el valor identificador, el c&#x00f3;digo hash podr&#x00ed;a cambiar,
rompiendo el contrato de <literal>Set</literal>. Ver el sitio web de Hibernate para una
discusi&#x00f3;n completa de este problema. Observa que esto no es una incidencia de Hibernate,
sino la sem&#x00e1;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&#x00e9;todo <literal>equals()</literal>
compara s&#x00f3;lo las propiedades que forman la clave de negocio, una clave que podr&#x00ed;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&#x00f3;lida como
una clave primaria candidata de base de datos (ver
<xref linkend="transactions-basics-identity"/>). Las propiedades inmutables o
&#x00fa;nicas son usualmente buenas candidatas para una clave de negocio.
</para>
</sect1>
<sect1 id="persistent-classes-dynamicmodels">
<title>Modelos din&#x00e1;micos</title>
<para>
<emphasis>Ten en cuenta que las siguientes funcionalidades est&#x00e1;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&#x00f3;n. Hibernate
soporta adem&#x00e1;s modelos din&#x00e1;micos (usando <literal>Map</literal>s de
<literal>Map</literal>s en tiempo de ejecuci&#x00f3;n) y la representaci&#x00f3;n
de entidades como &#x00e1;rboles de DOM4J. Con este enfoque no escribes clases
persistentes, s&#x00f3;lo ficheros de mapeo.
</para>
<para>
Por defecto, Hibernate funciona en modo POJO normal. Puedes establecer una
representaci&#x00f3;n de entidad por defecto para una <literal>SessionFactory</literal>
en particular usando la opci&#x00f3;n de configuraci&#x00f3;n
<literal>default_entity_mode</literal>
(ver <xref linkend="configuration-optional-properties"/>).
</para>
<para>
Los siguientes ejemplos demuestran la representaci&#x00f3;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&#x00f3;n puede
ser adem&#x00e1;s una entidad din&#x00e1;mica en vez de un POJO.
</para>
<para>
Despu&#x00e9;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&#x00f3;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&#x00e1;mico es r&#x00e1;pido tiempo de ciclo
de prototipado sin la necesidad de implementaci&#x00f3;n de clases de entidad.
Sin embargo, pierdes chequeo de tipos en tiempo de compilaci&#x00f3;n y
muy probablemente tratar&#x00e1;s con muchas excepciones en tiempo de ejecuci&#x00f3;n.
Gracias al mapeo de Hibernate, el esquema de base de datos puede estar facilmente
sano y normalizado, permitiendo agregar una implementaci&#x00f3;n apropiada del
modelo de dominio m&#x00e1;s tarde.
</para>
<para>
Los modos de representaci&#x00f3;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&#x00e1; 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&#x00f3;n
JDBC, transacci&#x00f3;n y otra informaci&#x00f3;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&#x00e9;n dejar el manejo
de la transacci&#x00f3;n y de la conexi&#x00f3;n a la unidad de trabajo primaria.
</para>
<para>
Puede encontrarse m&#x00e1;s informaci&#x00f3;n sobre las capacidades de
representaci&#x00f3;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>