Proyecto de Integración
 

Migración del proyecto a Spring

Introducción

Durante las dos próximas sesiones vamos a migrar el código de los proyectos ya creados al framework Spring. Con esto perseguimos varios objetivos:

  • Simplificar el código sustituyendo las factorías de objetos por el contenedor de Spring. Los Bos y Eaos serán beans de Spring y por tanto su ciclo de vida lo gestionará éste.
  • Eliminar la necesidad de gestionar las transacciones de manera manual, pasando a usar transaccionalidad declarativa en la capa de negocio.
  • Facilitar la validación de datos en la capa web, y una posible internacionalización de la aplicación.
  • Y por último, pero no lo menos importante: aprender cómo se implementa el proyecto con un stack de tecnologías alternativo, y cuáles son las diferencias y semejanzas con el oficial.

Nuevos proyectos

Vamos a crear proyectos nuevos, aunque aprovecharemos gran parte del código. Probablemente la forma más sencilla de hacerlo sea:

  1. Partiendo del proyecto "padre" original, proyint-jbib, con botón derecho hacer "Copy" y luego "Paste", poniéndole como nuevo nombre proyint-jbib-spring. Por desgracia, esto no renombra las carpetas de los módulos ni cambia el contenido de los pom.xml, por lo que tendremos que arreglarlo manualmente.
  2. Desconectar el nuevo proyecto del SVN (Team > Disconnect), para evitar sobreescribir accidentalmente el original, ya que estará conectado al repositorio original del antiguo proyecto.
  3. Editar el pom.xml del nuevo proyecto padre proyint-jbib-spring, para:
    • Cambiar el artifactId y el name por proyint-jbib-spring
    • Cambiar los nombres de todos los módulos para ponerles el sufijo "-spring"
  4. Cambiar de nombre las carpetas de los nuevos proyectos hijos, añadiéndoles el sufijo "-spring". Por ejemplo, jbib-modelo pasará a ser jbib-modelo-spring.
  5. Editar los pom.xml de cada uno de los hijos. Esto podemos hacerlo de una sola vez, o a medida que vamos refactorizando el código para migrar el proyecto a Spring:
    • Cambiar el artifactId del "parent" por proyint-jbib-spring
    • Cambiar el artifactId y el name para añadirles el sufijo "-spring"
    • Actualizar las dependencias de los otros módulos. Por ejemplo, el nuevo jbib-negocio-spring pasará a depender de jbib-modelo-spring.

Modelo de dominio y capa de acceso a datos: proyecto jbib-modelo-spring

Cambios en la configuración: pom.xml

Además de los cambios que ya hemos hecho del name, artifactId, etc, debemos introducir las dependencias de varios módulos de Spring. Debéis añadir dentro de <dependencies>:


<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
	<version>3.0.6.RELEASE</version>
</dependency>

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-orm</artifactId>
	<version>3.0.6.RELEASE</version>
</dependency>

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-test</artifactId>
	<version>3.0.6.RELEASE</version>
	<scope>test</scope>
</dependency>

Cambios en los EAOs

Vamos a hacer dos cambios fundamentales en las clases Eao:

  • Van a ser beans de Spring, y por tanto su ciclo de vida lo va a controlar este, por ello:
    1. Anotaremos los Eao con @Repository
    2. Les añadiremos un constructor sin parámetros, ya que es el que va a usar Spring para instanciar el objeto.
  • Le especificaremos a Spring que debe inyectarles el EntityManager a través del setter, anotándolo con @PersistenceContext:
    @PersistenceContext
    public void setEntityManager(EntityManager em) {
    	this.em = em;
    }
    

Al hacer esto último , la clase PersistenceManager ya no es necesaria, ya que ahora el responsable de "fabricar" Entity Managers va a ser el propio Spring, que se los pasará a los Eao.

Vamos a convertir también BibliotecaBR en un bean de Spring. Anotadla con @Service. Podéis eliminar la variable de instancia "me" y el método "getInstance". Además tendréis que convertir el constructor en "public", para que pueda ser llamado desde Spring.

