FRAMEWORK ATLAS MANUAL DE BUENAS PRÁCTICAS CON HIBERNATE Versión 1.0 UNIDAD DE ARQUITECTURA DE SOFTWARE DE APLICACIONES DIRECCIÓN DE INGENIERIA Hoja de Control Título Manual de buenas prácticas con Hibernate Responsable Arquitectura Software de Aplicaciones Versión del Framework ATLAS 1.2.9 Versión del documento 1.0 Fecha Versión 29/01/2016 Registro de Cambios Versión Causa del Cambio Versión framework Fecha 1.0 Versión inicial del documento 1.2.9 10/12/2015 Framework: ATLAS: Manual de buenas prácticas con Hibernate Versión: 1.0 Fecha: 29/01/2016 1 DIRECCIÓN DE INGENIERIA Índice 1 INTRODUCCIÓN ...............................................................................................................................................3 1.1 1.2 2 CUANDO USAR LAS DISTINTAS FORMAS DE CONSULTA DE DATOS CON HIBERNATE ..........4 2.1 2.2 2.3 2.4 2.5 3 AUDIENCIA OBJETIVO ............................................................................................................................3 CONOCIMIENTOS PREVIOS ....................................................................................................................3 NAVEGACIÓN POR EL ÁRBOL DE OBJETOS ....................................................................................................4 CUANDO USAR LA INTERFAZ CRITERIA DE HIBERNATE ...............................................................................5 CUANDO USAR ATLASQUERY.......................................................................................................................5 CUANDO USAR CONSULTAS HQL .................................................................................................................6 CUANDO USAR CONSULTAS SQL DE ORACLE ..............................................................................................7 PROBLEMA DEL N+1 EN HIBERNATE .......................................................................................................8 3.1 SOLUCIÓN AL PROBLEMA N+1 CON LEFT JOIN FETCH .................................................................................9 4 LLAMADA A PROCEDIMIENTOS ALMACENADOS..............................................................................10 5 PROCEDIMIENTOS ALMACENADOS QUE DEVUELVEN UN CURSOR ...........................................11 6 USO DE LA SESIÓN DE HIBERNATE.........................................................................................................12 7 USO DE SQL NATIVO ....................................................................................................................................12 8 USO DE NAMED PARAMETES ....................................................................................................................13 9 CONSULTAS EN BATCH CON HIBERNATE ............................................................................................14 9.1 USO DE LA CLASE ATLASPROCESOBATCHDB............................................................................................14 Framework: ATLAS: Manual de buenas prácticas con Hibernate Versión: 1.0 Fecha: 29/01/2016 2 DIRECCIÓN DE INGENIERIA 1 INTRODUCCIÓN Este documento contiene instrucciones y buenas prácticas para el uso Hibernate. 1.1 AUDIENCIA OBJETIVO Este documento está orientado a toda aquella persona que esté desarrollando una aplicación Web basada en el Framework Atlas. 1.2 CONOCIMIENTOS PREVIOS Para un completo entendimiento del documento, el lector deberá tener conocimientos previos sobre las siguientes tecnologías: Lenguaje SQL Lenguaje HQL (Hibernate Query Language) Hibernate criterias Configuración de Hibernate Java Se recomienda la lectura del libro Java Persistence with Hibernate, Editorial Manning, Autor Christian Bauer y Gavin King. Además es imprescindible conocer la documentación oficial de Hibernate https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/index.html En particular el capítulo dedicado al rendimiento. https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/performance.html Framework: ATLAS: Manual de buenas prácticas con Hibernate Versión: 1.0 Fecha: 29/01/2016 3 DIRECCIÓN DE INGENIERIA 2 Cuando usar las distintas formas de consulta de datos con Hibernate Desde Hibernate existen varias maneras de acceso a datos. Navegación en el árbol de objetos Mediante Hibernate Criteria Interface Mediante las clase AtlasQuery de ATLAS Mediante consultas HQL (Hibernate Query Language) Mediante consultas SQL Nativas En cada caso de uso de las aplicaciones se deberá valorar y planear cual es la mejor técnica y estrategia para recuperar los objetos de la consulta necesaria para el caso de uso, teniendo en cuenta minimizar el número de Querys sobre las base de datos. 2.1 Navegación por el árbol de objetos Siempre que se pueda, para acceder a un objeto se debe usar acceso por clave primaria. Esto es una cosa obvia. Lo que hay que tener en cuenta es que si el objeto ya cargado tiene una relación con una lista de objetos, al acceder mediante el arbol de objetos a esa lista, Hibernate en ese momento habrá otra query para cargar la lista siguiente de objetos. Esto no es un problema cuando, como en el ejemplo siguiente se está accediendo a un único objeto. Ejemplo de consulta correcta mediante el árbol de objetos Profesor profesor = this.facade.findProfesor(idProfesor); for (Asignatura asignatura : profesor.getAsignaturas()) { System.out.println("\t" + asignatura); } En este caso Hibernate lanzará 2 querys. Una para obtener el profesor y otra para obtener las asignaturas. Aunque se podría hacer una consulta para obtenerlo de una vez, es una práctica aceptable. Esto es un problema si se van a recorrer una lista de objetos y de esta a su vez obtener otra lista de objetos. Ejemplo de consulta INCORRECTA mediante el árbol de objetos Profesor profesor = this.facade.findProfesor(idProfesor); for (Asignatura asignatura : profesor.getAsignaturas()) { for (Alumno alumno : asignatura.getAlumnos()) { System.out.println("\t" + alumno.getNombre()); } } En este caso se harán muchas más querys de las imprescindible lo cual puede causar un problema de rendimiento. En este caso se realizará una query para obtener el profesor, otra para obtener las asignaturas, y por cada asignatura se hará otra query para obtener los alumnos de esa asignatura. En este caso es mucho más óptimo utilizar Criterias o HQL para en una sola consulta obtener todos los alumnos de las asignaturas de un profesor. Framework: ATLAS: Manual de buenas prácticas con Hibernate Versión: 1.0 Fecha: 29/01/2016 4 DIRECCIÓN DE INGENIERIA 2.2 Cuando usar la Interfaz Criteria de Hibernate El Api de Hibernate de Criteria permite construir una query estableciendo las restricciones dinámicamente y en tiempo de ejecución. En la parte de negocio de la aplicación, es decir cuando se están haciendo consultas para luego hacer modificaciones, procesado, validación de los datos de las entidades, etc, lo recomendable es usar los propios criterias de Hibernate (o la clase AtlasQuery de Atlas). Ejemplo: En una aplicación de expedientes, en la pantalla que se están creando un expediente, validando los datos del expediente que introduce el usuario, danto de alta varias actuaciones para un expediente, etc, es recomendable usar los criterias de Hibernate para tener un acceso muy cómodo a los datos mediante los objetos de domino que devuelven los criterias y las facilidades de objetos mapeados en otros objetos, etc. Además, en este caso el código generado es muy claro y mantenible. 2.3 Cuando usar AtlasQuery La clase AtlasQuery existente dentro del Framework ATLAS es una clase de ayuda para la creación de Criterias de Hibernate. A esta clase se le van diciendo los criterions, joins, proyeccciones etc que va a tener una consulta y al final se usa el método toHibernateCriteria para generar un criteria con todas las características indicadas. El uso de esta clase es cómodo y es recomendado para consultas sencillas, pero no es obligatorio su uso. Tampoco cubre el 100% de las consultas que se podrían hacer usando los criterias de Hibernate. Existe un manual llamado ATLAS_MUS_AtlasQuery.pdf que explica el uso de esta clase. Si la consulta a realizar es muy complicada entonces será mejor usar directamente Criterias de Hibernate o HQL. Framework: ATLAS: Manual de buenas prácticas con Hibernate Versión: 1.0 Fecha: 29/01/2016 5 DIRECCIÓN DE INGENIERIA 2.4 Cuando usar consultas HQL HQL es un dialecto orientado a objetos del lenguaje SQL. Este lenguaje es comúnmente usado para recuperación de objetos, no para actualizaciones, inserciones o borrado de datos. En las pantallas de consulta, donde exista una pantalla que muestra una serie de registros (Listas Paginadas), que se obtienen de una consulta a varias tablas relacionadas es muy recomendable usar HQL. Causas por las que es recomendable: 1. Haciendo una consulta con criterias de Hibernate o con AtlasQuery (y si no se usan proyeccciones), la consulta final que ejecuta Hibernate en HQL se trae todos los campos de todas las tablas relacionadas, cuando lo más probable es que en la lista de campos a mostrar sólo se muestren unos cuantos 2. Si la consulta con criterias no se hace correctamente (usando correctamente los join y los fetch mode en los criterias) es posible que Hibernate ejecute muchas queries de SQL para una sola consulta o criteria. Es el proveedor el que debe preocuparse de mirar las queries que realmente se están lanzando en una consulta, para ver si se debe optimizar el criteria, o como es el caso que estamos describiendo hacer una consulta con HQL Ejemplo: Tenemos una pantalla en el que queremos mostrar en una lista paginada: Nombre de los alumnos, asignaturas y curso de la asignatura. Tenemos: La BBDD que tenemos para obtener estos 3 campos tiene que hacer una query de 5 tablas (por ejemplo) La tabla alumnos tiene 30 campos, la asigturas 15 y la curso 6. Haciéndolo con un Criteria de Hibernate o con un AtlasQuery obtendríamos una query que pediría al menos 51 campos (30+16+6) de los que sólo vamos a usar 3. Además si el criteria no está correctamente formado. Por ejemplo no se han hecho correctamente los join con el fetch mode join, es posible que Hibernate haga una query para obtener los alumnos a mostrar, y luego por cada alguno obtenido haga otra query para obtener las asignaturas. Con lo cual esto no es lo más óptimo en una pantalla de consulta. Ver el apartado “Problema del N+1 en Hibernate” para más información. En este caso sería mucho más óptimo hacer una HQL que sólo se traiga los 3 campos a mostrar y haga las joins y los where que necesite para hacer esa consulta. No sólo en las listas páginadas es recomendable usar HQL, sino en cualquier consulta que con Criterias se vuelva muy complicada o aquellas en las que no queramos obtener más que sólo unos cuantos campos de las tablas involucradas en la consulta. Framework: ATLAS: Manual de buenas prácticas con Hibernate Versión: 1.0 Fecha: 29/01/2016 6 DIRECCIÓN DE INGENIERIA 2.5 Cuando usar consultas SQL de Oracle En principio no es recomendable usar directamente consultas SQL a no ser que sea porque una query concreta se detecte que tarde mucho y haya que optimizarla haciendo una consulta SQL compleja. La optimización se haría directamente en el gestor de BBDD y luego se trasladaría la query al código de la aplicación. Hay que tener en cuenta que este tipo de queries pueden ser muy dependientes del gestor de BBDD, y en algunos casos incluso de la versión usada en el Gestor de BBDD. (Oracle 9, Oracle 11g…) Framework: ATLAS: Manual de buenas prácticas con Hibernate Versión: 1.0 Fecha: 29/01/2016 7 DIRECCIÓN DE INGENIERIA 3 Problema del N+1 en Hibernate Uno de los mayores problemas de los ORM en general y de Hibernate en particular es el problema llamado de los “n+1” SELECTs. Este problema consiste en que al lanzar una consulta que retorna n filas, el ORM lanza n+1 consultas SQL de SELECT. Como podemos imaginar esto genera unas pérdidas de rendimiento brutales y consumo excesivo de CPU por parte de las aplicaciones. Veamos ahora un ejemplo con Hibernate de este problema. Ejemplo de consulta INCORRECTA con HQL – EjemploDao.java List<Profesor> profesores = getHibernateTemplate().find("SELECT p FROM Profesor p"); for (Profesor profesor : profesores) { System.out.println(profesor.toString()); for (CorreoElectronico correoElectronico : profesor.getCorreosElectronicos()) { System.out.println("\t"+correoElectronico); } } Este código lo único que hace es mostrar todos los profesores, y para cada profesor mostrar todas sus direcciones de correo. Esto lo hemos realizado lanzado únicamente una consulta HQL contra Hibernate. Al ejecutar el programa y comprobar las SELECTs de SQL que se han lanzado podemos ver como se lanza una primera consulta para obtener todos los profesores pero posteriormente se lanza una consulta adicional por cada profesor para obtener los correos electrónicos de cada profesor. Es decir que se ejecutan ”n+1” SELECTs siendo ”n” el número de filas que retorna la primera consulta, en nuestro caso el número de profesores. Framework: ATLAS: Manual de buenas prácticas con Hibernate Versión: 1.0 Fecha: 29/01/2016 8 DIRECCIÓN DE INGENIERIA 3.1 Solución al problema N+1 con Fetch La solución más sencilla es decirle a Hibernate que también cargue todos los correos electrónicos en la misma query, para ellos deberemos hacer lo siguiente: En HQL deberemos hacer un la join utilizando FETCH en la Join a realizar entre los profesores y los correos electrónicos. La consulta HQL que realiza el LEFT JOIN FETCH entre los profesores y los correos electrónicos es la siguiente: Ejemplo de consulta correcta con HQL List<Profesor> profesores = getHibernateTemplate() .find("SELECT p FROM Profesor p LEFT JOIN FETCH p.correosElectronicos "); for (Profesor profesor : profesores) { System.out.println(profesor.toString()); for (CorreoElectronico correoElectronico : profesor.getCorreosElectronicos()) { System.out.println("\t"+correoElectronico); } } Si ejecutamos ahora el código podremos ver en la consola que se ha lanzado únicamente la siguiente SELECT de SQL, con lo que se ha solucionado el problema. Query ejecutada por Hibernate SELECT profesor0_.Id AS Id0_0_, correosele1_.IdCorreo AS IdCorreo1_1_, profesor0_.nombre AS nombre0_0_, profesor0_.ape1 AS ape3_0_0_, profesor0_.ape2AS ape4_0_0_, correosele1_.direccionCorreo AS direccio2_1_1_, correosele1_.idProfesor AS idProfesor1_1_, correosele1_.idProfesor AS idProfesor0_0__, correosele1_.IdCorreo AS IdCorreo0__ FROM Profesor profesor0_ LEFT OUTER JOIN CorreoElectronico correosele1_ ON profesor0_.Id=correosele1_.idProfesor Atención Si se están usando Criterias de Hibernate (o la clase AtlasQuery), este problema no está solucionado para las consultas con innerJoin. Es decir aunque se espeficique para los Criteria con innerJoin un FetchMode.JOIN se seguirá produciendo el problema del N+1 Para estos casos hay que usar siempre HQL para optimizar las consultas Referencia: https://hibernate.atlassian.net/browse/HHH-7842 Framework: ATLAS: Manual de buenas prácticas con Hibernate Versión: 1.0 Fecha: 29/01/2016 9 DIRECCIÓN DE INGENIERIA 4 Llamada a procedimientos almacenados La llamada a procedimientos almacenados se hará mediante la clase doWork() de Hibernate, de forma que no hay que pedir la sesión actual. Ejemplo de uso: EjemploDAO.java /** * Valida un documento de identidad llamando a un procedimiento * almacenado en la BBDD * * @param documento a validar * @return resultado de la validación del documento * @throws DataAccessException */ public String validaDocumento(final String documento) throws DataAccessException { class WorkImpl implements Work { private String resultado; public void execute(Connection connection) throws SQLException { CallableStatement statement = null; try { statement = connection.prepareCall( "{call sis_proc_ident_cod(?, ?)}"); statement.setString(1, documento.toUpperCase()); statement.registerOutParameter(1, java.sql.Types.VARCHAR); statement.registerOutParameter(2, java.sql.Types.VARCHAR); statement.executeQuery(); resultado = statement.getString(1); } finally { if (statement != null) { statement.close(); } } } } // Creamos el work y lo ejecutamos para devolver el resultado WorkImpl work = new WorkImpl(); try { getHibernateTemplate().getSessionFactory(). getCurrentSession().doWork(work); return work.resultado; } catch (Exception e) { throw new DataAccessException("Error validando documento " + documento, e); } } Framework: ATLAS: Manual de buenas prácticas con Hibernate Versión: 1.0 Fecha: 29/01/2016 10 DIRECCIÓN DE INGENIERIA 5 Procedimientos almacenados que devuelven un cursor Si uno de los parámetros de salida del procedimiento almacenado es un SYS_REFCURSOR de Oracle este es un ejemplo de cómo leerlo usando un doWork(): EjemploDAO.java /** * Obtiene los módulos de una aplicación. * El tercer parámetro del procedimiento almacenado es un cursor de Oracle * * @param codigoAplicacion * @return lista de objetos ModulosApp * @throws DataAccessException */ public List<ModulosApp> obtenerModulosAplicacion (final String codigoAplicacion) throws DataAccessException { class WorkImpl implements Work { private List<ModulosApp> listaResultado = new ArrayList<ModulosApp>(); public void execute(Connection connection) throws SQLException { ResultSet rs = null; CallableStatement statement = null; try { statement = connection.prepareCall( "{call CONSULTA_MODULOS(?,?,?) }"); statement.setString(1, codigoAplicacion); statement.registerOutParameter(2, java.sql.Types.VARCHAR); statement.registerOutParameter(3, oracle.jdbc.OracleTypes.CURSOR); statement.execute(); //Recuperamos cursor rs = (ResultSet) statement.getObject(3); while (rs.next()) { ModulosApp modulos = new ModulosApp(); modulos.setCdaplic(rs.getString("CDAPLIC")); modulos.setDsaplic(rs.getString("DSAPLIC")); ... ... listaResultado.add(modulos); } } finally { if (rs != null) { rs.close(); // Importante cerrar el cursor de Oracle } if (statement != null) { statement.close();// Cerrar el statement } } } } //Creamos el work y lo ejecutamos para devolver la lista de módulos WorkImpl work = new WorkImpl(); try { getHibernateTemplate().getSessionFactory(). getCurrentSession().doWork(work); return work.listaResultado; } catch (Exception e) { throw new DataAccessException( "Error recuperando módulos de la aplicación " + codigoAplicacion, e); } } Framework: ATLAS: Manual de buenas prácticas con Hibernate Versión: 1.0 Fecha: 29/01/2016 11 DIRECCIÓN DE INGENIERIA 6 Uso de la sesión de Hibernate En la medida de lo posible se debe evitar el uso de la sesión de Hibernate. Para el caso de los procedimientos almacenados se debe hacer como se explica en el apartado correspondiente, mediante el uso de doWork. En el caso de que sea imprescindible obtener la sesión se hará desde un DAO y con el siguiente código. EjemploDao.java public List<Object[]> getQueryData() { … Session sesion = getHibernateTemplate().getSessionFactory(). getCurrentSession(); … } 7 Uso de SQL Nativo En el caso de utilizar SQL se implementará mediante los mecanimos que proporciona Hibernate para la ejecución de queries SQL nativas (createSQLQuery) EjemploDao.java – Ejemplo de uso de SQL Nativo public List<Object[]> getClientesConNombre(String nombre) { String sql = ("SELECT nombre, apellido1 FROM clientes where nombre = :nombre"); SQLQuery query = getHibernateTemplate().getSessionFactory(). getCurrentSession().createSQLQuery(sql); query.setString("nombre", nombre); return query.list(); } Framework: ATLAS: Manual de buenas prácticas con Hibernate Versión: 1.0 Fecha: 29/01/2016 12 DIRECCIÓN DE INGENIERIA 8 Uso de Named Parametes Por motivos de optimización y seguridad de las consultas es necesario que los parámetros utilizados como criterios no se adjunten en el String de la consulta y sean pasados como ‘named parameters’. A continuación se muestra un ejemplo incorrecto y otro correcto de creación de consultas: Creación INCORRECTA de consultas String strQuery = "from Cliente where nombre = '" + nombre + "'"; // MAL Query query = getHibernateTemplate()getSessionFactory(). getCurrentSession().createQuery(strQuery); return query.list(); Creación CORRECTA de consultas String strQuery = "from Cliente where nombre = :nombre"; // BIEN Query query = getHibernateTemplate()getSessionFactory(). getCurrentSession().createQuery(strQuery); query.setString("nombre", nombre); return query.list(); Framework: ATLAS: Manual de buenas prácticas con Hibernate Versión: 1.0 Fecha: 29/01/2016 13 DIRECCIÓN DE INGENIERIA 9 Consultas en BATCH con Hibernate Cuando se necesita hacer un proceso en Batch sobre un número muy grande de registros, un error muy común usando Hibernate que es: Primero hacer la query o criteria para obtener la lista de entidades y luego hacer un bucle para tratar cada una de las entidades. Al hacerlo de esta forma la lista de entidades primero se carga en memoria, con lo que si la query devuelve 200.000 registros (por ejemplo), lo primero que hace la aplicación es cargar esos 200.000 registros en memoria y esto puede dar muy facilmente errores de falta de memoria en la máquina virtual. Para evitar este problema hay que hacer uso de algunas funcionalidades que ofrece Hibernate como son: Activar la propiedad hibernate.jdbc.batch_size en la sesión .setCacheMode(CacheMode.IGNORE) en los criteria .scroll(ScrollMode.FORWARD_ONLY)en los criteria session.flush() y session.clear() Desde el Framework ATLAS se ha creado una clase de ayuda para realizar el proceso de muchos registros en Batch llamada AtlasProcesoBatchBD.java. Atención Si se está usando una versión de ATLAS anterior a la 1.2.9 solicitar la clase AtlasProcesoBatchDB.java a Arquitectura para añadirla en el proyecto manualmente 9.1 Uso de la clase AtlasProcesoBatchDB Los pasos a seguir son los siguientes 1. Configurar las propiedades de hibernate para poner que los registros se procesen en lotes (en principio de 50) y quitar la caché de segundo nivel de Hibernate 2. Crear en la capa de servicios un objeto que implemente la interface AtlasProcesarEntidadBD<T> y sobreescribir el método altasProcesarEntidadBD en el que se pondrá la lógica para procesar una entidad o registro. 3. Crear en la capa de DAO un método para procesar la tabla que reciba por parámetro el método creado anteriormente, que cree la query para leer todo el fichero a procesar y que llame al método AtlasProcesoBatchBD.ejecutarProcesoEntidadBD que es el que procesará los registros de la query sin cargarlos previamente todos en memoria 4. Desde la capa de servicios hacer un método que llame a método del DAO creado en el punto anterior Framework: ATLAS: Manual de buenas prácticas con Hibernate Versión: 1.0 Fecha: 29/01/2016 14 DIRECCIÓN DE INGENIERIA A continuación se muestra un ejemplo: Modificaciones en el fichero applicationContext-database.xml <property <props> <prop <prop <prop <prop <prop <prop <prop name="hibernateProperties"> key="hibernate.dialect">${hibernate.dialect}</prop> key="hibernate.show_sql">false</prop> key="hibernate.format_sql">false</prop> key="hibernate.use_sql_comments">false</prop> key="hibernate.cache.use_second_level_cache">false</prop> key="hibernate.cache.use_query_cache">false</prop> key="hibernate.cache.region.factory_class"> atlas.core.cache.AtlasCacheRegionFactory</prop> key="hibernate.cache.region_prefix"></prop> key="net.sf.ehcache.configurationResourceName">ehcache.xml</prop> key="atlas.cache.debugListener">false</prop> key="hibernate.jdbc.batch_size">50</prop> <prop <prop <prop <prop </props> </property> Javadoc de la clase de ayuda AtlasProcesoBatchBD.java /** * Clase de ayuda para realizar procesamiento por lotes en base de datos en el * framework Atlas * @author ICM */ public class AtlasProcesoBatchBD { /** * Interfaz que habrá a implementar para realizar el procesamiento batch * dada una query de cada una de los objetos que recupera */ public interface AtlasProcesarEntidadBD<T> { /** Método a sobreescibir **/ public void altasProcesarEntidadBD(T t); } /** * Método de ayuda para procesar un query grande y llamar a un proceso por * cada entidad de la query */ public static <T> void ejecutarProcesoEntidadBD(Session session, Query query, AtlasProcesarEntidadBD<T> proceso, boolean readonly) throws Exception { } } Framework: ATLAS: Manual de buenas prácticas con Hibernate Versión: 1.0 Fecha: 29/01/2016 15 DIRECCIÓN DE INGENIERIA Ejemplo ProcesaUnCliente.java public class ProcesaUnCliente implements AtlasProcesarEntidadBD<Cliente>{ /** * Fichero destino donde escribir los clientes */ private PrintWriter pw; /** * Constructor con los parámetros necesarios para procesar una entidad * @param pw */ public ProcesaUnCliente(PrintWriter pw) { this.pw = pw; } /** * Método a sobreescribir que procesa una entidad del query */ @Override public void altasProcesarEntidadBD(Cliente cliente) { … … <código para procesar un cliente> … … } Ejemplo ClienteDao.java public void procesaClientes(ProcesaUnCliente procesaUnCliente, Map<String, Object> parametrosFiltro){ //Crear la query necesaria para recorrer el fichero a tratar StringBuilder strQuery = new StringBuilder("from Cliente c "); Query query = getSessionFactory().getCurrentSession(). createQuery(strQuery.toString()); for (Map.Entry<String, Object> entry : parametrosFiltro.entrySet()){ query.setString(entry.getKey(), (String) entry.getValue()); } try { AtlasProcesoBatchBD.ejecutarProcesoEntidadBD( getSessionFactory().getCurrentSession(), query, procesaUnCliente, true); // readonly } catch (Exception e) { LOG.error("Error procesando tabla de clientes: " + e.getMessage()); throw new DataAccessException(e); } } Framework: ATLAS: Manual de buenas prácticas con Hibernate Versión: 1.0 Fecha: 29/01/2016 16 DIRECCIÓN DE INGENIERIA Ejemplo ClienteService.java public File generarFicheroClientes(String sNombreFichero) throws ServiceException { .., try { writer = new FileWriter(sNombreFichero); pw = new PrintWriter(writer); getDao().procesaClientes( new ProcesaUnCliente(pw), //Tratamiento cada entidad filtro); //parámetros de la query } catch (IOException io) { ... } finally{ //Se cierra el fichero pw.close(); ... } } Framework: ATLAS: Manual de buenas prácticas con Hibernate Versión: 1.0 Fecha: 29/01/2016 17