¿Son spring y seam frameworks incompatibles? ¿puedo aprovechar las ventajas que me ofrecen ambos en mis aplicaciones? Supongo que no seré el único que se plantea estas preguntas
En un proyecto interno sobre seam se nos planteó un grave problema, ¿cómo manejo de forma transparente y sencilla las transacciones distribuidas de mi aplicación? ¿tengo que implementar un manejador de transacciones propio? ¿qué problemas puede darme? ¿es escalable esta solución? Al final, buceando por la documentación de seam encontré como integrarlo con spring y me dió paso a implementar la solución que andaba buscando ![]()
Al principio optamos por una solución sencilla, la transacción de Hibernate englobará a la transacción de JackRabbit de la siguiente manera:
hibernateSession.beginTransaction() try{ // Bloque operaciones Hibernate jackrabbitSession.beginTransaction() // Bloque operaciones Jackrabbit jackrabbitSession.commit() hibernateSession.commit() }catch(Exception){ hibernateSession.rollbackTransaction() }
El código parece bueno pero es muy poco escalable. ¿Que sucedería si jackrabbit se nos queda pequeño y necesitamos utilizar otro JCR como puede ser Alfresco?, muy simple, aparte de cambiar la arquitectura de la aplicación deberíamos de cambiar todos los DAOs lo que nos llevaría a resignarnos y continuar con la implementación actual. Esta solución fue descartada por su escasa escalabilidad.
“Si la solución no es buena, ¿Qué hacemos?”
La solución pasa por la propagación de las transacciones de ambos sistemas utilizando un manejador de transacciones que hable el mismo idioma (JTA) y un framework que nos proporcione una cómoda integración (spring).
En el caso que nos ocupaba, nuestro servicio de negocio realiza el salvado de una nueva entidad en la BBDD y en el gestor de contenidos utilizando dos transacciones separadas, con lo cual ninguna de ellas conoce el estado de la otra y si en algún caso alguna de ellas realiza un rollback la otra no sería consciente de ello. Este problema puede resultar bastante traumático debido a que puede crear inconsistencias entre los datos almacenados en la BBDD y el gestor de contenidos.
Es por ello necesario englobar las dos transacciones en una y así poder controlar el estado de éstas y realizar las operaciones oportunas si alguna de ellas falla.
Seam posee un módulo de integración con spring bastante bueno y muy fácil de utilizar.
Para realizar la integración necesitamos los paquetes:
- jboss-seam-ioc: Modulo de integración entre los frameworks seam y spring
- spring: Framework spring completo
- aspectjweaver: Modulo de aspectj para la configuración de aspectos en spring (tx-advise)
- cglib-nodep: Modulo de generación de código en tiempo de ejecución utilizado por spring en la creación de los proxis que envuelven las clases.
El primer paso para configurar la integración de seam con spring es incluir el contexto de spring en el de seam añadiendo las siguientes líneas en el archivo components.xml:
<spring:context-loader config-locations="/WEB-INF/applicationContext.xml"> </spring:context-loader> <transaction:no-transaction />
En el primer nodo declaramos la localización del fichero de configuración de spring y en el segundo delegamos el control de las transacciones a spring al deshabilitarlas en seam.
El siguiente paso es realizar la configuración de spring creando el archivo xml “applicationContext.xml”:
Espacios de nombres utilizados:
<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" xmlns:seam="http://jboss.com/products/seam/spring-seam" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://jboss.com/products/seam/spring-seam http://jboss.com/products/seam/spring-seam-2.1.xsd">
El único a destacar es xmlns:seam el cual se utilizará para declarar un bean de spring como componente seam y poderlo inyectar en los demas componentes de la aplicación.
Para poder englobar las transacciones que ocurran en nuestra aplicación en la transacción controlada por Jboss es necesaria la creación de conectores JCA compatibles con Jboss. Estos conectores serán expuestos por el servidor de aplicaciones para ser utilizados desde nuestro entorno mediante la búsqueda por el nombre JNDI.
Jboss 5.1 posee un “desplegador” de fuentes de datos, solamente debemos de crear archivos *-ds.xml utilizando el dtd http://www.jboss.org/j2ee/dtd/jboss-ds_1_5.dtd y dejarlos en el directorio de despliegue. Este dtd posee la definición para configurar la transaccion a utilizar en nuestra fuente de datos.
Los archivos de configuración de fuentes de datos para Jboss serían:
JDBC:
<datasources> <local-tx-datasource> <jndi-name>myJdbcDS</jndi-name> <driver-class>org.postgresql.Driver</driver-class> <connection-url>jdbc:postgresql://localhost:myPsqlPort/myDDBB</connection-url> <user-name>myUser</user-name> <password>myPwd</password> <transaction-isolation>TRANSACTION_SERIALIZABLE</transaction-isolation> </local-tx-datasource> </datasources>
JackRabbit:
<connection-factories> <tx-connection-factory> <jndi-name>myJcrRepo</jndi-name> <xa-transaction /> <rar-name>jackrabbit-jca-1.5.6.rar</rar-name> <connection-definition>javax.jcr.Repository</connection-definition> <config-property name="homeDir" type="java.lang.String">myRepoLocation </config-property> <config-property name="configFile" type="java.lang.String">myRepo.xml </config-property> <config-property name="bindSessionToTransaction" type="java.lang.Boolean">true</config-property> <application-managed-security/> </tx-connection-factory> </connection-factories>
Al no ser un conector común, debemos decirle a Jboss que paquete “.rar” utilizar y la clase que lo implementa “javax.jcr.Repository”. El paquete jackrabbit-jca-1.5.6.rar lo pondremos dentro de la carpeta de despliegue.
Antes de arrancar el servidor de aplicaciones y ver que nuestros conectores están accesibles, es necesario copiar las librerias de jackrabbit y sus dependencias dentro del directorio “lib” del servidor que vamos a utilizar (comúnmente $JBOSS_HOME/server/default/lib)
Configuraremos Hibernate utilizando spring y obtendremos la fuente de datos utilizando JNDI. La configuración completa quedaría:
<bean id="jdbcXaDataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value="java:myJdbcDs" /> </bean> <bean id="hibernateSession" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="jdbcXaDataSource" /> <property name="mappingResources"> <list> <value>hbm/myFile.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <value>hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect </value> </property> </bean>
Para poder configurar el repositorio de JackRabbit desde spring, es necesario utilizar la librería springmodules-jcr. Como en el caso de Hibernate, la fuente de datos la obtendremos por JNDI desde el servidor de aplicaciones. La configuración quedaría:
<bean id="repositoryCFB" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value="java:myJcrRepo" /> </bean> <bean id="jcrCredentials" class="javax.jcr.SimpleCredentials"> <constructor-arg index="0" value="admin" /> <constructor-arg index="1"> <bean factory-bean="jcrCredentialsPassword" factory-method="toCharArray" /> </constructor-arg> </bean> <bean id="jcrCredentialsPassword" class="java.lang.String"> <constructor-arg index="0" value="password" /> </bean> <bean id="jcrSessionFactory" class="org.springmodules.jcr.JcrSessionFactory"> <property name="repository" ref="repositoryCFB" /> <property name="credentials" ref="jcrCredentials" /> <property name="sessionHolderProviderManager"> <bean> <property name="providers"> <list> <bean class="org.springmodules.jcr.jackrabbit.support.JackRabbitSessionHolderProvider"/> </list> </property> </bean> </property> <seam:component /> </bean>
Como las sesiones las va a manejar spring, es muy recomendable el uso de la plantilla JCR que nos proporciona el módulo de spring y así delegar en ella el control de las sesiones de nuestro repositorio y evitar posibles problemas a la hora de sincronizarlas.
<bean id="jcrTemplate" class="org.springmodules.jcr.JcrTemplate"> <property name="sessionFactory" ref="jcrSessionFactory" /> <property name="allowCreate" value="true" /> </bean>
Ya tenemos configurados los orígenes de datos, y ahora ¿cómo gestionamos ambas transacciones? Spring nos permite configurar manejadores de transacciones de manera trasparente a las fuentes de datos y será el encargado de englobarlas en un mismo entorno. Este entorno transaccional será el que nos aporta nuestro servidor de aplicaciones (para el cual hemos definido las fuentes de datos correspondientes), lo obtendremos creando el siguiente bean dentro del contexto de spring:
<bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManagerName"> <value>java:/TransactionManager</value> </property> </bean>
La configuración del aspecto que nos manejará las transacciones que ocurran en nuestra aplicación será:
<tx:advice id="txAdvice" transaction-manager="jtaTransactionManager"> <tx:attributes> <tx:method name="*" /> <tx:method name="get*" read-only="true"/> </tx:attributes> </tx:advice>
El último paso es definir qué clases/métodos queremos que sean transaccionales mediante el uso de puntos de ruptura:
<aop:config proxy-target-class="true"> <aop:pointcut expression="execution(* com.s2grupo.sample..*(..))" id="txServicePointCut" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="txServicePointCut" /> </aop:config>
Para que los puntos de ruptura sean efectivos es necesario agregar las clases afectadas al contexto de spring. Como estas clases son utilizadas dentro del contexto de seam añadiremos el nodo <seam:component/> dentro de cada una de ellas. La configuración quedará:
<!-- Service --> <bean id="repositoryService"> <property name="repositoryManager" ref="repositoryManager"></property> <seam:component name="repositoryService" intercept="false" /> </bean> <bean id="repositoryManager" class="com.s2grupo.sample.RepositoryManager" init-method="create" destroy-method="close"> <property name="repositoryConfiguration" ref="repositoryConfiguration"></property> <property name="jcrTemplate" ref="jcrTemplate"></property> <seam:component name="repositoryManager" /> </bean> <bean id="repositoryConfiguration" class="com.s2grupo.sample.RepositoryConfiguration" init-method="create" destroy-method="close"> <property name="jcrTemplate" ref="jcrTemplate"></property> <seam:component name="repositoryConfiguration" /> </bean> <bean id="myService" class="com.s2grupo.sample.MyService"> <property name="myDao" ref="myDao"></property> <seam:component name="myService" /> </bean> <!-- Data --> <bean id="abstractDao" abstract="true"> <property name="hibernateSessionFactory" ref="hibernateSession"></property> </bean> <bean id="myDao" parent="abstractDao"> <seam:component name="myDao" /> </bean>
Y con esto tendríamos todas las transacciones controladas y no perderíamos así la integridad de los datos de nuestra aplicación.
Espero que esta entrada os sea tan útil como lo fue para mí. Y ahora ... comentarios!!!












noviembre 17th, 2009 - 14:08
Muy buen artículo, si señor!
noviembre 17th, 2009 - 20:05
de PM!
Eso si, hay que tener en cuenta lo que implica JTA… bases de datos con soporte XA… servidores de aplicaciones completos (nada de tomcat/jetty) o, si no hay de eso, al menos un JTA externo (JOTM, atomikos…)
noviembre 17th, 2009 - 23:39
Gracias Raúl, y mucha razón Francisco
Al utilizar como servidor de aplicaciones JBoss desde un inicio descarté otros caminos por la simplicidad de la configuración descrita en la entrada.
Gracias por vuestros comentarios