Cambios en las pruebas unitarias

Lo primero que debemos hacer es crear un fichero de configuración de beans de Spring para configurar estas pruebas. En la carpeta src/test/resources crearemos un eaos-test.xml con el siguiente contenido: (recuerda que si estás usando STS puedes usar New > Spring Bean Configuration File):


<?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:jee="http://www.springframework.org/schema/jee" 
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:p="http://www.springframework.org/schema/p"
	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/jee 
          http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
          http://www.springframework.org/schema/tx 
          http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

	<!-- para que Spring le haga caso a las anotaciones -->
	<context:component-scan base-package="org.especialistajee.jbib" />
	<context:annotation-config/>

	<!-- para obtener el Entity Manager por inyección de dependencias -->
	<bean id="miEMF"
		class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
		<property name="persistenceUnitName" value="proyint_test_entidades" />
	</bean>

	<!-- soporte de transacciones para poder anular las operaciones hechas en 
		las pruebas -->
	<tx:annotation-driven transaction-manager="miTransactionManager" />

	<bean id="miTransactionManager" 
	      class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="miEMF" />
	</bean>
</beans>

En el archivo anterior hacemos básicamente tres cosas, las dos primeras son las más importantes:

  • Le decimos a Spring que examine el paquete org.especialistajee.jbib y subpaquetes en busca de anotaciones de beans.
  • Definimos un LocalEntityManagerFactoryBean, que es el responsable de inyectar los EntityManager
  • Finalmente, definimos un gestor de transacciones. De este modo, podríamos anular si así lo deseamos los datos insertados o modificados en las pruebas con un simple rollback para no "ensuciar" la BD. Aunque en nuestro caso esto no es necesario, no está de más ver cómo funciona.

Vamos ahora a cambiar el código fuente de las pruebas para que se adapten a la nueva situación. Lo primero es modificar la inicialización de los datos de pruena. Vamos a inicializar la base de datos con un script SQL en lugar de hacerlo en el método initialize de EaoTest. Por tanto, eliminaremos dicho método y pondremos en src/test/sql el nuevo script biblioteca_test.sql

Además, como inicializamos la base de datos con SQL y no con Java, no nos hace falta en la unidad de persistencia especificarle a Hibernate que queremos crear las tablas cada vez que arrancamos. Podemos editar el persistence.xml y eliminar de la unidad de persistencia proyint_test_entidades la línea:

<property name="hibernate.hbm2ddl.auto" value="create" />
Métodos static e inyección de dependencias
Hacemos esto porque Spring no hace inyección de dependencias en métodos static, por lo que no podríamos obtener Entity Managers en el initialize. Bueno, podríamos, manteniendo el "antiguo" PersistenceManager, pero no tendría mucho sentido dejarlo solo para esto.

Por último, para que se ejecute el script SQL antes de los test, es necesario configurar el plugin sql de maven en el pom.xml. Introduciremos este código:

<build>
	<plugins>
		<plugin>
			<groupId>org.codehaus.mojo</groupId>
			<artifactId>sql-maven-plugin</artifactId>
			<version>1.4</version>

			<dependencies>
				<!-- specify the dependent JDBC driver here -->
				<dependency>
					<groupId>mysql</groupId>
					<artifactId>mysql-connector-java</artifactId>
					<version>5.0.8</version>
				</dependency>
			</dependencies>

			<!-- common configuration shared by all executions -->
			<configuration>
				<driver>com.mysql.jdbc.Driver</driver>
				<url>jdbc:mysql://localhost:3306/</url>
				<username>root</username>
				<password>especialista</password>
			</configuration>

			<executions>
				<execution>
					<id>create-data</id>
					<phase>process-test-resources</phase>
					<goals>
						<goal>execute</goal>
					</goals>
					<configuration>
						<srcFiles>
							<srcFile>src/test/sql/biblioteca_test.sql</srcFile>
						</srcFiles>
					</configuration>
				</execution>
			</executions>
		</plugin>
	</plugins>
