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.
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:
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();
}
}
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.
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:
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();
}
}
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>
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...
}
});
}
}
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 propiedadjtaTransactionManager
para su beanLocalSessionFactoryBean
(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 llamadaafterCompletion
.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 unclose()
llamada a la sesión de Hibernate, lo que hará que Hibernate intente una conexión JDBCclose()
.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 queConnection
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 elbeforeCompletion
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é.
GO TO FULL VERSION