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é.