</build>
Esto no va contigo, domain
En las pruebas que no se haga uso de beans de Spring ni de Entity Managers no es necesario hacer los cambios en el código que se describen a continuación. Por ejemplo, los test de los objetos de dominio no deberían depender de Spring.

Las clases de prueba debemos anotarlas adecuadamente para activar la inyección de dependencias de Spring. Por ejemplo, para EaoTest:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"file:src/test/resources/eaos-test.xml"})
@TransactionConfiguration(transactionManager = "miTransactionManager")
public class EaoTest {
   ...

Si quisiéramos anular las modificaciones de la BD hechas en las pruebas tendríamos que cambiar el @TransactionConfiguration por algo como:

@TransactionConfiguration(transactionManager = "miTransactionManager"
   ,defaultRollback = true)  

Y añadirle a la clase (o a todos los métodos) la anotación @Transactional. De este modo se haría automáticamente un rollback tras cada test.

Finalmente vamos a ver cómo cambiar el propio código de las pruebas. Vamos a ver como ejemplo parte de la clase EaoTest. Lo más importante es que le "digamos a Spring que nos pase las cosas" en lugar de buscarlas nosotros mismos. Por tanto:

  • En lugar de instanciar nosotros los Eao con new() en los test, definiremos variables de instancia y las anotaremos con @Autowired.
  • En lugar de obtener "manualmente" el Entity Manager, definiremos una variable de instancia de este tipo y la anotaremos con @PersistenceContext.

Por ejemplo, así quedaría el testUpdate de EaoTest:

@PersistenceContext
EntityManager em;

@Autowired
LibroEao lEao;
      
@Test
@Transactional
public void testUpdate() {	
	LibroDomain libro = lEao.findByIsbn("0321127420");
	libro.setNumPaginas(590);
	lEao.update(libro);
	// El eao gestiona entidades conectadas y no las desconecta tras un update
	assertTrue(em.contains(libro));
	libro = lEao.findByIsbn("0321127420");
	assertTrue(libro.getNumPaginas()==590);
}
Transaccionalidad y gestión del Entity Manager
Si un caso de uso implica varias operaciones de persistencia y usamos un EM "inyectado", el método debe ser @Transactional. Esto da un "contexto transaccional" para que el Entity Manager se ejecute y se mantenga "vivo" hasta el final de la transacción. En caso contrario, cada llamada a un eao inyectaría un "nuevo" em y se perdería el contexto de persistencia anterior.

Cambiad el resto de las pruebas según lo visto en el método testUpdate

Capa de negocio: proyecto jbib-negocio-spring

Cambios en la configuración: pom.xml

Además de los cambios que ya hemos hecho del name, artifactId, etc, debemos introducir las dependencias de varios módulos de Spring. Debéis añadir dentro de <dependencies>:

		
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
	<version>3.0.6.RELEASE</version>
</dependency>

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-test</artifactId>
	<version>3.0.6.RELEASE</version>
	<scope>test</scope>
</dependency>

Además aseguráos, como ya hemos dicho, que la dependencia del módulo de dominio es del nuevo proyecto jbib-modelo-spring.

Cambios en los BOs

Los cambios básicos van a ser similares a los hechos en los EAOs:

  • Anotar los Bos para que sean beans de Spring, en este caso @Service al ser de la capa de negocio. Anotad las clases, no los interfaces.
  • Si un Bo necesita de un Eao lo debe obtener con inyección de dependencias, no creándolo con new(). Por ejemplo, así podría quedar el inicio del método eliminarLibro de LibroBo (nos falta la transaccionalidad, que añadiremos en un momento):
    @Autowired
    LibroEao libroEao;
    
