Descargar - Comunidad de Madrid

Anuncio
Anexo Hibernate
ANEXO: HIBERNATE
1.1
Introducción
Hibernate es un potente mapeador objeto/relacional y servicio de consultas para Java. Es la
solución ORM (Object-Relational Mapping) más popular en el mundo Java.
Hibernate permite desarrollar clases persistentes a partir de clases comunes, incluyendo
asociación, herencia, polimorfismo, composición y colecciones de objetos. El lenguaje de
consultas de Hibernate HQL (Hibernate Query Language), diseñado como una mínima
extensión orientada a objetos de SQL, proporciona un puente elegante entre los mundos de
objetos y relacional. Hibernate también permite expresar consultas utilizando SQL nativo o
consultas basadas en criterios.
Soporta todos los sistemas gestores de bases de datos SQL y se integra de manera elegante y
sin restricciones con los más populares servidores de aplicaciones J2EE y contenedores web, y
por supuesto también puede utilizarse en aplicaciones standalone.
Características clave:
o
Persistencia transparente: Hibernate puede operar proporcionando persistencia de
una manera transparente para el desarrollador.
o
Modelo de programación natural: Hibernate soporta el paradigma de orientación a
objetos de una manera natural: herencia, polimorfismo, composición y el framework de
colecciones de Java.
o
Soporte para modelos de objetos con una granularidad muy fina: Permite una
gran variedad de mapeos para colecciones y objetos dependientes.
o
Sin necesidad de mejorar el código compilado (bytecode): No es necesaria la
generación de código ni el procesamiento del bytecode en el proceso de compilación.
o
Escalabilidad extrema: Hibernate posee un alto rendimiento, tiene una caché de dos
niveles y puede ser usado en un cluster. Permite inicialización perezosa (lazy) de
objetos y colecciones.
o
Lenguaje de consultas HQL: Este lenguaje proporciona una independencia del SQL
de cada base de datos, tanto para el almacenamiento de objetos como para su
recuperación.
o
Soporte para transacciones de aplicación: Hibernate soporta transacciones largas
(aquellas que requieren la interacción con el usuario durante su ejecución) y gestiona la
política optimistic locking automáticamente.
o
Generación automática de claves primarias: Soporta los diversos tipos de
generación de identificadores que proporcionan los sistemas gestores de bases de
datos (secuencias, columnas autoincrementales,...) así como generación independiente
de la base de datos, incluyendo identificadores asignados por la aplicación o claves
compuestas.
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página: 1
Anexo Hibernate
Seguidamente se plantean unas normas unificadas de actuación adecuadas y buenas prácticas
a seguir para el desarrollo con Hibernate.
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página: 2
Anexo Hibernate
1.2
Herencia
El mapeo entre clases y tablas de la base de datos se suele realizar siguiendo la estrategia de
una tabla para cada clase, pero al aparecer la herencia esto deja de estar tan claro. También
surge otro problema al unir los mundos de sistemas orientados a objetos y bases de datos
relacionales, las bases de datos relacionales sólo tienen relaciones del tipo “tiene un”, mientras
que los sistemas orientados a objetos soportan relaciones de “es un” y “tiene un”.
Existen varias técnicas para abordar estos problemas:
1.2.1
o
Tabla por clase concreta.
o
Tabla por jerarquía de clases.
o
Tabla por cada subclase.
Tabla por clase concreta
Consiste en crear una tabla por cada clase no abstracta. Todas las propiedades de una clase,
incluidas las heredadas, se pueden mapear con columnas de la tabla.
<<tabla>>
COCHE
COCHE_ID –PK-TITULAR
RUEDAS
FECHA_VENTA
PLAZAS
PUERTAS
<<tabla>>
MOTO
MOTO_ID –PK-TITULAR
RUEDAS
FECHA_VENTA
TIPO_SIDECAR
TIPO_MANILLAR
figura 3.1
Para el ejemplo de la figura 3.1.1 se puede ver que la herencia no queda reflejada de forma
exacta en el modelo relacional. La clase VEHICULO tiene una relación con la tabla de usuarios
mediante la clave titular. El sistema de clase por tabla provoca que se tenga que duplicar la
clave externa TITULAR en la tabla COCHE y en MOTO. También habrá problemas a la hora de
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página: 3
Anexo Hibernate
hacer consultas polimórficas, que son aquellas que devuelven objetos de todas las clases que
cumplen el interfaz de la clase pedida. Estos problemas vendrán dados porque una consulta
por la clase padre dará como resultado la ejecución de varias sentencias SQL, una por cada
clase hija. Si se pregunta por la clase VEHÍCULO con un número de ruedas determinado se
realizarán las siguientes consultas:
select COCHE_ID, TITULAR, RUEDAS, FECHA_VENTA, PLAZAS, PUERTAS
from COCHE
where RUEDAS = ?
select MOTO_ID,
TIPO_MANILLAR
from MOTO
where RUEDAS = ?
TITULAR,
RUEDAS,
FECHA_VENTA,
TIPO_SIDECAR,
Otra pega viene dada porque al modificar una superclase modificando un campo habrá que
modificar todas las tablas correspondientes al mapeo de las clases hijas.
Para realizar en Hibernate los mapeos correspondientes a esta estrategia, sólo habrá que crear
una nueva <class> por cada clase concreta.
1.2.2
Tabla por jerarquía de clases
Esta opción mapea toda una jerarquía de clases a una sola tabla. La tabla creada debe
contener todas las propiedades de las clases que forman la jerarquía, más otra columna que
diferencia a qué subclase representa cada registro (discriminante).
Esta alternativa es la mejor a la hora de rendimiento y simplicidad. Contemplando el
rendimiento, es la mejor forma de representar la herencia. Tanto las peticiones polimórficas,
como las que no lo son, se ejecutan correctamente. La pega de este sistema radica en que las
columnas correspondientes a las propiedades de las subclases deben admitir el valor null. Si
alguna de las subclases requiere propiedades que sean NOT NULL entonces habría un
problema de integridad de datos.
<<tabla>>
VEHICULO
VEHICULO_ID –PK-TIPO_VEHICULO –
discriminante—
TITULAR
RUEDAS
FECHA_VENTA
PLAZAS
PUERTAS
TIPO_SIDECAR
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página: 4
Anexo Hibernate
figura 3.2
En Hibernate se emplea el elemento <subclass> para ejecutar esta técnica.
<hibernate-mapping>
<!—Mapeo de la SuperClase con la tabla de base de datos -->
<class name="Vehiculo" table="VEHICULO" discriminator-value="VE">
<id name="id" column="VEHICULO_ID" type="long">
<generator class="native"/>
</id>
<!—Discriminador que diferencia la subclase del objeto -->
<discriminator column="TIPO_ VEHICULO" type="string"/>
<!—Resto de las propiedades de la superclase -->
<property name="name" column="TITULAR" type="string"/>
...
<!—Subclase Coche con sus propiedades -->
<subclass name="Coche" discriminator-value="CO">
<property name="plazas" column="PLAZAS"/>
<property name="puertas" column="PUERTAS"/>
</subclass>
<!—Subclase Moto con sus propiedades -->
<subclass name="Moto" discriminator-value="MO">
<property name="tipoSidecar" column="TIPO_SIDECAR"/>
<property name="tipoManillar" column="TIPO_MANILLAR"/>
</subclass>
...
</class>
</hibernate-mapping>
Ahora la consulta polimórfica por la superclase Vehículo quedaría de esta forma:
select VEHICULO_ID, TIPO_ VEHICULO,
TITULAR, RUEDAS,FECHA_VENTA, PLAZAS, PUERTAS, TIPO_SIDECAR, TIPO_MANILLAR
from VEHICULO
where RUEDAS = ?
Como se puede apreciar únicamente se hace una consulta frente a las dos de la aproximación
Tabla por clase.
Si se realizara una consulta para la subclase Coche, Hibernate emplearía la condición ‘CO’ en
el discriminante:
select VEHICULO_ID, TIPO_ VEHICULO,
TITULAR, RUEDAS,FECHA_VENTA, PLAZAS, PUERTAS, TIPO_SIDECAR, TIPO_MANILLAR
from VEHICULO
where TIPO_VEHICULO = ‘CO’ and RUEDAS = ?
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página: 5
Anexo Hibernate
1.2.3
Tabla por cada subclase
En esta opción se usa una tabla por subclase. Con esta aproximación la herencia se
representa por medio de claves externas. Todas las subclases con propiedades incluidas
clases abstractas e incluso interfaces tienen su propia tabla.
Cada tabla sólo recoge las propiedades no heredadas de esa clase. La clave primaria de la
superclase será la misma que la de la subclase.
<<tabla>>
VEHICULO
VEHICULO_ID –PKTITULAR
RUEDAS
FECHA_VENTA
<<tabla>>
Coche
<<tabla>>
MOTO
COCHE_ID -PK- FKPLAZAS
MOTO_ID -PK- -FKTIPO_SIDECAR
TIPO_MANILLAR
figura 3.3
Entre las ventajas de esta opción cabe destacar que el modelo relacional está normalizado.
Este modelo permite que la herencia se represente mediante una clave externa, que es la
posibilidad natural que ofrece el mundo relacional para identificar las relaciones entre tablas.
En Hibernate se emplea el elemento <joined-subclass> :
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página: 6
Anexo Hibernate
<?xml version="1.0"?>
<hibernate-mapping>
<!—Mapeo de la superclase Vehiculo con la tabla VEHICULO -->
<class name="Vehiculo" table="VEHICULO">
<id name="id" column="VEHICULO_ID" type="long">
<generator class="native"/>
</id>
<!—Propiedades de la superclase Vehiculo -->
<property name="titular" column="TITULAR" type="string"/>
...
<!—Subclase Coche hija de Vehículo mapeada a la tabla COCHE-->
<joined-subclass name="Coche" table="COCHE">
<!—Clave primaria de la tabla COCHE Y externa de VEHICULO -->
<key column="COCHE_ID">
<!—Resto De propiedades de la clase COCHE -->
<property name="plazas" column="PLAZAS"/>
...
</joined-subclass>
<!—Aquí vendrían los datos de la clase MOTO -->
...
</class>
</hibernate-mapping>
Ahora la consulta polimórfica por la superclase Vehículo quedará de esta forma:
Select VE.VEHICULO_ID,VE.TITULAR, VE.RUEDAS, VE.FECHA_VENTA, CO.PLAZAS,
CO.PUERTAS, MO.TIPO_SIDECAR, MO.TIPO_MANILLAR
case
when CO.COCHE_ID is not null then 1
when MO.MOTO_ID is not null then 2
when VE.VEHICULO_ID is not null then 0
end as TYPE
from VEHICULO VE
left join COCHE CO on VE.VEHICULO_ID = CO.COCHE_ID
left join MOTO MO on VE.VEHICULO_ID = MO.MOTO_ID
where VE.RUEDAS = ?
Si la consulta se realiza sobre la clase Coche resultaría así:
select BD.BILLING_DETAILS_ID, BD.OWNER, BD.CREATED, CC.TYPE, ...
from CREDIT_CARD CC
inner join BILLING_DETAILS BD on
BD.BILLING_DETAILS_ID = CC.CREDIT_CARD_ID
where CC.CREATED = ?
Como se puede ver en este segundo caso usa inner join en lugar de outer join.
Una pega de la técnica tabla por cada subclase es que más difícil de implementar a mano. Si
se van a intercalar peticiones SQL escritas por el programador habrá que tenerlo en cuenta.
También hay que recordar que se puede crear una vista que simplifique esta aproximación
dejándola en el modelo tabla por jerarquía.
Se ha comprobado que el uso de esta técnica para jerarquías muy complejas afecta al
rendimiento.
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página: 7
Anexo Hibernate
Lo normal suele ser alternar entre las diferentes técnicas.
1.3
Relaciones
Las relaciones son asociaciones entre diversas tablas en el mundo relacional. Hay varios tipos:
1-1, n-1, 1-n y n-m.
En general la regla de oro en lo que a relaciones se refiere es evitar relaciones complejas, son
muy raros los casos en los que se necesita de relaciones many-to-many e hibernate solo ha
implementado estas relaciones por compatibilidad con posibles aplicaciones con un modelo de
datos mal diseñado y heredado.
1.3.1
Relaciones <one-to-one>
Este tipo de relación se puede registrar de varias formas: con el enfoque de asociación de
clave primaria y con el enfoque de asociación de clave externa.
La opción de clave primaria implica que ambas tablas comparten la misma clave primaria. La
clave primaria de una tabla es la clave externa de la otra. Si se desea navegar en los dos
sentidos de la relación se deben emplear en ambos mapeos el elemento <one-to-one>:
<<tabla>>
VEHICULO
VEHICULO_ID –PK-TITULAR
RUEDAS
FECHA_VENTA
<<tabla>>
FACTURA
1
1
FACTURA_ID -PK- -FKTOTAL
NUM_FACTURA
figura 3.4
Para mapear el Vehículo con la factura se añade en la <class name=”Vehiculo”…>:
<one-to-one name="factura"
class="Factura"
cascade="save-update"/>
Ahora se mapea la factura con el vehículo:
<one-to-one name="vehiculo"
class="Vehiculo"
constrained="true"/>
Con constrained=”true” se le dice a Hibernate que la clave primaria de FACTURA es también
clave externa y se refiere a la clave primaria de la tabla VEHICULO.
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página: 8
Anexo Hibernate
Para asegurar que las el guardado de las instancias de Factura tienen el mismo identificador
que el vehículo al que pertenecen se emplea el <generador class=”foreign”>:
<class name="Factura" table="FACTURA">
<id name="id" column="FACTURA_ID">
<generator class="foreign">
<param name="property">vehiculo</param>
</generator>
</id>
...
<one-to-one name="vehiculo" class="Vehiculo" constrained="true"/>
</class>
La etiqueta <generator> contiene <param name="property">vehiculo</param> que indica que
la clave primaria de FACTURA será la que se asocie mediante una relación (en este caso
vehiculo) definida como <one-to-one name="vehiculo" class="Vehiculo" constrained="true"/>.
El generador foreign inspecciona el objeto Vehículo asociado y emplea su identificador como
identificador del nuevo objeto Factura.
Existe otra alternativa para definir relaciones de uno a uno empleando una asociación de clave
externa. Esta opción implica usar un mapeo <many-to-one> marcando la clave externa como
unique=”true”. En el ejemplo se añadirá un FACTURA_VEHICULO_ID a la tabla VEHICULO
quedando de esta manera:
<<tabla>>
VEHICULO
VEHICULO_ID –PK—
FACTURA_VEHICULO_ID –
FKTITULAR
RUEDAS
FECHA_VENTA
<<tabla>>
FACTURA
1
1
FACTURA_ID -PKTOTAL
NUM_FACTURA
figura 3.5
Se añade la columna de clave externa FACTURA_VEHICULO_ID en el mapeo de la clase
Vehiculo:
<many-to-one name="facturaVehiculo"
class="Factura"
column="FACTURA_VEHICULO_ID"
cascade="all"
unique="true"/>
El parámetro unique=”true” es el que obliga a que sólo haya un vehículo por factura.
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página: 9
Anexo Hibernate
Para hacer la relación navegable desde una Factura a un Vehículo se añade al mapeo de
Factura:
<one-to-one name="vehiculo"
class="Vehiculo"
property-ref="facturaVehiculo"/>
De esta manera Hibernate sabe que la asociación vehiculo en Factura es el sentido inverso de
la asociación facturaVehiculo en Vehiculo.
1.3.2
Relaciones <many-to-one>
Para explicar esta relación se plantea la relación de ejemplo de un vehículo que tiene ventanas.
La relación será muy similar a la alternativa de relación uno a uno con clave externa,
mencionada anteriormente, quitando la propiedad unique.
<<tabla>>
VENTANA
<<tabla>>
VEHICULO
VEHICULO_ID –PK—
TITULAR
RUEDAS
FECHA_VENTA
1
N VENTANA_ID -PKVEHICULO_ID –FKPOSICION
DIMENSION
TIPO
figura 3.5
En los objetos de la clase Ventana habrá una referencia al objeto padre de la clase Vehículo,
quedando de esta forma:
public class Ventana {
...
private Vehiculo vehiculo;
public void setVehiculo(Vehiculo vehiculo) {
this. vehiculo = vehiculo;
}
public Item getVehiculo () {
return vehiculo;
}
En Hibernate se mapeará así:
<class name="Ventana" table="VENTANA">
...
<many-to-one
name="vehiculo"
class="Vehiculo" not-null="true"/>
</class>
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
column="VEHICULO_ID"
Página:
10
Anexo Hibernate
La columna VEHICULO_ID de la tabla VENTANA es la clave externa que relaciona con la tabla
VEHICULO en su clave primaria.
El resultado nos da una relación navegable desde la clase Ventana a la clase Vehículo.
1.3.3
Relaciones <one-to-many>
Para seguir con el ejemplo anterior se va a hacer navegable la relación desde Vehículo hacia
Ventana, con lo que se explicará la relación <one-to-many>.
La relación entre las tablas es la de la figura 3.5.
El código en la clase Vehiculo quedaría así:
public class Vehiculo {
...
private List ventanas = new ArrayList();
public void setVentanas(List ventanas) {
this.ventanas = ventanas;
}
public List getVentanas() {
return bids;
}
public void addVentana(Ventana ventana) {
ventana.setVehiculo(this);
ventanas.add(ventana);
}
...
}
Se define la lista de objetos ventana que pertenecen al vehículo.
En Hibernate se mapearía como sigue:
<class name="Vehiculo" table="VEHICULO">
...
<list name="ventanas" inverse="true">
<key column="VEHICULO_ID"/>
<one-to-many class="Ventana"/>
</list>
</class>
El elemento <key> define la columna que es clave externa de la tabla asociada VENTANA.
El atributo inverse=”true” indica a Hibernate que es el fin de la relación entre las dos tablas.
1.3.4
Relaciones <many-to-many>
Para explicar este tipo de relación se empleará la asociación entre vehículos que tienen extras
y extras que pueden estar en varios vehículos.
Las tablas resultantes de esta relación quedarían así:
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página:
11
Anexo Hibernate
N
<<tabla>>
VEHICULO_EXTRA
N
VEHICULO_ID –PK –FKEXTRA_ID -PK- -FK1
1
<<tabla>>
VEHICULO
<<tabla>>
EXTRA
VEHICULO_ID -PKTITULAR
RUEDAS
FECHA_VENTA
EXTRA_ID -PKDESCRIPCION
PRECIO
figura 3.6
Esto se mapearía como sigue para la clase Extra:
<class name="Extra" table="EXTRA">
...
<list name="vehiculos" table="VEHICULO_EXTRA">
<key column="EXTRA_ID" />
<many-to-many column="VEHICULO_ID" class="Vehiculo" />
</list>
</class>
El mapeo correspondiente para la clase Vehículo:
<class name="Vehiculo" table="VEHICULO">
...
<list name="extras" table="VEHICULO_EXTRA" inverse="true">
<key column="VEHICULO_ID" />
<many-to-many column="EXTRA_ID" class="Extra" />
</list>
</class>
De nuevo vuelve a aparecer el atributo inverse=”true” que marca el fin de la relación.
1.4
Lazy
Hibernate proporciona la posibilidad de realizar carga lenta bajo demanda. Consiste en que los
objetos se cargan cuando se accede a ellos. Se realiza un acceso a la base de datos cuando
se accede al objeto a no ser que esté cacheado. En los ejemplos anteriores si se carga un
vehículo lo normal es que no se carguen de un golpe todos sus extras y relaciones varias. La
técnica más usual consiste en declarar las relaciones como lazy, puede mejorar el rendimiento
notablemente, pero en ocasiones se puede acompañar de batched lazy. Con batched lazy se
cargan un número de objetos de la relación al cargar la entidad principal. En el ejemplo de
vehículo se podría cargar el vehículo y sus primeros 5 extras en un solo acceso a la base de
datos. Así reducimos el típico problema de select n+1. El rendimiento se vería muy afectado si
en una relación declarada como lazy se intentase acceder a un número de objetos elevado ya
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página:
12
Anexo Hibernate
que requeriría un acceso a la base de datos por cada objeto accedido. Aquí es donde entra el
batched lazy reduciendo el número de accesos.
Hay que tener en cuenta que las técnicas de recuperación de datos se pueden sobrescribir en
ejecución, permitiendo así la elección de una mejor estrategia que la reflejada en los archivos
de mapeo.
Para declarar que una lista se obtendrá con carga lazy se añadirá en el fichero de mapeo:
<list name="miLista" lazy="true" ...>
También se puede definir una clase como lazy:
<class name="Vehiculo" lazy="true" ...>
Para indicar una carga lazy, pero recuperando un número de registros fijo en cada acceso:
<class name="Vehiculo" lazy="true" batch-size="3" ...>
Si se desea una carga lazy batch de listas se hará así:
<list name="miLista" lazy="true" batch-size="3" ...>
Se cargarán 3 listas de tipo miLista, no 3 elementos de “miLista”.
Hay que tener en cuenta que para poder obtener elementos que son cargados con lazy=”false”
hay que mantener la conexión con la base de datos abierta, por este motivo cuando es
necesario pasar un objeto y aquellos que dependen de el (por ejemplo un coche y sus extras) a
otras clases hay que asegurarse de pasar también como parámetro la sesión o cargarlos
manualmente llamando simplemente a los métodos get del objeto antes de cerrar la sesión,
puede ser interesante el uso de lazy=”false” para evitar tener que realizar esta práctica en
aquellos objetos que sean muy propensos a este tipo de situaciones pero teniendo sumo
cuidado de no provocar cadenas interminables de objetos que cargan a otros objetos que
cargan a otros objetos … pues el rendimiento de la aplicación se podría ver seriamente
afectado.
1.5
Ejemplos de uso: SAVE, STORE, DELETE y FIND
Antes de mostrar cómo se realizan las operaciones en un entorno controlado hay que recordar
los puntos comunes de las operaciones que implican modificaciones en la base de datos. Las
transacciones en HIBERNATE se realizan del siguiente modo:
1) Se obtiene la sesión sobre la que se realizarán las operaciones.
2) Se abre la transacción dentro de un entorno try-catch-finally.
3) Se realizan las operaciones pertinentes sobre los objetos.
4) Se hace el commit de la transacción.
5) En el catch se añade el rollback de la transacción por si algo falla.
6) En el finally se cierra la conexión, para que se haga siempre (tanto si
va bien, como si falla).
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página:
13
Anexo Hibernate
También es recomendable que se creen en una clase auxiliar unos métodos para encapsular
tanto el comienzo de la transacción, como el commit y el rollback de la misma, para dejar el
código más claro en los métodos que los empleen.
Hay que tener en cuenta que la sentencia saveOrUpdate actualizará o insertará datos según la
política de unsaved-value que sigamos, podemos configurar los mapeos de hibernate para que
se comporte de tres maneras diferentes cuando se ejecuta un saveOrUpdate de un objeto
según el valor de su Id.
1.5.1

