Comencemos cubriendo Hibernate 5 en el entorno Spring, usándolo para demostrar el enfoque de Spring para integrar mapeadores OR. Esta sección cubre muchas cuestiones en detalle y muestra varias opciones para implementar una DAO y demarcar transacciones. La mayoría de estos patrones se pueden transferir directamente a todas las demás herramientas ORM compatibles. Las secciones posteriores de este capítulo describen otras tecnologías ORM y brindan breves ejemplos.

A partir de Spring Framework 5.3, Spring requiere Hibernate ORM 5.2+ para HibernateJpaVendorAdapter de Spring, así como para la configuración nativa SessionFactory de Hibernate. Se recomienda encarecidamente utilizar Hibernate ORM 5.4 para aplicaciones que recién están comenzando. Para usar con HibernateJpaVendorAdapter, Hibernate Search debe actualizarse a la versión 5.11.6.

Configuración de SessionFactory en un contenedor Spring

Para evitar vincular objetos de aplicación a búsquedas de recursos codificadas, puede definir recursos (como DataSource de JDBC o SessionFactory de Hibernate) como beans en un contenedor Spring. Los objetos de aplicación que necesitan acceso a recursos recibirán referencias a dichas instancias predefinidas a través de referencias de beans, como se muestra en la definición de DAO en siguiente sección.

El siguiente extracto de la definición del contexto de la aplicación XML muestra cómo configurar DataSource desde JDBC y SessionFactory desde Hibernate encima:


<beans>
    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
        <property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    </bean>
    <bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <property name="dataSource" ref="myDataSource"/>
        <property name="mappingResources">
            <list>
                <value>product.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <value>
                hibernate.dialect=org.hibernate.dialect.HSQLDialect
            </value>
        </property>
    </bean>
</beans>

Movimiento desde el BasicDataSource local de Jakarta Commons DBCP al DataSource ubicado en JNDI (generalmente administrado por el servidor de aplicaciones), es solo una cuestión de configuración, como se muestra en el siguiente ejemplo:


<beans>
    <jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/>
</beans>

También puede acceder a SessionFactory ubicado en JNDI usando JndiObjectFactoryBean / <jee:jndi-lookup> de Spring para recibirlo y abrirlo. Sin embargo, esta generalmente no es una solución típica fuera del contexto de un EJB.

Spring también proporciona un LocalSessionFactoryBuilder opción que es fácil de interoperar con configuración basada en la anotación @Bean y la instalación programática (sin la participación de FactoryBean).

Ambos LocalSessionFactoryBean y LocalSessionFactoryBuilder admiten el arranque en segundo plano, donde la inicialización de Hibernate se produce en paralelo con el subproceso de arranque de la aplicación para un ejecutor de arranque determinado (por ejemplo, SimpleAsyncTaskExecutor). Para LocalSessionFactoryBean, esto está disponible a través de la propiedad bootstrapExecutor. El LocalSessionFactoryBuilder programático tiene un método buildSessionFactory sobrecargado que toma un argumento de ejecutor de arranque.

A partir de Spring Framework 5.1, esta configuración nativa de Hibernate también puede exponer EntityManagerFactory de JPA para una interacción JPA típica junto con acceso nativo a Hibernate. Para obtener más información, consulte "Configuración de hibernación nativa para JPA".

Implementación de DAO basada en la sencilla API de Hibernate

Hibernate tiene una característica llamada sesiones contextuales, en las que Hibernate administra una Session actual para cada transacción. Esto equivale aproximadamente a sincronizar una Session de Hibernate para cada transacción en Spring. La implementación DAO correspondiente es similar al siguiente ejemplo, basado en la API de Hibernate normal:

Java

public class ProductDaoImpl implements ProductDao {
    private SessionFactory sessionFactory;
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
    public Collection loadProductsByCategory(String category) {
        return this.sessionFactory.getCurrentSession()
                .createQuery("from test.Product product where product.category=?")
                .setParameter(0, category)
                .list();
    }
}
Kotlin

class ProductDaoImpl(private val sessionFactory: SessionFactory) : ProductDao {
    fun loadProductsByCategory(category: String): Collection<*> {
        return sessionFactory.currentSession
                .createQuery("from test.Product product where product.category=?")
                .setParameter(0, category)
                .list()
    }
}