    public void eliminaLibro(String isbn) throws LibroException {
       try {
          LibroDomain libro = libroEao.findByIsbn(isbn);
    	  libroEao.delete(libro.getId());
          ... 
    
    Es decir, si necesitamos un Eao definimos una variable de instancia de esa clase, la anotamos con @Autowired y la usamos en donde haga falta, confiando en que Spring la habrá inicializado correctamente.

Por supuesto, tras hacer estos cambios, La FactoriaBOs no será necesaria.

Otro cambio importante es la gestión de la transaccionalidad. Ahora los Bos la hacen de forma "manual". Quitaremos todas las referencias a los "em" del código (las líneas que obtienen el em, inician una transacción y hacen commit o rollback) y anotaremos los métodos como @Transactional. En algunos casos ahora hacemos rollback si se produce una determinada excepción. Por tanto, configuraremos @Transactional para decir que queremos rollback automático en caso de esa excepción. Por ejemplo, esta sería la versión con transaccionalidad de eliminarLibro de LibroBo:

@Autowired
LibroEao libroEao;

@Transactional(rollbackFor=LibroException.class)
public void eliminaLibro(String isbn) throws LibroException {
	try {
		LibroDomain libro = libroEao.findByIsbn(isbn);
		libroEao.delete(libro.getId());
	} catch (Exception ex) {
		throw new LibroException("Error eliminando libro", ex);
	}	
}

Un efecto lateral de todo esto es que la capa de persistencia se independiza de la tecnología usada por la capa de dominio y acceso a datos. Podríamos pasar a Spring JDBC, iBatis o cualquier otra tecnología sin modificar para nada las clases de negocio. Cosa que antes no era posible ya que estábamos obligados a gestionar manualmente el entity manager y por tanto, "atados" al API JPA.

Cambios en las pruebas

Habrá que crear un fichero de configuración de beans de Spring para las pruebas. Crearlo en src/test/resources y llamarlo bos-test.xml. El contenido será igual que el de eaos-test.xml que teníamos en el módulo anterior, pero en el bean LocalEntityManagerFactoryBean tenemos que referenciar la unidad de persistencia que en su momento se creó para el proyecto de negocio:


<bean id="miEMF"
	class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
	<property name="persistenceUnitName" value="proyint_test_negocio" />
</bean>

Por otro lado, en el fuente de las pruebas haremos algo parecido a lo que hemos hecho en los propios Bos: eliminar todas las referencias al "em" y cuando necesitemos un Eao, obtenerlo con inyección de dependencias en lugar de con new(). Por ejemplo:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"file:src/test/resources/bos-test.xml"})
@TransactionConfiguration(transactionManager = "miTransactionManager")
public class BoTest {
   @Autowired
   IUsuarioBo uBo;
   @Autowired
   IOperacionBo oBo;
   @Autowired
   ILibroBo lBo;
   
   @Test
   public void recuperaUsuarioTest() {
      UsuarioDomain usuario = uBo.recuperaUsuario("antonio");
      assertTrue(usuario.getId().equals(2L));
   }

   @Test
   public void reservaLibroTest() {
      UsuarioDomain usuario = uBo.recuperaUsuario("patricia");
      oBo.realizaReserva(usuario.getId(), "097451408X");
      LibroDomain libro = lBo.recuperaLibro("097451408X");
      assertTrue(libro.getActiva().getTipo()
            .equals(TipoOperacion.RESERVA)
            && libro.getActiva().getUsuario().equals(usuario));
   }

   ...

Tendréis que modificar el resto del código vosotros mismos. Como se ve, también se elimina el método initialize de los test, que aquí carga la unidad de persistencia, ya que eso lo hace Spring.

Entrega

No es necesario hacer una entrega por separado de esta primera parte, la integración de Spring está dividida en dos partes por cuestiones de organización, pero constituye una única entrega.

Como ayuda visual se muestran los archivos a entregar en los proyectos modelo y de negocio. Son en realidad los mismos que teníamos (refactorizados a Spring) con la adición de los ficheros de beans, eaos-test.xml y bos-test.xml.