Trabajando con objetos Hibernate es una solución completa de mapeo objeto/relacional que no sólo abstrae al desarrollador de los detalles del sistema de manejo de base datos subyacente, sino que además ofrece manejo de estado de objetos. Esto es, al contrario del manejo de sentencias SQL en capas comunes de persistencia JDBC/SQL, una vista de la persistencia en aplicaciones Java muy natural y orientada a objetos. En otras palabras, los desarroladores de aplicaciones Hibernate deben siempre pensar en el estado de sus objetos, y no necesariamente en la ejecución de sentencias SQL. Esta parte es cuidada por Hibernate y es sólo relevante para el desarrollador de la aplicación al afinar el rendimiento del sistema. Estados de objeto de Hibernate Hibernate define y soporta los siguientes estados de objeto: Transitorio - un objeto es transitorio si ha sido recién instanciado usando el operador new, y no está asociado a una Session de Hibernate. No tiene una representación persistente en la base de datos y no se le ha asignado un valor identificador. Las instancias transitorias serán destruídas por el recolector de basura si la aplicación no mantiene más una referencia. Usa la Session de Hibernate para hacer un objeto persistente (y deja que Hibernate cuide de las sentencias SQL que necesitan ejecutarse para esta transición). Persistente - una instancia persistente tiene una representación en la base de datos y un valor identificador. Puede haber sido salvado o cargado, sin embargo, está por definición en el ámbito de una Session. Hibernate detectará cualquier cambio hecho a un objeto en estado persistentey sincronizará el estado con la base de datos cuando se complete la unidad de trabajo. Los desarrolladores no ejecutan sentencias UPDATE manuales, o sentencias DELETE cuando un objeto debe ser hecho transitorio. Separado (detached) - una instancia separada es un objeto que ha sido hecho persistente, pero su Session ha sido cerrada. La referencia al objeto todavía es válida, por supuesto, y la instancia separada podría incluso ser modificada en este estado. Una instancia separada puede ser re-unida a una nueva Session en un punto posterior en el tiempo, haciéndola persistente de nuevo (con todas las modificaciones). Este aspecto habilita un modelo de programación para unidades de trabajo de ejecución larga que requieren tiempo-para-pensar del usuario. Las llamamos transaccciones de aplicación, es decir, una unidad de trabajo desde el punto de vista del usuario. Discutiremos ahora los estados y transiciones de estados (y los métodos de Hibernate que disparan una transición) en más detalle: Haciendo los objetos persistentes Las instancias recién instanciadas de una clase persistente son consideradas transitorias por Hibernate. Podemos hacer una instancia transitoria persistente asociándola con una sesión: Si Cat tiene un identificador generado, el identificador es generado y asignado al cat cuando se llama a save(). Si Cat tiene un identificador assigned, o una clave compuesta, el identificador debe ser asignado a la instancia de cat antes de llamar a save(). Puedes también usar persist() en vez de save(), con la semántica definida en el temprano borrador de EJB3. Alternativamente, puedes asignar el identificador usando una versión sobrecargada de save(). Si el objeto que haces persistente tiene objetos asociados (por ejemplo, la colección kittens en el ejemplo anterior), estos objetos pueden ser hechos persistentes en cualquier orden que quieras a menos que tengas una restricción NOT NULL sobre una columna clave foránea. Nunca hay riesgo de violar restricciones de clave foránea. Sin embargo, podrías violar una restricción NOT NULL si llamas a save() sobre objetos en orden erróneo. Usualmente no te preocupas con este detalle, pues muy probablemente usarás la funcionalidad de persistencia transitiva de Hibernate para salvar los objetos asociados automáticamente. Entonces, ni siquiera ocurren violaciones de restricciones NOT NULL - Hibernate cuidará de todo. La persistencia transitiva se discute más adelante en este capítulo. Cargando un objeto Los métodos load() de Session te brindan una forma de traer una instancia persistente si ya saves su identificador. load() toma un objeto clase y cargará el estado dentro de una instancia recién instanciada de esta clase, en estado persistente. Alternativamente, puedes cargar estado dentro de una instancia dada: Nota que load() lanzará una excepción irrecuperable si no hay una fila correspondiente en base de datos. Si la clase es mapeada con un proxy, load() sólo devuelve un proxy no inicializado y no llamará realmente a la base de datos hasta que invoques un método del proxy. Este comportamiento es muy útil si deseas crear una asociación a un objeto sin cargarlo realmente de la base de datos. Permite además que múltiples instancias sean cargadas como un lote si se define batch-size para el mapeo de la clase. Si no tienes certeza que exista una fila correspondiente, debes usar el método get(), que llama a la base de datos inmediatamente y devuelve nulo si no existe una fila correspondiente. Puedes incluso cargar un objeto usando un SELECT ... FOR UPDATE de SQL, usando un LockMode. Ver la documentación de la API para más información. Ten en cuenta que ninguna instancia asociada o colección contenida es selecciona FOR UPDATE, a menos que decidas especificar lock o all como un estilo de cascada para la asociación. Es posible volver a cargar un objeto y todas sus colecciones en cualquier momento, usando el método refresh(). Esto es útil cuando se usan disparadores de base de datos para inicializar algunas de las propiedades del objeto. Una cuestión importante aparece usualmente en este punto: ¿Cuánto carga Hibernate de la base de datos y cuántos SELECTs de SQL usará? Esto depende de la estrategia de recuperación y se explica en . Consultando Si no sabes los identificadores de los objetos que estás buscando, necesitas una consulta. Hibernate soporta un lenguaje de consulta orientado a objetos (HQL) fácil de usar pero potente. Para la creación de consultas programáticas, Hibernate soporta una funcionalidad sofisticada de consulta de Criteria y Example (QBC and QBE). También puedes expresar tu consulta en el SQL nativo de tu base de datos, con soporte opcional de Hibernate para la conversión del conjunto resultado en objetos. Ejecutando consultas Las consultas HQL y SQL nativas son representadas con una instancia de org.hibernate.Query. Esta interface ofrece métodos para la ligación de parámetros, manejo del conjunto resultado, y para la ejecución de la consulta real. Siempre obtienes una Query usando la Session actual: Una consulta se ejecuta usualmente invocando a list(), el resultado de la consulta será cargado completamente dentro de una colección en memoria. Las instancias de entidad traídas por una consulta están en estado persistente. El método uniqueResult() ofrece un atajo si sabes que tu consulta devolverá sólo un objeto. Iterando los resultados Ocasionalmente, podrías ser capaz de lograr mejor rendimiento al ejecutar la consulta usando el método iterate(). Esto sólo será en el caso que esperes que las instancias reales de entidad devueltas por la consulta estén ya en la sesión o caché de segundo nivel. Si todavía no están en caché, iterate() será más lento que list() y podría requerir muchas llamadas a la base de datos para una consulta simple, usualmente 1 para la selección inicial que solamente devuelve identificadores, y n selecciones adicionales para inicializar las instancias reales. Consultas que devuelven tuplas Las consultas de Hibernate a veces devuelven tuplas de objetos, en cuyo caso cada tupla se devuelve como un array: Resultados escalares Las consultas pueden especificar una propiedad de una clase en la cláusula select. Pueden incluso llamar a funciones de agregación SQL. Las propiedades o agregaciones son considerados resultados "escalares" (y no entidades en estado persistente). Ligación de parámetros Se proveen métodos en Query para ligar valores a parámetros con nombre o parámetros ? de estilo JDBC. Al contrario de JDBC, Hibernate numera los parámetros desde cero. Los parámetros con nombre son identificadores de la forma :name en la cadena de la consulta. Las ventajas de los parámetros con nombre son: los parámetros con nombre son insensibles al orden en que aparecen en la cadena de consulta pueden aparecer múltiples veces en la misma consulta son auto-documentados Paginación Si necesitas especificar límites sobre tu conjunto resultado (el número máximo de filas que quieras traer y/o la primera fila que quieras traer) debes usar los métodos de la interface Query: Hibernate sabe cómo traducir este límite de consulta al SQL nativo de tu DBMS. Iteración scrollable Si tu driver JDBC soporta ResultSets scrollables, la interface Query puede ser usada para obtener un objeto ScrollableResults, que permite una navegación flexible de los resultados de consulta. i++ ) && cats.next() ) pageOfCats.add( cats.get(1) ); } cats.close()]]> Nota que se requiere una conexión de base de datos abierta (y cursor) para esta funcionalidad, usa setMaxResult()/setFirstResult() si necesitas la funcionalidad de paginación fuera de línea. Externalizando consultas con nombre Puedes además definir consultas con nombre en el documento de mapeo. (Recuerda usar una sección CDATA si tu consulta contiene caracteres que puedan ser interpretados como etiquetado.) ? ] ]>]]> La ligación de parámetros y ejecución se hace programáticamente: Nota que el código real del programa es independiente del lenguaje de consulta usado; puedes además definir consultas SQL nativas en metadatos, o migrar consultas existentes a Hibernate colocándolas en ficheros de mapeo. Filtrando colecciones Un filtro de colección es un tipo especial de consulta que puede ser aplicado a una colección persistente o array. La cadena de consulta puede referirse a this, significando el elemento de colección actual. La colección devuelta es considerada un bag, y es una copia de la colección dada. La colección original no es modificada (esto es contrario a la implicación del nombre "filtro", pero consistente con el comportamiento esperado). Observa que los filtros no requieren una cláusula from (aunque pueden tener uno si se requiere). Los filtros no están limitados a devolver los elementos de colección por sí mismos. Incluso una consulta de filtro vacío es útil, por ejemplo, para cargar un subconjunto de elementos en una colección enorme: Consultas de criterios HQL es extremadamente potente pero algunos desarrolladores prefieren construir consultas dinámicamente usando una API orientada a objetos, en vez construir cadenas de consulta. Hibernate provee una API intuitiva de consulta Criteria para estos casos: Las APIs de Criteria y la asociada Example son discutidas en más detalle en . Consultas en SQL nativo Puedes expresar una consulta en SQL, usando createSQLQuery() y dejando que Hibernate cuide del mapeo de los conjuntos resultado a objetos. Nota que puedes llamar en cualquier momento a session.connection() y usar la Connection JDBC directamente. Si eliges usar la API de Hibernate, debes encerrar los alias de SQL entre llaves: Las consultas SQL pueden contener parámetros con nombre y posicionales, al igual que las consultas de Hibernate. Puede encontrarse más información sobre consultas en SQL nativo en . Modificando objetos persistentes Las instancias persistentes transaccionales (es decir, objetos cargados, creados o consultados por la Session) pueden ser manipulados por la aplicación y cualquier cambio al estado persistente será persistido cuando la Session sea limpiada (flushed) (discutido más adelante en este capítulo). No hay necesidad de llamar un método en particular (como update(), que tiene un propósito diferente) para hacer persistentes tus modificaciones. De modo que la forma más directa de actualizar el estado de un objeto es cargarlo con load(), y entonces manipularlo directamente, mientras la Session está abierta: A veces este modelo de programación es ineficiente pues podría requerir una SELECT de SQL (para cargar un objeto) y un UPDATE de SQL (para hacer persistentes sus datos actualizados) en la misma sesión. Por lo tanto, Hibernate ofrece un enfoque alternativo, usando instancias separadas (detached). Nota que Hibernate no ofreve su propia API para ejecución directa de sentencias UPDATE o DELETE. Hibernate es un servicio de gestión de estado, no tienes que pensar en sentencias para usarlo. JDBC es una API perfecta para ejecutar sentencias SQL; puedes obtener una Connection JDBC en cualquier momento llamando a session.connection(). Además, la noción de operaciones masivas entra en conflicto con el mapeo objeto/relacional en aplicaciones en línea orientadas al procesamiento de transacciones. Versiones futuras de Hibernate pueden, sin embargo, proveer funciones de operación masiva especiales. Ver por algunos trucos de operación en lote (batch) posibles. Modificando objetos separados Muchas aplicaciones necesitan recuperar un objeto en una transacción, enviarla a la capa de UI para su manipulación, y entonces salvar los cambios en una nueva transacción. Las aplicaciones que usan este tipo de enfoque en un entorno de alta concurrencia usualmente usan datos versionados para asegurar el aislamiento de la unidad de trabajo "larga". Hibernate soporta este modelo al proveer re-unión de instancias separadas usando los métodos Session.update() o Session.merge(): Si el Cat con identificador catId ya hubiera sido cargado por secondSession cuando la aplicación intentó volver a unirlo, se habría lanzado una excepción. Usa update() si no estás seguro que la sesión tenga una instancia ya persistente con el mismo identificador, y merge() si quieres fusionar tus modificaciones en cualquier momento sin consideración del estado de la sesión. En otras palabras, update() es usualmente el primer método que llamarías en una sesión fresca, asegurando que la re-unión de tus instancias separadas es la primera operación que se ejecuta. La aplicación debe actualizar individualmente las instancias separadas alcanzables por la instancia separada dada llamando a update(), si y sólo si quiere que sus estados sean también actualizados. Esto puede, por supuesto, ser automatizado usando persistencia transitiva, ver . El método lock() también permite a una aplicación reasociar un objeto con una sesión nueva. Sin embargo, la instancia separada no puede haber sido modificada! Nota que lock() puede ser usado con varios LockModes, ver la documentación de la API y el capítulo sobre manejo de transacciones para más información. La re-unión no es el único caso de uso para lock(). Se discuten otros modelos para unidades de trabajo largas en . Detección automática de estado Los usuarios de Hibernate han pedido un método de propósito general que bien salve una instancia transitoria generando un identificador nuevo, o bien actualice/reúna las instancias separadas asociadas con su identificador actual. El método saveOrUpdate() implementa esta funcionalidad. El uso y semántica de saveOrUpdate() parece ser confuso para usuarios nuevos. Primeramente, en tanto no estés intentando usar instancias de una sesión en otra sesión nueva, no debes necesitar usar update(), saveOrUpdate(), o merge(). Algunas aplicaciones enteras nunca usarán ninguno de estos métodos. Usualmente update() o saveOrUpdate() se usan en el siguiente escenario: la aplicación carga un objeto en la primera sesión el objeto es pasado a la capa de UI se hacen algunas modificaciones al objeto el objeto se pasa abajo de regreso a la capa de negocio la aplicación hace estas modificaciones persistentes llamando a update() en una segunda sesión saveOrUpdate() hace lo siguiente: si el objeto ya es persistente en esta sesión, no hace nada si otro objeto asociado con la sesión tiene el mismo identificador, lanza una excepción si el objeto no tiene ninguna propiedad identificadora, lo salva llamando a save() si el identificador del objeto tiene el valor asignado a un objeto recién instanciado, lo salva llamando a save() si el objeto está versionado (por un <version> o <timestamp>), y el valor de la propiedad de versión es el mismo valor asignado a una objeto recién instanciado, lo salva llamando a save() en cualquier otro caso se actualiza el objeto llamando a update() y merge() es muy diferente: si existe una instancia persistente con el mismo identificador asignado actualmente con la sesión, copia el estado del objeto dado en la instancia persistente si no existe ninguna instancia persistente actualmente asociada a la sesión, intente cargarla de la base de datos, o crear una nueva instancia persistente la instancia persistente es devuelta la instancia dada no resulta ser asociada a la sesión, permanece separada Borrando objetos persistentes Session.delete() quitará el estado de un objeto de la base de datos. Por supuesto, tu aplicación podría tener aún una referencia a un objeto borrado. Lo mejor es pensar en delete() como hacer transitoria una instancia persistente. Puedes borrar los objetos en el orden que gustes, sin riesgo de violaciones de restricción de clave foránea. Aún es posible violar una restricción NOT NULL sobre una columna clave foránea borrando objetos en un orden erróneo, por ejemplo, si borras el padre, pero olvidas borrar los hijos. Replicando objetos entre dos almacénes de datos diferentes Es ocasionalmente útil ser capaz de tomar un grafo de instancias persistentes y hacerlas persistentes en un almacén de datos diferente, sin regenerar los valores identificadores. El ReplicationMode determina cómo replicate() tratará los conflictos con filas existentes en la base de datos. ReplicationMode.IGNORE - ignora el objeto cuando existe una fila de base de datos con el mismo identificador ReplicationMode.OVERWRITE - sobrescribe cualquier fila de base de datos existente con el mismo identificador ReplicationMode.EXCEPTION - lanza una excepción si existe una fila de base de datos con el mismo identificador ReplicationMode.LATEST_VERSION - sobrescribe la fila si su número de versión es anterior al número de versión del objeto, o en caso contrario ignora el objeto Los casos de uso para esta funcionalidad incluyen reconciliar datos ingresados en instancias diferentes de bases de datos, actualizar información de configuración de sistema durante actualizaciones de producto, deshacer cambios producidos durante transacciones no-ACID y más. Limpiando (flushing) la sesión Cada tanto, la Session ejecutará las sentencias SQL necesarias para sincronizar el estado de la conexión JDBC con el estado de los objetos mantenidos en menoria. Este proceso, limpieza (flush), ocurre por defecto en los siguientes puntos antes de algunas ejecuciones de consulta desde org.hibernate.Transaction.commit() desde Session.flush() Las sentencias SQL son liberadas en el siguiente orden todas las inserciones de entidades, en el mismo orden que los objetos correspondientes fueron salvados usando Session.save() todas las actualizaciones de entidades todas los borrados de colecciones todos los borrados, actualizaciones e inserciones de elementos de colección todas las inserciones de colecciones todos los borrados de entidades, en el mismo orden que los objetos correspondientes fueron borrados usando Session.delete() (Una excepción es que los objetos que usan generación de ID native se insertan cuando son salvados.) Excepto cuando llamas explícitamente a flush(), no hay en absoluto garantías sobre cuándo la Session ejecuta las llamadas JDBC. sólo sobre el orden en que son ejecutadas. Sin embargo, Hibernate garantiza que los métodos Query.list(..) nunca devolverán datos añejos o erróneos. Es posible cambiar el comportamiento por defecto de modo que la limpieza (flush) ocurra menos frecuentemente. La clase FlushMode tres modos diferentes: sólo en tiempo de compromiso (y sólo cuando se use la API de Transaction de Hibernate), limpieza automática usando la rutina explicada, o nunca limpiar a menos que se llame a flush() explícitamente. El último modo es útil para unidades de trabajo largas, donde una Session se mantiene abierta y desconectada por largo tiempo (ver ). Durante la limpieza, puede ocurrir una excepción (por ejemplo, si una operación DML violase una restricción). Ya que el manejo de excepciones implica alguna comprensión del comportamiento transaccional de Hibernate, lo discutimos en . Persistencia transitiva Es absolutamente incómodo dalvar, borrar, o reunir objetos individuales, especialmente si tratas con un grafo de objetos asociados. Un caso común es una relación padre/hijo. Considera el siguiente ejemplo: Si los hijos en una relación padre/hijo pudieran ser tipificados en valor (por ejemplo, una colección de direcciones o cadenas), sus ciclos de vida dependerían del padre y se requeriría ninguna otra acción para el tratamiento en "cascada" de cambios de estado. Cuando el padre es salvado, los objetos hijo tipificados en valor son salvados también, cuando se borra el padre, se borran los hijos, etc. Esto funciona incluso para operaciones como el retiro de un hijo de la colección. Hibernate detectará esto y, ya que los objetos tipificados en valor no pueden tener referencias compartidas, borrará el hijo de la base de datos. Ahora considera el mismo escenario con los objetos padre e hijos siendo entidades, no tipos de valor (por ejemplo, categorías e ítems, o gatos padre e hijos). Las entidades tienen su propio ciclo de vida, soportan referencias compartidas (de modo que quitar una entidad de una colección no significa que sea borrada), y no hay por defecto ningún tratamiento en "cascada" de estado de una entidad a otras entidades asociadas. Hibernate no implementa persistencia por alcance. Para cada operación básica de la sesión de Hibernate - incluyendo persist(), merge(), saveOrUpdate(), delete(), lock(), refresh(), evict(), replicate() - hay un estilo de cascada correspondiente. Respectivamente, los estilos de cascada se llaman create, merge, save-update, delete, lock, refresh, evict, replicate. Si quieres que una operación sea tratada en cascada a lo largo de una asociación, debes indicar eso en el documento de mapeo. Por ejemplo: ]]> Los estilos de cascada pueden combinarse: ]]> Puedes incluso usar cascade="all" para especificar que todas las operaciones deben ser tratadas en cascada a lo largo de la asociación. El por defecto cascade="none" especifica que ninguna operación será tratada en cascada. Un estilo de cascada especial, delete-orphan, se aplica sólo a asociaciones uno-a-muchos, e indica que la operación delete() debe aplicarse a cualquier objeto hijo que sea quitado de la asociación. Recomendaciones: Usualmente no tiene sentido habilitar el tratamiento en cascada a una asociación <many-to-one> o <many-to-many>. El tratamiento en cascada es frecuentemente útil para las asociaciones <one-to-one> y <one-to-many>. associations. Si la esperanza de vida de los objetos hijos está ligada a la eesperanza de vida del objeto padre, házlo un objeto de ciclo de vida especificando cascade="all,delete-orphan". En otro caso, puede que no necesites tratamiento en cascada en absoluto. Pero si piensas que estarás trabajando frecuentemente con padre e hijos juntos en la misma transacción, y quieres ahorrarte algo de tipeo, considera usar cascade="persist,merge,save-update". Mapear una asociación (ya sea una asociación monovaluada, o una colección) con cascade="all" marca la asociación como una relación del estilo padre/hijo donde save/update/delete en el padre resulta en save/update/delete del hijo o hijos. Además, una mera referencia a un hijo desde un padre persistente resultará en un save/update del hijo. Esta metáfora está incompleta, sin embargo. Un hijo que deje de ser referenciado por su padre no es borrado automáticamente, excepto en el caso de una asociación <one-to-many> mapeada con cascade="delete-orphan". La semántica precisa de las operaciones en cascada para una relación padre/hijo es: Si un padre le es pasado a persist(), todos los hijos le son pasados a persist() Si un padre le es pasado a merge(), todos los hijos le son pasados a merge() Si un padre le es pasado a save(), update() o saveOrUpdate(), todos los hijos le son pasados a saveOrUpdate() Si un hijo transitorio o separado se vuelve referenciado por un padre persistente, le es pasado a saveOrUpdate() Si un padre es borrado, todos los hijos le son pasados a delete() Si un hijo deja de ser referenciado por un padre persistente, no ocurre nada especial - la aplicación debe borrar explícitamente el hijo de ser necesario - a menos que cascade="delete-orphan", en cuyo caso el hijo "huérfano" es borrado. Usando metadatos Hibernate requiere de un modelo de meta-nivel muy rico de todas las entidades y tipos de valor. De vez en cuando, este modelo es muy útil para la aplicación misma. Por ejemplo, la aplicación podría usar los metadatos de Hibernate para implementar un algoritmo "inteligente" de copia en profundidad que entienda qué objetos deben ser copiados (por ejemplo, tipo de valor mutables) y cuáles no (por ejemplo, tipos de valor inmutables y, posiblemente, entidades asociadas). Hibernate expone los metadatos vía las interfaces ClassMetadata y CollectionMetadata y la jerarquía Type. Las instancias de las interfaces de metadatos pueden obtenerse de SessionFactory.