Este estilo es similar al estilo de la documentación de referencia y los ejemplos de Hibernate, excepto que SessionFactory se almacena en una variable de instancia. Recomendamos encarecidamente utilizar esta configuración basada en instancias en lugar de la antigua clase HibernateUtil de la aplicación de muestra CaveatEmptor Hibernate. (En general, no debe almacenar recursos en variables estáticas a menos que sea absolutamente necesario).

El ejemplo anterior de DAO sigue el patrón de inyección de dependencia. Encaja perfectamente en un contenedor Spring IoC, tal como lo haría si estuviera escrito en base a la plantilla HibernateTemplate de Spring. También es posible configurar dicho DAO en Java normal (por ejemplo, en pruebas unitarias). Para hacer esto, cree una instancia y llame a setSessionFactory(..) con la referencia deseada a la fábrica. Como definición de Spring Bean, el objeto DAO se vería así:

        
<beans>
    <bean id="myProductDao" class="product.ProductDaoImpl">
        <property name="sessionFactory" ref="mySessionFactory"/>
    </bean>
</beans>

La principal ventaja de este estilo de DAO es que depende únicamente de la API de Hibernate. No es necesario importar ninguna clase Spring. Esto es atractivo desde una perspectiva no invasiva y puede parecer más natural para los desarrolladores de Hibernate.

Sin embargo, el DAO genera una HibernateException (que no está marcada, por lo que no necesita ser declarado o capturado), lo que significa que las personas que llaman sólo pueden tratar con aquellas excepciones que se consideran principalmente críticas si quieren evitar la dependencia de la propia jerarquía de excepciones de Hibernate. Es imposible determinar causas específicas (como una falla de bloqueo optimista) sin vincular el programa de llamada a la estrategia de implementación. Esta compensación puede ser aceptable para aplicaciones que dependen en gran medida de Hibernate, que no necesitan un manejo especial de excepciones, o ambas cosas.

Afortunadamente, LocalSessionFactoryBean de Spring es compatible con SessionFactory. getCurrentSession() de Hibernate para cualquier estrategia transaccional de Spring, que devuelve la Session transaccional actual administrada por Spring, incluso cuando se usa HibernateTransactionManager. La lógica predeterminada para este método es devolver la Session actual asociada con la transacción JTA actual, si corresponde. Esta lógica se aplica ya sea que utilice JtaTransactionManager de Spring, transacciones administradas por contenedor (transacciones CMT) de EJB o la interfaz JTA.

En general, puede implementar objetos DAO basados en la API de Hibernate normal, pero aún así poder usarlas en transacciones administradas por Spring.

Distinción de transacciones declarativas

Recomendamos usar el soporte de transacciones declarativas de Spring, que permitirá reemplazar llamadas API explícitas para delimitar transacciones en código Java con un interceptor de transacciones AOP. Puede configurar este interceptor de transacciones en un contenedor Spring utilizando anotaciones Java o XML. Esta capacidad de utilizar transacciones declarativas permite a los servicios empresariales liberar código de demarcación de transacciones repetitivas y centrarlo en agregar lógica empresarial que realmente importe a la aplicación.

Antes de continuar, recomendamos encarecidamente leer la sección "Gestión de transacciones declarativas" si aún no lo has hecho. .

Puede marcar la capa de servicio con anotaciones @Transactional e indicarle al contenedor Spring que busque estas anotaciones y proporcione semántica transaccional para estos métodos anotados. El siguiente ejemplo muestra cómo hacer esto:

Java

public class ProductServiceImpl implements ProductService {
    private ProductDao productDao;
    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }
    @Transactional
    public void increasePriceOfAllProductsInCategory(final String category) {
        List productsToChange = this.productDao.loadProductsByCategory(category);
        // ...
    }
    @Transactional(readOnly = true)
    public List<Product> findAllProducts() {
        return this.productDao.findAllProducts();
    }
}
Kotlin

class ProductServiceImpl(private val productDao: ProductDao) : ProductService {
    @Transactional
    fun increasePriceOfAllProductsInCategory(category: String) {
        val productsToChange = productDao.loadProductsByCategory(category)
        // ...
    }
    @Transactional(readOnly = true)
    fun findAllProducts() = productDao.findAllProducts()
}

