

Unos buenos motivos para optar tanto por Spring como por los EJB es que son soluciones ya desarrolladas con lo que solo tendremos que integrarlo en nuestros proyectos y no tendremos que preocuparnos de mantener nuestra solución en caso de que tenga errores, además ambas son ampliamente usadas incluso en proyectos grandes y complejos y están ya probadas lo que es una garantía. Entre optar por Spring o los EJB depende de varios factores como puede ser si la aplicación va ha ser desplegada en un servidor de aplicaciones con soporte para EJB (como JBoss/Wildfly, Geronimo, ...) o no (Tomcat, Jetty) o de nuestras preferencias entre ambas opciones. En esta entrada explicaré como integrar Spring con el framework Apache Tapestry y como hacer uso de las transacciones de Spring en los servicios que contienen la lógica de la aplicación.
Primeramente, decir que en cierta medida la funcionalidad proporcionada por el contenedor de dependencias de Tapestry y el contenedor de dependencias de Spring se solapan, ambos proporcionan Inversion of Control (IoC). Pero el contenedor de dependencias de Tapestry tiene algunas ventajas como permitir configuración distribuida, esto hace referencia a que cada librería jar puede contribuir con su configuración al contenedor de dependencias y que la configuración se hace mediante código Java en vez de xml como en Spring con la ventaja de que es más rápido, tenemos la ayuda del compilador para detectar errores y el lenguaje Java es más adecuado para expresar la construcción de objetos. De modo que si podemos es mejor usar el contenedor de Tapestry que el de Spring, sin embargo, Spring ofrece un montón de funcionalidades muy útiles y esto nos puede obligar a usar el contenedor de Spring para ciertos objetos. Una de ellas son las transacciones para cumplir con las reglas ACID de las bases de datos relacionales, para ello deberemos definir en el contenedor de Spring (y no en el de Tapestry) los servicios con la lógica de negocio con necesidades transaccionales y las dependencias referidas por esos servicios en la configuración del contexto de Spring. A pesar de todo en los demás casos podemos optar por la opción que prefiramos ya que tanto a los servicios de Spring se les pueden inyectar dependencias del contenedor de Tapestry y, el caso contrario, a los servicios de Tapestry se les pueden inyectar servicios de Spring.
Veamos en código un ejemplo de como conseguir integración entre Tapestry y Spring. La primera cosa que cambia es que hay que usar un filtro de Tapestry especial para integrarse con Spring con lo que deberemos modificarlo en el archivo web.xml. Si normalmente usamos el filtro org.apache.tapestry5.TapestryFilter para que Tapestry procese las peticiones que llegan a la aplicación, integrándonos con Spring usaremos un filtro especial, org.apache.tapestry5.spring.TapestrySpringFilter. También mediante una propiedad de contexto indicaremos el (o los) xml con la definición de los beans de Spring.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" | |
version="3.0"> | |
<display-name>PlugInTapestry Tapestry 5 Application</display-name> | |
<context-param> | |
<!-- The only significant configuration for Tapestry 5, this informs Tapestry of where to look for pages, components and mixins. --> | |
<param-name>tapestry.app-package</param-name> | |
<param-value>es.com.blogspot.elblogdepicodev.plugintapestry</param-value> | |
</context-param> | |
<context-param> | |
<param-name>contextConfigLocation</param-name> | |
<param-value>classpath:/applicationContext.xml</param-value> | |
</context-param> | |
<filter> | |
<filter-name>app</filter-name> | |
<filter-class>org.apache.tapestry5.spring.TapestrySpringFilter</filter-class> | |
</filter> | |
<filter-mapping> | |
<filter-name>app</filter-name> | |
<url-pattern>/*</url-pattern> | |
<dispatcher>REQUEST</dispatcher> | |
<dispatcher>ERROR</dispatcher> | |
</filter-mapping> | |
<error-page> | |
<error-code>404</error-code> | |
<location>/error404</location> | |
</error-page> | |
<error-page> | |
<error-code>500</error-code> | |
<location>/error500</location> | |
</error-page> | |
<session-config> | |
<tracking-mode>COOKIE</tracking-mode> | |
</session-config> | |
</web-app> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" | |
xmlns:aop="http://www.springframework.org/schema/aop" | |
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd | |
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd | |
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd | |
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> | |
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> | |
<property name="driverClassName" value="org.h2.Driver" /> | |
<property name="url" value="jdbc:h2:mem:test" /> | |
<property name="username" value="sa" /> | |
<property name="password" value="sa" /> | |
</bean> | |
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> | |
<property name="dataSource" ref="dataSource" /> | |
<property name="packagesToScan" value="es.com.blogspot.elblogdepicodev.plugintapestry.entities" /> | |
<property name="hibernateProperties"> | |
<props> | |
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop> | |
<prop key="hibernate.hbm2ddl.auto">create</prop> | |
<!-- Debug --> | |
<prop key="hibernate.generate_statistics">true</prop> | |
<prop key="hibernate.show_sql">true</prop> | |
</props> | |
</property> | |
</bean> | |
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> | |
<property name="sessionFactory" ref="sessionFactory" /> | |
</bean> | |
<bean id="productoDAO" class="es.com.blogspot.elblogdepicodev.plugintapestry.services.dao.ProductoDAOImpl" /> | |
<context:annotation-config /> | |
<tx:annotation-driven transaction-manager="transactionManager" /> | |
</beans> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package es.com.blogspot.elblogdepicodev.plugintapestry.services.hibernate; | |
import org.apache.tapestry5.hibernate.HibernateSessionSource; | |
import org.hibernate.Session; | |
import org.hibernate.SessionFactory; | |
import org.hibernate.cfg.Configuration; | |
import org.springframework.context.ApplicationContext; | |
import org.springframework.orm.hibernate4.LocalSessionFactoryBean; | |
public class HibernateSessionSourceImpl implements HibernateSessionSource { | |
private SessionFactory sessionFactory; | |
private Configuration configuration; | |
public HibernateSessionSourceImpl(ApplicationContext context) { | |
this.sessionFactory = (SessionFactory) context.getBean("sessionFactory"); | |
// http://stackoverflow.com/questions/2736100/how-can-i-get-the-hibernate-configuration-object-from-spring | |
LocalSessionFactoryBean localSessionFactoryBean = (LocalSessionFactoryBean) context.getBean("&sessionFactory"); | |
this.configuration = localSessionFactoryBean.getConfiguration(); | |
} | |
@Override | |
public Session create() { | |
return sessionFactory.openSession(); | |
} | |
@Override | |
public SessionFactory getSessionFactory() { | |
return sessionFactory; | |
} | |
@Override | |
public Configuration getConfiguration() { | |
return configuration; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package es.com.blogspot.elblogdepicodev.plugintapestry.services; | |
... | |
public class AppModule { | |
public static void bind(ServiceBinder binder) { | |
// Añadir al contenedor de dependencias nuestros servicios, se proporciona la interfaz y la | |
// implementación. Si tuviera un constructor con parámetros se inyectarían como | |
// dependencias. | |
// binder.bind(Sevicio.class, ServicioImpl.class); | |
// Servicios de persistencia (definidos en Spring por la necesidad de que Spring gestione las transacciones) | |
// binder.bind(ProductoDAO.class, ProductoDAOImpl.class); | |
} | |
// Servicio que delega en Spring la inicialización de Hibernate, solo obtiene la configuración de Hibernate creada por Spring | |
public static HibernateSessionSource buildAppHibernateSessionSource(ApplicationContext context) { | |
return new HibernateSessionSourceImpl(context); | |
} | |
public static void contributeServiceOverride(MappedConfiguration<Class, Object> configuration, @Local HibernateSessionSource hibernateSessionSource) { | |
configuration.add(HibernateSessionSource.class, hibernateSessionSource); | |
} | |
... | |
public static void contributeBeanValidatorSource(OrderedConfiguration<BeanValidatorConfigurer> configuration) { | |
configuration.add("AppConfigurer", new BeanValidatorConfigurer() { | |
public void configure(javax.validation.Configuration<?> configuration) { | |
configuration.ignoreXmlConfiguration(); | |
} | |
}); | |
} | |
... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package es.com.blogspot.elblogdepicodev.plugintapestry.services.dao; | |
import java.io.Serializable; | |
import java.util.List; | |
import es.com.blogspot.elblogdepicodev.plugintapestry.misc.Pagination; | |
public interface GenericDAO<T> { | |
T findById(Serializable id); | |
List<T> findAll(); | |
List<T> findAll(Pagination paginacion); | |
long countAll(); | |
void persist(T entity); | |
void remove(T entity); | |
void removeAll(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package es.com.blogspot.elblogdepicodev.plugintapestry.services.dao; | |
import java.io.Serializable; | |
import java.util.List; | |
import org.hibernate.Criteria; | |
import org.hibernate.Query; | |
import org.hibernate.SessionFactory; | |
import org.hibernate.criterion.Order; | |
import org.hibernate.criterion.Projections; | |
import org.springframework.transaction.annotation.Propagation; | |
import org.springframework.transaction.annotation.Transactional; | |
import es.com.blogspot.elblogdepicodev.plugintapestry.misc.Pagination; | |
@SuppressWarnings({ "rawtypes", "unchecked" }) | |
public class GenericDAOImpl<T> implements GenericDAO<T> { | |
private Class clazz; | |
protected SessionFactory sessionFactory; | |
public GenericDAOImpl(Class<T> clazz, SessionFactory sessionFactory) { | |
this.clazz = clazz; | |
this.sessionFactory = sessionFactory; | |
} | |
@Override | |
@Transactional(readOnly = true) | |
public T findById(Serializable id) { | |
return (T) sessionFactory.getCurrentSession().get(clazz, id); | |
} | |
@Override | |
@Transactional(readOnly = true) | |
public List<T> findAll() { | |
return findAll(null); | |
} | |
@Override | |
@Transactional(readOnly = true) | |
public List<T> findAll(Pagination paginacion) { | |
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(clazz); | |
if (paginacion != null) { | |
List<Order> orders = paginacion.getOrders(); | |
for (Order order : orders) { | |
criteria.addOrder(order); | |
} | |
} | |
if (paginacion != null) { | |
criteria.setFirstResult(paginacion.getStart()); | |
criteria.setFetchSize(paginacion.getEnd() - paginacion.getStart() + 1); | |
} | |
return criteria.list(); | |
} | |
@Override | |
@Transactional(readOnly = true) | |
public long countAll() { | |
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(clazz); | |
criteria.setProjection(Projections.rowCount()); | |
return (long) criteria.uniqueResult(); | |
} | |
@Override | |
@Transactional(propagation = Propagation.REQUIRED) | |
public void persist(T object) { | |
sessionFactory.getCurrentSession().persist(object); | |
} | |
@Override | |
@Transactional(propagation = Propagation.REQUIRED) | |
public void remove(T object) { | |
sessionFactory.getCurrentSession().delete(object); | |
} | |
@Override | |
@Transactional(propagation = Propagation.REQUIRED) | |
public void removeAll() { | |
String hql = String.format("delete from %s", clazz.getName()); | |
Query query = sessionFactory.getCurrentSession().createQuery(hql); | |
query.executeUpdate(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
description = 'PlugInTapestry application' | |
apply plugin: 'eclipse' | |
apply plugin: 'java' | |
apply plugin: 'groovy' | |
apply plugin: 'war' | |
apply plugin: 'jetty' | |
apply plugin: 'tomcat' | |
group = 'es.com.blogspot.elblogdepicodev.plugintapestry' | |
version = '1.1' | |
... | |
dependencies { | |
// Tapestry | |
compile 'org.apache.tapestry:tapestry-core:5.4-alpha-23' | |
compile 'org.apache.tapestry:tapestry-hibernate:5.4-alpha-23' | |
compile 'org.apache.tapestry:tapestry-beanvalidator:5.4-alpha-23' | |
// Compresión automática de javascript y css en el modo producción | |
compile 'org.apache.tapestry:tapestry-webresources:5.4-alpha-23' | |
appJavadoc 'org.apache.tapestry:tapestry-javadoc:5.4-alpha-23' | |
// Spring | |
compile ('org.apache.tapestry:tapestry-spring:5.4-alpha-23') { | |
exclude(group: 'org.springframework') | |
} | |
compile 'org.springframework:spring-web:3.2.4.RELEASE' | |
compile 'org.springframework:spring-orm:3.2.4.RELEASE' | |
compile 'org.springframework:spring-tx:3.2.4.RELEASE' | |
compile 'commons-dbcp:commons-dbcp:1.4' | |
// Hibernate | |
compile 'org.hibernate:hibernate-core:4.2.7.SP1' | |
compile 'org.hibernate:hibernate-validator:5.0.1.Final' | |
compile 'com.h2database:h2:1.3.173' | |
... | |
} | |
... |
Si quieres probarlo en tu equipo lo puedes hacer de forma muy sencilla con los siguientes comandos y sin instalar nada. Si no dispones de git para clonar mi repositorio de GitHub puedes obtener el código fuente del repositorio en un archivo zip.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ git clone git://github.com/picodotdev/elblogdepicodev.git | |
$ cd elblogdepicodev/PlugInTapestry | |
$ ./gradlew tomcatRun | |
# Abrir en el navegador http://localhost:8080/PlugInTapestry/ |
Transacciones en Apache Tapestry
Persistencia con JPA y Apache Tapestry
Acceso a base de datos con Hibernate y JPA
Transaction Management (Spring)