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