En el contenedor, debe configurar la implementación PlatformTransactionManager (como un bean) y la entrada <tx:annotation-driven/> , permitiendo el procesamiento de annotations @Transactional en tiempo de ejecución. El siguiente ejemplo muestra cómo hacer esto:

 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- SessionFactory, DataSource, etc. omitted -->
    <bean id="transactionManager"
            class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <tx:annotation-driven/>
    <bean id="myProductService" class="product.SimpleProductService">
        <property name="productDao" ref="myProductDao"/>
    </bean>
</beans>

Delimitación de transacciones de software

Puede desacoplar transacciones en un nivel de aplicación superior, además de los servicios de acceso a datos en niveles de nivel inferior que cubren cualquier número de operaciones. Tampoco existen restricciones en la implementación del servicio empresarial circundante. Solo necesita PlatformTransactionManager de Spring. Nuevamente, este último se puede obtener desde cualquier lugar, pero sería preferible que estuviera en forma de referencia al bean a través del método setTransactionManager(..). Además, productDAO debe establecerse mediante el método setProductDao(..). Los siguientes fragmentos muestran el administrador de transacciones y la definición del servicio empresarial en el contexto de una aplicación Spring y una implementación de ejemplo de un método empresarial:


<beans>
    <bean id="myTxManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="mySessionFactory"/>
    </bean>
    <bean id="myProductService" class="product.ProductServiceImpl">
        <property name="transactionManager" ref="myTxManager"/>
        <property name="productDao" ref="myProductDao"/>
    </bean>
</beans>
Java

public class ProductServiceImpl implements ProductService {
    private TransactionTemplate transactionTemplate;
    private ProductDao productDao;
    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }
    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }
    public void increasePriceOfAllProductsInCategory(final String category) {
        this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            public void doInTransactionWithoutResult(TransactionStatus status) {
                List productsToChange = this.productDao.loadProductsByCategory(category);
                // aumentar precios...
            }
        });
    }
}
Kotlin

class ProductServiceImpl(transactionManager: PlatformTransactionManager,
                        private val productDao: ProductDao) : ProductService {
    private val transactionTemplate = TransactionTemplate(transactionManager)
    fun increasePriceOfAllProductsInCategory(category: String) {
        transactionTemplate.execute {
            val productsToChange = productDao.loadProductsByCategory(category)
            // aumentar precios...
        }
    }
}

TransactionInterceptor de Spring permite que cualquier excepción marcada de la aplicación se genere junto con el código de devolución de llamada, mientras que TransactionTemplate se limita a excepciones no marcadas dentro de la devolución de llamada. TransactionTemplate desencadena una reversión en caso de una excepción de aplicación no marcada o si la aplicación marca la transacción para revertirse (al configurar TransactionStatus). De forma predeterminada, TransactionInterceptor se comporta igual, pero le permite configurar una política de reversión para cada método.

Estrategias de gestión de transacciones

Y TransactionTemplate y TransactionInterceptor delegan el procesamiento de transacciones real a una instancia de PlatformTransactionManager (que podría ser un HibernateTransactionManager (para un SessionFactory de Hibernate), usando ThreadLocal Session que se llama "detrás de escena") o JtaTransactionManager (delegando al subsistema JTA del contenedor) para aplicaciones de Hibernate. Incluso puedes utilizar tu propia implementación PlatformTransactionManager. Pasar de la gestión de transacciones nativa de Hibernate a JTA (por ejemplo, cuando tiene requisitos de transacciones distribuidas para ciertas implementaciones de su aplicación) es solo una cuestión de configuración. Puede reemplazar el administrador de transacciones de Hibernate con la implementación de transacciones JTA de Spring. Tanto el código de demarcación de transacciones como el código de acceso a datos funcionan sin cambios porque utilizan API de administración de transacciones escritas.