La primera aproximación y la que hibernate toma por defecto es unsavedvalue=”null” cuando realicemos un saveOrUpdate de un objeto cuyo Id se nulo
hibernate intentará insertar un nuevo registro en la BD.

La segunda posibilidad es unsaved-value=”any”, cuando se ejecute el update
comprueba si se ha actualizado algún registro, en caso contrario intentará
insertarlo en la BD, usar esta opción es peligroso pues puede crear registros
duplicados (con un id distinto) en la BD.

La tercera posibilidad es unsaved-value=”none”, hibernate nunca insertará
registros con el método saveOrUpdate, es una buena opción si se desea forzar
el uso específico de save para insertar nuevos registros aunque se pierde
cierta flexibilidad.
Ejemplos de uso: SAVE
En este ejemplo se muestra una forma de guardar un objeto.
public Serializable onSave(Serializable data) throws MiException{
Session session = null;
Transaction transaction = null;
try{
//Abrir transacción
session = HibernateSessionFactory.openSession();
transaction = session.beginTransaction();
//Guardado de datos
session.save(data);
//commit Transacción
transaction.commit();
return data;
} catch(Exception e){
try {
transaction.rollback();
} catch (HibernateException e1) {
// Tratamiento de la excepción
throw new MiException("codigoError", e1);
}
// Tratamiento de la excepción
throw new MiException("codigoError", e);
}
finally{
try {
sesion.close();
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página:
14
Anexo Hibernate
sesion = null;
}
catch (HibernateException e) {
// Tratamiento de la excepción
throw new MiException("codigoError", e1);
}
}
}
Método save que guarda datos de una colección o de otro tipo de objeto:
public void save(Object o) throws MiException{
try {
if (o instanceof Collection) {
Iterator iter = ((Collection) o).iterator();
while (iter.hasNext()) {
save(iter.next());
}
}else{
session.save(o);
}
}
catch (HibernateException e) {
// Tratamiento de la excepción
throw new MiException("codigoError", e);
}
}
1.5.2
Ejemplos de uso: STORE
Uso de STORE:
public Serializable onStore(Serializable data) throws MiException{
Session session = null;
try{
//Abrir transacción
session = HibernateSessionFactory.openSession();
session.beginTransaction();
//Guardado de datos
session.saveOrUpdate(data);
//commit transacción
transaction.commit();
return data;
}
catch(Exception e){
try {
transaction.rollback();
} catch (HibernateException e1) {
// Tratamiento de la excepción
throw new MiException("codigoError", e1);
}
// Tratamiento de la excepción
throw new MiException("codigoError", e);
}
finally{
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página:
15
Anexo Hibernate
try {
sesion.close();
sesion = null;
}
catch (HibernateException e) {
// tratamiento de la excepción
throw new MiException("codigoError", e);
}
}
}
public void saveOrUpdate(Object o) throws MiException{
try{
if (o instanceof Collection) {
Iterator iter = ((Collection) o).iterator();
while (iter.hasNext()) {
saveOrUpdate(iter.next());
}
}else{
session.saveOrUpdate(o);
}
}
catch(HibernateException he){
// Tratamiento de la excepción
throw new MiException("codigoError", he);
}
}
1.5.3
Ejemplos de uso: DELETE
protected void onDelete(Serializable data) throws MiException{
Session session = null;
try{
//Abrir transacción
session = HibernateSessionFactory.openSession();
session.beginTransaction();
session.delete(data);
sesion.flush();
transaction.commit();
}
catch(Exception e){
try {
transaction.rollback();
} catch (HibernateException e1) {
//
throw new MiException("codigoError", e1);
}
//
throw new MiException("codigoError", e);
}
finally{
try {
sesion.close();
sesion = null;
}
catch (HibernateException e) {
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página:
16
Anexo Hibernate
// Tratamiento de la excepción
throw new MiException("codigoError", e1);
}
}
}
public void delete(Object o) throws MiException{
try {
session.delete( o );
}
catch (HibernateException he) {
// Tratamiento de la excepción
throw new MiException("codigoError", he);
}
}
El método delete(Object o) admite el uso de una sentencia HQL en lugar del objeto a borrar,
hibernate borrará todos los objetos que cumplan las condiciones de nuestra HQL, esta práctica
debe usarse con comedimiento por el riesgo que implica, si lo que deseamos es borrar un
determinado Objeto la opción más fiable y optima sin duda es:
Object o = session.load(id, clazz.class);
If (o != null)
Session.delete(o);
...
1.5.4
Ejemplos de uso: FIND
En este ejemplo hay que mencionar que existen varias formas de hacer queries en Hibernate:
 Con HQL:
session.createQuery("from Vehiculo v where v.ruedas = 4");

API Criteria:
session.createCriteria(Vehiculo.class).add(
new Integer(4)) );

Expression.eq("ruedas",
SQL nativo:
session.createSQLQuery("select {v.*} from VEHICULO {v} where RUEDAS =
4", "v", Vehiculo.class);
En el siguiente ejemplo se empleará el API Criteria:
protected Serializable findVehiculosPorNumRuedas(Integer numRuedas)
throws MiException{
Session session = null;
try{
//Búsqueda de los datos
session = HibernateSessionFactory.openSession();
List list = session.createCriteria(Vehiculo.class)
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página:
17
Anexo Hibernate
.add(Expression.eq(“ruedas”, numRuedas))
.list();
//return result
return (Serializable)list;
}
catch(Exception e){
/* Tratamiento de la excepción, se puede lanzar la
* excepción del framework.
*/
throw new MiException("codigoError", e);
}
finally{
session.close();
}
}
1.6
Uso de campos BLOB y CLOB con Oracle 9i
Hibernate soporta el uso de campos BLOB y CLOB de forma nativa mediante mapeos a
propiedades tipo java.sql.blob / clob en las clases de persistencia pero existen algunas cosas
que hay que tener en cuenta a la hora de trabajar con ellos.
La primera y más importante es que Hibernate maneja un puntero a dichos campos y no el
contenido. Para manejar la información de dichos campos hemos de apoyarnos en los métodos
que contienen las clases java.sql.clob y blob.
Dado que el contenido no es cargado en memoria, es necesario mantener la sesión de
Hibernate abierta hasta recuperar dicho contenido, al igual que nos sucede en inicializaciones
lazy de las colecciones.
Existe un problema en el driver JDBC de Oracle 9 que no permite guardar blobs o clobs de más
de 4000 bytes ó caracteres, para evitar dicho problema debemos seguir estos pasos:
o
Crear un blob o clob con menos contenido, por ejemplo con un espacio en blanco.
o
Salvar nuestro objeto de persistencia.
o
Volver a cargar la referencia al blob o clob con el método flush de la sesión.
o
Obtener nuestro campo blob o clob para ser actualizado.
o
Por último escribir el contenido.
s = sf.openSession();
tx = s.beginTransaction();
foo = new Foo();
foo.setClob( Hibernate.createClob(" ") );
s.save(foo);
s.flush();
s.refresh(foo, LockMode.UPGRADE); //grabs an Oracle CLOB
oracle.sql.CLOB clob = (oracle.sql.CLOB) foo.getClob();
java.io.Writer pw = clob.getCharacterOutputStream();
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página:
18
Anexo Hibernate
pw.write(content);
pw.close();
tx.commit();
s.close();
Hemos de tener en cuenta que cuando actualizamos contenido de un clob o blob, estamos
actualizado directamente el contenido almacenado en nuestra base de datos.
Existe otra aproximación para resolver estos problemas, carga el contenido de nuestro blob o
clob en memoria (en una propiedad de tipo byte[] o char[] respectivamente).
Para ello hay que crear un nuevo tipo de datos de hibernate, para ello extenderemos nuestra
clase de UserType de hiberanate como muestra el siguiente ejemplo:
public class BinaryBlobType implements UserType
{
public int[] sqlTypes() {
return new int[] { Types.BLOB };
}
public Class returnedClass() {
return byte[].class;
}
public boolean equals(Object x, Object y) {
return (x == y)
|| (x != null
&& y != null
&& java.util.Arrays.equals((byte[]) x, (byte[]) y));
}
public Object nullSafeGet(ResultSet rs, String[] names, Object
owner)
throws HibernateException, SQLException {
Blob blob = rs.getBlob(names[0]);
return blob.getBytes(1, (int) blob.length());
}
public void nullSafeSet(PreparedStatement st, Object value,
int index)
throws HibernateException, SQLException {
st.setBlob(index, Hibernate.createBlob((byte[]) value));
}
public Object deepCopy(Object value) {
if (value == null) return null;
byte[] bytes = (byte[]) value;
byte[] result = new byte[bytes.length];
System.arraycopy(bytes, 0, result, 0, bytes.length);
return result;
}
public boolean isMutable() {
return true;
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página:
19
Anexo Hibernate
}
}
Ahora podemos usar este tipo de datos en nuestros mapeos:
<property
name="nombrePropiedad"
column="nombreColumna"
type="mypackage.BinaryBlobType"
/>
El problema de ésta segunda aproximación es la carga en memoria de gran cantidad de
información, a veces innecesariamente.
1.7
Generación de IDs mediante secuencias
Las clases de persistencia de Hibernate deben incorporar un identificador único de forma
obligatoria, este identificador puede definirse de diversas formas lo que nos permite adaptarnos
a nuestro modelo y base de datos.
Los distintos tipos de generación de identificadores que Hibernate contempla son:
Incremental: Hibernate gestiona automáticamente la generación de un identificador
incremental, este tipo de generación no es aconsejada pues varios procesos insertando
simultáneamente en la misma tabla podrían generar conflictos, nunca debe usarse en un
sistema clusterizado.
Identity: Utiliza columnas de identidad para las bases de datos que las soportan como DB2,
MySQL, MS SQL Server, Sybase e HypersonicSQL.
Sequence: Hibernate genera identificadores a partir de objetos de secuencia de las bases de
datos que los soportan como DB2, PostgreSQL, Oracle, SAP DB, McKoi o Interbase, este es
un ejemplo sencillo de su uso:
<id name="id" type="long" column="columna">
<generator class="sequence">
<param name="sequence">nombreDelObjetoDeSecuencia</param>
</generator>
</id>
Hilo: utiliza el algoritmo hi/lo generando identificadores que son únicos solo para una
determinada base de datos, nunca debe usarse con conexiones JTA o definidas por el usuario.
Seqhilo: Funciona de forma similar al método Hilo a partir del nombre de una secuencia de
base de datos.
Uuid.hex: Utiliza el algoritmo uuid de 128 bits para generar identificadores únicos en forma de
cadena hexadecimal de 32 dígitos dentro de una red (la dirección IP es usada en la generación
de los identificadores)
Uuid.string: Utiliza el mismo algoritmo que uuid.hex pero genera cadenas de caracteres ASCII
de 16 dígitos, no puede usarse en bases de datos PostgreSQL.
Native: Hibernate utiliza automáticamente los métodos sequence, identity o hilo dependiendo
de las capacidades base de datos a la que accede.
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página:
20
Anexo Hibernate
Assigned: Hibernate confía al usuario la tarea de generar el identificador antes de llamar al
método save().
Foreign: Hibernate utiliza el identificador de otro objeto asociado, generalmente mediante una
relación <one-to-one> mediante la clave primaria.
1.8
Hibernate y Struts
El uso de hibernate en el framework Struts es una práctica muy habitual y no es raro que los
que adoptan dicho framework utilicen también hibernate como sistema ORM. Para mejorar el
rendimiento de hibernate es recomendable cachear en memoria la clase SessionFactory,
para ello la forma más cómoda es crear nuestro propio plugin de Struts que inicialice dicha
clase con nuestro fichero de configuración de hibernate, para ello necesitaremos crear nuestra
clase de plugin, insertar unas líneas de configuración en nuestro fichero struts-config.xml y ya
podremos usar nuestro objeto cacheado mejorando el rendimiento de nuestra aplicación.
Para crear nuestro plugin deberemos crear un clase java que implemente el objeto Plugin de
Struts, en ella definiremos un método init() y un método destroy() así como las propiedades con
métodos get y set de los parámetros que necesitemos, en nuestro caso un parámetro con la
ruta del fichero de configuración de hibernate. Inicializaremos la SessionFactory en el método
init() y la cerraremos en el método destroy():
package paquete;
import java.net.URL;
import javax.servlet.ServletException;
import
import
import
import
net.sf.hibernate.HibernateException;
net.sf.hibernate.MappingException;
net.sf.hibernate.SessionFactory;
net.sf.hibernate.cfg.Configuration;
import
import
import
import
import
org.apache.commons.logging.Log;
org.apache.commons.logging.LogFactory;
org.apache.struts.action.ActionServlet;
org.apache.struts.action.PlugIn;
org.apache.struts.config.ModuleConfig;
public class HibernatePlugin implements PlugIn {
private Configuration config;
private SessionFactory factory;
private String path = "/hibernate.cfg.xml";
private static Class clazz = HibernatePlugin.class;
public static final String KEY_NAME = clazz.getName();
private static Log log = LogFactory.getLog(clazz);
public void setPath(String path) {
this.path = path;
}
public void init(ActionServlet servlet, ModuleConfig modConfig)
throws ServletException {
try {
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página:
21
Anexo Hibernate
URL url = HibernatePlugin.class.getResource(path);
config = new Configuration().configure(url);
factory = config.buildSessionFactory();
servlet.getServletContext().setAttribute(KEY_NAME, factory);
} catch (MappingException e) {
log.error("mapping error", e);
throw new ServletException();
} catch (HibernateException e) {
log.error("hibernate error", e);
throw new ServletException();
}
}
public void destroy() {
try {
factory.close();
} catch (HibernateException e) {
log.error("unable to close factory", e);
}
}
}
Como podemos observar hemos puesto nuestro SessionFactory en un atributo del contexto
bajo el nombre de nuestra clase de plugin que además hemos declarado como una constante,
una vez configuremos nuestro plugin en el fichero de configuración de struts podremos obtener
nuestra factory de sesiones con la sentencia:
factory = (SessionFactory) servlet.getServletContext()
.getAttribute(HibernatePlugin.KEY_NAME);
La configuración en el fichero de configuración de Struts sería tan sencilla como estas tres
líneas:
<plug-in className="paquete.HibernatePlugin">
<set-property property="path" value="/hibernate.cfg.xml"/>
</plug-in>
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página:
22
Anexo Hibernate
1.9
Uso de la API de JDBC para la ejecución de procedimientos almacenados.
Los desarrollos que implemente el uso del API JDBC para la llamada a procedimientos
almacenados desde la capa de persistencia deberán cumplir los siguientes requisitos.
1.9.1
Implementación.
La consulta debe estar descrita en el fichero de mapeo. A la hora de realizar la llamada al
procedimiento almacenado se recupera la sentencia SQL del fichero de mapeo mediante la
sesión de Hibernate, convirtiéndolo a STRING para crear un objeto CallableStatement en la
capa de persistencia. La conexión a BBDD se obtendrá de la sesión de Hibernate.
Para identificar los parámetros (parameterIndex) se utilizará su nombre en lugar de su posición
ordinal mejorando así la legibilidad del código generado.
1.9.2
Ejemplo.
Definición de procedimiento almacenado en el fichero de mapeo correspondiente.
Ejemplo.hbm.xml
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<sql-query name="ejemploQuery_SP" callable="true">
<!-- Se deberá sustituir por la función de cada aplicación -->
{?=call ints_pack_log.crear_reg_log_ejemplo(?,?,?,?)}
</sql-query>
<sql-query name ="ejemplo_SP" callable="true">
{?=call sis_func_control_sesion_ora_ ejemplo (?,?)}
</sql-query>
</hibernate-mapping>
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página:
23
Anexo Hibernate
Codificación de la clase en la capa de persistencia.
EjemploUtilDAO.java
* @throws EjemploException En caso de no poderse realizar la operación en BBDD.
*/
public String ejemploQuery(Session session, BeanEjemplo ejem) throws EjemploException {
Integer result = null;
Transaction tx = null;
try{
if(ejem != null) {
// El uso de NamedQueries requiere de una transacción.
// En caso de no existir se crea una.
if (session.getTransaction() == null || !session.getTransaction().isActive()) {
tx = session.beginTransaction();
}
Query ejemQ = session.getNamedQuery("ejemploQuery_SP");
String query = ejemQ.getQueryString();
logger.debug("EjemploUtil.ejemploQuery [" + query + "]");
CallableStatement cStmt = session.connection().prepareCall(query);
cStmt.setString(codAplicacion, ejem.getCodAplicacion());
cStmt.setString(dsFichLogico, ejem.getDsFichLogico());
cStmt.setString(cdacion, ejem.getCdOperacion());
cStmt.setString(cdTpAccesoLogico, ejem.getCdTpAccesoLogico());
cStmt.registerOutParameter(result, ejem.getResult());
cStmt.execute();
logger.debug("EjemploUtil.ejemploQuery [" + cstmt.getInt("result"))); "]");
logger.debug("Result: " + (cstmt.getInt("result")));
ejem.setResult(cstmt.getInt("result"));
cStmt.close();
if (tx != null) {
tx.commit();
}
}
}catch(Exception e){
TrazasUtil.trazaError(logger, "EjemploUtil.ejemploQuery", e);
try {
if (tx != null) {
tx.rollback();
}
}
catch (Exception he) {
TrazasUtil.trazaError(logger, "EjemploUtil.ejemploQuery", he);
}
throw new EjemploException("errors.ejemplo.consulta", e);
}
return ejem;
}
Subdirección General de Desarrollo, Tecnología e Infraestructuras.
Página:
24
Descargar