lunes, 4 de junio de 2012
sábado, 2 de junio de 2012
miércoles, 30 de mayo de 2012
6.4 Manejo de objetos persistentes
Las clases
presistentes son clases en una aplicación que implementan las entidades del
problema empresarial (por ejemplo, Customer y Order en una aplicación de
comercio electrónico). No se considera que todas las instancias de una clase
persistente estén en estado persistente. Por ejemplo, una instancia puede ser
transitoria o separada.
Hibernate
funciona mejor si estas clases siguen algunas reglas simples, también conocidas
como el modelo de programación POJO (Plain Old Java Object). Sin embargo,
ninguna de estas reglas son requerimientos rígidos. De hecho, Hibernate3 asume
muy poco acerca de la naturaleza de sus objetos persistentes. Puede expresar un
modelo de dominio en otras formas (por ejemplo, utilizando árboles de instancias
de Map).
Ejemplo
simple de POJO
La
mayoría de aplicaciones Java requieren una clase persistente que represente a
los felinos. Por
ejemplo:
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;
}
public void addKitten(Cat kitten) {
kitten.setMother(this);
kitten.setLitterId( kittens.size() );
kittens.add(kitten);
}
}
En las
siguientes secciones vamos a explorar en mayor detalle las cuatro reglas
principales de las clases persistentes.
Implemente un constructor sin argumentos
Cat
tiene un contructor sin argumentos. Todas las clases persistentes deben tener un
constructor predeterminado (el cual puede ser no-público) de modo que Hibernate
pueda instanciarlas usando Constructor.newInstance(). Le recomendamos contar con
un constructor por defecto con al menos una visibilidad de paquete para
la generación de proxies en tiempo de ejecución en Hibernate.
Proporcione una propiedad identificadora (opcional)
Cat
tiene una propiedad llamada id. Esta propiedad mapea a la columna de la llave
principal de la tabla de la base de datos. La propiedad podría llamarse de
cualquier manera y su tipo podría haber sido cualquier tipo primitivo, cualquier
tipo de "wrapper" primitivo, java.lang.String o java.util.Date. Si su tabla de
base de datos heredada tiene claves compuestas, puede utilizar una clase
definida por el usuario con propiedades de estos tipos
La
propiedad identificadora es estrictamente opcional. Puede olvidarla y dejar que
Hibernate siga internamente la pista de los identificadores del objeto. Sin
embargo, no recomendamos que esto suceda.
De
hecho, algunas funcionalidades se encuentran disponibles sólamente para clases
que declaran una propiedad identificadora:
- Session.saveOrUpdate()
- Session.merge()
Le
recomendamos que declare propiedades identificadoras nombradas-consistentemente
en clases persistentes. y que utilice un tipo nulable (por ejemplo, no
primitivo).
Prefiera
las clases no finales
Un
aspecto central de Hibernate, los proxies, dependen de que las clases
persistentes sean no finales o de la implementación de una interfaz que declare
todos los métodos públicos.
Con
Hibernate puede persistir las clases finales que no implementen una interfaz.
Sin embargo, no podrá utilizar proxies para recuperación perezosa de
asociaciones, lo cual limitará sus opciones para afinar el rendimiento.
También
debe evitar el declarar métodos public final en las clases no-finales. Si quiere
utilizar una clase con un método public final, debe deshabilitar explícitamente
el uso de proxies estableciendo lazy="false".
Declare
métodos de acceso y de modificación para los campos
persistentes
Cat
declara métodos de acceso para todos sus campos persistentes. Muchas otras
herramientas ORM persisten directamente variables de instancia. Es mejor
proporcionar una indirección entre el esquema relacional y las estructuras de
datos internos de la clase. Por defecto, Hibernate persiste las propiedades del
estilo JavaBeans, y reconoce los nombres de método de la forma getFoo, isFoo y
setFoo. De ser necesario, puede cambiarse al acceso directo a campos para
propiedades específicas.
No es
necesario declarar públicas las propiedades. Hibernate puede persistir una
propiedad con un par get / set protected o private.
Implementación de herencia
Una
subclase también tiene que cumplir con la primera y la segunda regla. Hereda su
propiedad identificadora de la superclase Cat. Por ejemplo:
package eg;
public class DomesticCat extends Cat {
private String name;
public String getName() {
return name;
}
protected void setName(String name) {
this.name=name;
}
}
Implementando equals() y hashCode()
Tiene
que sobrescribir los métodos equals() y hashCode() si:
- piensa poner instancias de clases persistentes en un Set (la forma recomendada de representar asociaciones multivaluadas); y
- piensa utilizar reasociación de instancias separadas.
Hibernate garantiza la equivalencia de identidad persistente (fila de
base de datos) y de identidad Java sólamente dentro del ámbito de una sesión en
particular. De modo que en el momento en que mezcla instancias recuperadas en
sesiones diferentes, tiene que implementar equals() y hashCode() si desea tener
una semántica significativa para Sets.
La forma
más obvia es implementar equals()/hashCode() comparando el valor identificador
de ambos objetos. Si el valor es el mismo, ambos deben ser la misma fila de la
base de datos ya que son iguales. Si ambos son agregados a un Set, sólo
tendremos un elemento en el Set). Desafortunadamente, no puede utilizar 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 se encuentra
guardada y está actualmente en un Set, al guardarla se asignará un valor
identificador al objeto. Si equals() y hashCode() están basados en el valor
identificador, el código hash podría cambiar, rompiendo el contrato del Set.
Consulte el sitio web de Hibernate y allí encontrará una discusión completa
sobre este problema. Este no es un problema de Hibernate, sino de la semántica
normal de Java de identidad de objeto e igualdad.
Le
recomendamos implementar equals() y hashCode() utilizando igualdad de clave
empresarial (Business key equality). Igualdad de clave empresarial significa
que el método equals() sólamente compara las propiedades que forman la clave
empresarial. Esta es una clave que podría identificar nuestra instancia en el
mundo real (una clave candidata natural):
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;
}
}
Modelos
dinámicos
Las
entidades persistentes no necesariamente tienen que estar representadas como
clases POJO o como objetos JavaBean en tiempo de ejecución. Hibernate también
soporta modelos dinámicos (utilizando Mapeos de Mapeos en tiempo de ejecución) y
la representación de entidades como árboles de DOM4J. No escriba clases
persistentes con este enfoque, sólamente archivos de mapeo.
Los
siguientes ejemplos demuestran la representación utilizando Mapeos. Primero, en
el archivo de mapeo tiene que declararse un entity-name en lugar de, o además de
un nombre de clase:
<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
>
Aunque
las asociaciones se declaran utilizando nombres de clase destino, el tipo
destino de una asociación puede ser además una entidad dinámica en lugar de un
POJO.
Después
de establecer el modo de entidad predeterminado como dynamic-map para la
SessionFactory, puede trabajar en tiempo de ejecución con Mapeos de Mapeos:
Session s = openSession();
Transaction tx = s.beginTransaction();
// Crea un cliente
Map david = new HashMap();
david.put("name", "David");
// Crea una organizacion
Map foobar = new HashMap();
foobar.put("name", "Foobar Inc.");
// Liga ambos
david.put("organization", foobar);
// Guarda ambos
s.save("Customer", david);
s.save("Organization", foobar);
tx.commit();
s.close();
Una de
las ventajas principales de un mapeo dinámico es el rápido tiempo de entrega del
prototipado sin la necesidad de implementar clases de entidad. Sin embargo,
pierde el chequeo de tipos en tiempo de compilación y muy probablemente tendrá
que tratar con muchas excepciones en tiempo de ejecución. Gracias al mapeo de
Hibernate, el esquema de base de datos se puede normalizar y volver sólido,
permitiendo añadir una implementación apropiada del modelo de dominio más
adelante.
Los
modos de representación de entidad se pueden establecer por Session:
Session dynamicSession = pojoSession.getSession(EntityMode.MAP);
// Crea un cliente
Map david = new HashMap();
david.put("name", "David");
dynamicSession.save("Customer", david);
...
dynamicSession.flush();
dynamicSession.close()
...
// Continua en pojoSession
Tenga en
cuenta que la llamada a getSession() utilizando un EntityMode está en la API de
Session, no en la de SessionFactory. De esta forma, la nueva Session comparte la
conexión JDBC, la transacción y otra información de contexto. Esto significa que
no tiene que llamar a flush() ni a close() en la Session secundaria, y también
tiene que dejar el manejo de la transacción y de la conexión a la unidad de
trabajo primaria.
Tuplizers
org.hibernate.tuple.Tuplizer y sus subinterfaces son las responsables de
administrar una representación en particular de un dato, dadas las
representaciones de org.hibernate.EntityMode. Si un dato dado se considera como
una estructura de datos entonces un tuplizer es la cosa que sabe como crear tal
estructura de datos y sabe como extraer e insertar valores en dicha estructura
de datos. Por ejemplo, para el modo de entidad POJO, el tuplizer correspondiente
sabe como crear el POJO por medio de su constructor. También sabe como acceder a
las propiedades POJO utilizando los accesores de propiedad definidos.
Hay dos
tipos altos de niveles de Tuplizers, representados por las interfaces
org.hibernate.tuple.entity.EntityTuplizer y
org.hibernate.tuple.component.ComponentTuplizer. Los EntityTuplizers son los
responsables de administrar los contratos mencionados anteriormente en relación
con las entidades mientras que los ComponentTuplizers hacen lo mismo para los
componentes.
Los
usuarios también pueden enchufar sus propios tuplizers. Tal vez necesite que una
implementación java.util.Map diferente de java.util.HashMap se utilice en el
modo de entidad de mapeo dinámico. O quizás necesite definir una estrategia de
generación proxy diferente de la que se utiliza por defecto. Se pueden obtener
ambas al definir una implementación tuplizer personalizada. Las definiciones de
los tuplizers se encuentran sujetas a la entidad o componente de mapeo que se
supone que tienen que administrar. Regresando al ejemplo de nuestra entidad de
cliente:
<hibernate-mapping>
<class entity-name="Customer">
<!--
Override the dynamic-map entity-mode
tuplizer for the customer entity
-->
<tuplizer entity-mode="dynamic-map"
class="CustomMapTuplizerImpl"/>
<id name="id" type="long" column="ID">
<generator class="sequence"/>
</id>
<!-- other properties -->
...
</class>
</hibernate-mapping>
public class CustomMapTuplizerImpl extends org.hibernate.tuple.entity.DynamicMapEntityTuplizer{
// reemplaza
el método buildInstantiator () para conectar a nuestro mapa
personalizado…
protected final Instantiator buildInstantiator(
org.hibernate.mapping.PersistentClass mappingInfo) {
return new CustomMapInstantiator( mappingInfo );
}
private static final class CustomMapInstantiator
extends org.hibernate.tuple.DynamicMapInstantitor {
// reemplaza
el método generateMap() para regresar nuestro mapa personalizado...
protected final Map generateMap() {
return new CustomMap();
}
}
}
EntityNameResolvers
La
interfaz org.hibernate.EntityNameResolver es un contrato para resolver el nombre
de la entidad de una instancia de entidad dada. La interfaz define un solo
método resolveEntityName, el cual se le pasa la instancia entidad y se espera
que retorne el nombre de entidad apropriado (se permite nulo e indicaría que el
resolvedor no sabe cómo resolver el nombre de la entidad de la instancia de
entidad dada). Generalmente hablando, un org.hibernate.EntityNameResolver será
más útil en el caso de modelos dinámicos. Un ejemplo puede ser el usar
interfaces con proxis como su modelo de dominio. La suite de prueba de hibernate
tiene un ejemplo de este estilo exacto de uso bajo el
org.hibernate.test.dynamicentity.tuplizer2. Aquí está algo del código de ese
paquete para su ilustración.
public final class DataProxyHandler implements InvocationHandler {
private String entityName;
private HashMap data = new HashMap();
public DataProxyHandler(String entityName, Serializable id) {
this.entityName = entityName;
data.put( "Id", id );
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ( methodName.startsWith( "set" ) ) {
String propertyName = methodName.substring( 3 );
data.put( propertyName, args[0] );
}
else if ( methodName.startsWith( "get" ) ) {
String propertyName = methodName.substring( 3 );
return data.get( propertyName );
}
else if ( "toString".equals( methodName ) ) {
return entityName + "#" + data.get( "Id" );
}
else if ( "hashCode".equals( methodName ) ) {
return new Integer( this.hashCode() );
}
return null;
}
public String getEntityName() {
return entityName;
}
public HashMap getData() {
return data;
}
}
public class ProxyHelper {
public static String extractEntityName(Object object) {
if ( Proxy.isProxyClass( object.getClass() ) ) {
InvocationHandler handler = Proxy.getInvocationHandler( object );
if ( DataProxyHandler.class.isAssignableFrom( handler.getClass() ) ) {
DataProxyHandler myHandler = ( DataProxyHandler ) handler;
return myHandler.getEntityName();
}
}
return null;
}
// Otros metodos utiles....
}
public class MyEntityNameResolver implements EntityNameResolver {
public static final MyEntityNameResolver INSTANCE = new MyEntityNameResolver();
public String resolveEntityName(Object entity) {
return ProxyHelper.extractEntityName( entity );
}
public boolean equals(Object obj) {
return getClass().equals( obj.getClass() );
}
public int hashCode() {
return getClass().hashCode();
}
}
public class MyEntityTuplizer extends PojoEntityTuplizer {
public MyEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) {
super( entityMetamodel, mappedEntity );
}
public EntityNameResolver[] getEntityNameResolvers() {
return new EntityNameResolver[] { MyEntityNameResolver.INSTANCE };
}
public String determineConcreteSubclassEntityName(Object entityInstance, SessionFactoryImplementor factory) {
String entityName = ProxyHelper.extractEntityName( entityInstance );
if ( entityName == null ) {
entityName = super.determineConcreteSubclassEntityName( entityInstance, factory );
}
return entityName;
}
...
}
Suscribirse a:
Entradas (Atom)