En el caso de sesiones de Hibernate distribuidas en varias fábricas, las transacciones se pueden combinar con JtaTransactionManager como estrategia transaccional con múltiples definiciones LocalSessionFactoryBean. Luego, cada DAO recibirá una referencia específica a SessionFactory pasada a la propiedad del bean correspondiente. Si todas las fuentes de datos JDBC subyacentes son transaccionales y están en contenedores, un servicio empresarial podrá desacoplar transacciones entre cualquier número de DAO y cualquier número de fábricas de sesiones sin muchos problemas si se utiliza JtaTransactionManager como estrategia.

HibernateTransactionManager y JtaTransactionManager le permiten manejar el caché con elegancia a nivel de JVM usando Hibernate sin tener que buscar un administrador de transacciones en el contenedor o un conector JCA. (a menos que esté utilizando EJB para iniciar transacciones).

HibernateTransactionManager puede exportar Connection desde Hibernate basado en JDBC a un código de acceso JDBC normal para un específico Fuente de DataSource. Esta capacidad permite un desacoplamiento de alto nivel de transacciones de acceso a datos mixtos de Hibernate y JDBC completamente sin JTA, siempre que solo acceda a una base de datos. El HibernateTransactionManager abre automáticamente una transacción de Hibernate como una transacción JDBC si ha configurado el SessionFactory pasado con un DataSource a través del propiedad dataSource de la clase LocalSessionFactoryBean. Alternativamente, puede establecer explícitamente el DataSource para el cual se deben abrir transacciones a través de la propiedad dataSource de la clase HibernateTransactionManager.

Comparación de recursos administrados por contenedores y recursos definidos localmente

Puede cambiar entre un SessionFactory administrado por contenedor desde JNDI y uno definido localmente sin cambiar una sola línea de código de la aplicación. La cuestión de si se deben almacenar las definiciones de recursos en un contenedor o localmente en una aplicación depende en gran medida de la estrategia de transacción utilizada. En comparación con un SessionFactory local definido por Spring, registrar manualmente un SessionFactory desde JNDI no proporciona ningún beneficio. La implementación de SessionFactory a través del conector JCA de Hibernate proporciona el beneficio adicional de estar presente en la infraestructura de administración del servidor Java EE, pero no tiene ningún valor real más allá de eso.

El soporte de transacciones de Spring no es un contenedor -específico . Cuando se configura utilizando cualquier estrategia que no sea JTA, el soporte de transacciones también funciona en un entorno independiente o de prueba. El soporte de Spring para transacciones locales de un solo recurso es una alternativa liviana y con todas las funciones a JTA, especialmente en el caso típico de transacciones de una sola base de datos. Si utiliza beans de sesión EJB locales sin estado para gestionar transacciones, habrá una dependencia tanto del contenedor EJB como del JTA, incluso si accede sólo a una base de datos y utiliza sólo esos beans de sesión sin estado para proporcionar transacciones declarativas a través de contenedores gestionados. actas. El uso directo de JTA mediante programación también requiere un entorno Java EE. JTA incluye más que solo dependencias de contenedores en términos de la propia JTA y las instancias DataSource de JNDI. Para transacciones que no sean de Spring Hibernate administradas por JTA, debe usar el conector JCA de Hibernate o código transaccional adicional de Hibernate con TransactionManagerLookup configurado para un almacenamiento en caché adecuado a nivel de JVM.

Spring Las transacciones administradas pueden funcionar igual de bien con un SessionFactory definido localmente de Hibernate como con un DataSource local de JDBC, siempre que accedan a la misma base de datos. Por lo tanto, la estrategia de transacciones JTA de Spring solo debe usarse cuando existen requisitos de transacciones distribuidas. El conector JCA requiere primero pasos de implementación específicos del contenedor y (obviamente) herramientas de soporte JCA. Esta configuración requerirá más trabajo que si estuviera implementando una aplicación web simple con definiciones de recursos locales y transacciones administradas por Spring. Además, a menudo se requiere una edición Enterprise del contenedor si, por ejemplo, se utiliza WebLogic Express, que no proporciona JCA. Una aplicación Spring con recursos locales y transacciones que abarcan una única base de datos se ejecuta en cualquier contenedor web Java EE (sin JTA, JCA o EJB), como Tomcat, Resin o incluso Jetty. También puede reutilizar fácilmente un intermediario de este tipo en aplicaciones de escritorio o conjuntos de pruebas.

En general, si no utiliza beans EJB, limítese a configurar SessionFactory y localmente HibernateTransactionManager o JtaTransactionManager de Spring. Disfrutará de todos los beneficios, incluido el almacenamiento en caché de transacciones adecuado a nivel de JVM y las transacciones distribuidas, sin todos los inconvenientes de implementar contenedores. El registro JNDI de SessionFactory desde Hibernate a través del conector JCA solo es beneficioso cuando se usa junto con beans EJB.

Falsas advertencias del servidor de aplicaciones al usar Hibernate

En algunos entornos JTA con una implementación XADataSource muy estricta (actualmente algunas versiones de WebLogic Server y WebSphere), si Hibernate está configurado sin un despachador JTA transacciones para este entorno, pueden aparecer advertencias falsas o excepciones en el registro del servidor de aplicaciones. Estas advertencias o excepciones indican que la conexión a la que se accede ya no es válida o el acceso JDBC ya no es válido, quizás porque una transacción ya no está activa. Como ejemplo, aquí hay una excepción real de WebLogic:

java.sql.SQLException: la transacción ya no está activa; estado: 'Commitido'. No
se permite más acceso a JDBC dentro de esta transacción.

Otro problema común es la pérdida de conexión después de transacciones JTA si las sesiones de Hibernate (y posiblemente las conexiones JDBC subyacentes) no se cierran correctamente. De esta manera .

Estos problemas se pueden resolver haciendo que Hibernate sea compatible con el administrador de transacciones JTA con el que se sincroniza (junto con Spring). Hay dos opciones sobre cómo hacer esto:

  • Pasar su bean JtaTransactionManager de Spring a la configuración de Hibernate. La forma más sencilla es proporcionar una referencia de bean en la propiedad jtaTransactionManager para su bean LocalSessionFactoryBean (consulte "Configuración de transacciones de Hibernate"). Luego, Spring pondrá las estrategias JTA correspondientes a disposición de Hibernate.

  • También puede configurar explícitamente las propiedades de Hibernate relacionadas con JTA, específicamente "hibernate.transaction.coordinator_class", "hibernate.connection" .handling_mode" y posiblemente "hibernate.transaction.jta.platform" en "hibernateProperties", para LocalSessionFactoryBean (consulte el manual de Hibernate para obtener detalles sobre estas propiedades).

El resto de esta sección describe la secuencia de eventos que ocurren con y sin el hecho de que Hibernate sea compatible con PlatformTransactionManager de JTA.

Si Hibernate se configuró sin tener en cuenta el administrador de transacciones JTA, cuando se confirma una transacción JTA, ocurren los siguientes eventos:

  • La transacción JTA se confirma.

  • JtaTransactionManager de Spring está sincronizado con la transacción JTA, por lo que el administrador de transacciones JTA lo vuelve a llamar a través de la devolución de llamada afterCompletion.

  • Entre otras acciones, esta sincronización provoca una devolución de llamada de Spring a Hibernate a través de la devolución de llamada afterTransactionCompletion de Hibernate (utilizada para borrar el caché de Hibernate), seguida de un close() llamada a la sesión de Hibernate, lo que hará que Hibernate intente una conexión JDBC close().

  • En algunos entornos, esta llamada a Connection.close() provoca que se genere una advertencia o un error porque el servidor de aplicaciones ya no considera que Connection sea utilizable porque la transacción ya se ha confirmado.

Si Hibernate está configurado con un administrador de transacciones JTA en mente, se producen los siguientes eventos cuando se confirma una transacción JTA:

  • La transacción JTA está lista para confirmar.

  • JtaTransactionManager de Spring está sincronizado con la transacción JTA, por lo que el beforeCompletion del administrador de transacciones JTA vuelve a llamar a la transacción. callback.

  • Spring tiene en cuenta que Hibernate se sincroniza con la transacción JTA y comienza a comportarse de manera diferente que en el escenario anterior. En particular, se alinea con la transacción transaccional de Hibernate. gestión de recursos.

  • La transacción JTA está confirmada.

  • Hibernate se sincroniza con la transacción JTA para que se llame a la transacción de vuelta mediante la devolución de llamada afterCompletion del administrador de transacciones JTA y puede borrar correctamente su caché.