Spring, JPA y DBUnit (2)

Vaya, olvidé ayer explicar un detalle interesante (no imprescindible para los que ya conozcáis JPA). Pero creo que lo correcto es completar el artículo y dejarlo “hecho, hecho”.

Se trata de la clase principal de nuestro dominio en el ejemplo: la entidad User.

@Entity
public class User {

@Id
String id;

@Column(unique=true)
private String username;

@Column
private String name;

@Column
private String surname;

public User(String username, String name, String surname) throws UserInitializationException {
this.username = username;
this.name = name;
this.surname = surname;
if ( username == null ) {
throw new UserInitializationException("username is null");
}
if ( "".equals(username.trim())) {
throw new UserInitializationException("username is empty");
}
}

public String getUsername() {
return username;
}

public String getName() {
return name;
}

public String getSurname() {
return surname;
}

/**
* Changing someone's name can have side effects.
*
* name
*/
public void changeName(String name) {
this.name = name;
}

/**
* Changing someone's surname can have side effects.
*
* surname
*/
public void changeSurname(String surname) {
this.surname = surname;
}

public String getId() {
return this.id;
}

/**
* Private constructor only for JPA-use.
*/
private User() { }


}



Fijaos en dos detalles que sólo tienen que ver con la persistencia:

  • El atributo id (anotado convenientemente con para que el ORM se encargue de generarlo y todo eso) y su correspondiente getter. No tiene nada que ver con el dominio y, por tanto, si fueramos puristas no debería estar en esta clase sino en un DTO especializado. Sin embargo, JPA justamente nos viene a ayudar en esto para evitar esas clases utilitarias que no hacen más que enredarnos…
  • El constructor vacío que hemos dejado como privado tampoco tiene nada que ver con el dominio. De hecho, es el ORM el que lo necesita para instanciar las clases a partir de los datos en la BD. Por eso lo he dejado como privado, porque así evitamos la tentación de usar esta clase como DTO.

El resto es una clase del dominio: una entidad, tal y como la clasificaría Eric Evans y tal y como la hemos estereotipado con la anotación @Entity.

Spring, JPA y DBUnit

Como ahora tengo bastante tiempo libre, he empezado una aplicación para prototipar algunos conceptos que me interesa fijar. Así, he comenzado por resolver la arquitectura básica de persistencia. He decidido usar Spring y JPA (Hibernate). Y para las pruebas he querido usar también DbUnit para llenar una base de datos en memoria (H2).

Y como estaba bastante oxidado, estuve viendo las nuevas anotaciones en Spring 2 y me llamó la atención el estereotipo . Dado que encaja muy bien con el enfoque de “modelo rico” en el que quiero trabajar, he estado jugando con esto y ha quedado así.

Comenzaré por el test.

@ContextConfiguration(locations = {"classpath:/META-INF/dao-test-context.xml"})
public class UserRepositoryTest extends AbstractRepositoryTest {

@Autowired
private UserDAO userDAO;

@Test
public void testFindByUsername() {
User aUser = userDAO.findByUsername("rob");
assertEquals("0001", aUser.getId());
assertEquals("Robert", aUser.getName());
assertEquals("Smith", aUser.getSurname());
}



La anotación permite decir dónde están los ficheros de configuración de Spring (para nuestro ejemplo sólo usamos uno, pero se pueden poner todos los que se quieran). Con inyectamos el DAO que vamos a probar sin necesidad de declararlo en el xml.

El código de la clase abstracta de la que extendemos tiene la “fixture” de la base de datos usando DbUnit.


@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,TransactionalTestExecutionListener.class})
@Transactional
public class AbstractRepositoryTest {

private static String TEST_DATA_FILE = "src/test/resources/dbunit-test-data.xml";

/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());

@Autowired
private DataSource dataSource;

private DataSource getDataSource() {
return this.dataSource;
}

@BeforeTransaction
public void onSetUpInTransaction() throws Exception {
logger.info("*** Inserting test data ***");
// Use spring to get the datasource
DataSource ds = getDataSource();
Connection conn = ds.getConnection();
try {
IDatabaseConnection connection = new DatabaseConnection(conn);
DatabaseOperation.CLEAN_INSERT.execute(connection,
new FlatXmlDataSet(new FileInputStream(TEST_DATA_FILE),false));
} finally {
DataSourceUtils.releaseConnection(conn, ds);
logger.info("*** Finished inserting test data ***");
}
}

@AfterTransaction
public void onTearDownInTransaction() throws Exception {
// Delete the data
DataSource ds = getDataSource();
Connection conn = ds.getConnection();
try {
IDatabaseConnection connection = new DatabaseConnection(conn);
DatabaseOperation.DELETE.execute(connection, new FlatXmlDataSet(
new FileInputStream(TEST_DATA_FILE)));
} finally {
DataSourceUtils.releaseConnection(conn, ds);
logger.info("*** Finished removing test data ***");
}
}



El “truco” aquí consiste en la anotación y la inyección de DataSource (que está declarada en el xml de Spring).

El repositorio lo implementamos como un DAO, pero en vez de heredar de JpaDaoSupport (que es quizás lo más común cuando usamos Spring) lo implementamos independiente de Spring (aunque añadamos la anotación @Repository). Cuando usamos , el contenedor de Spring “sabe” cómo inyectar el EntityManager sin que tengamos que decirle nada explícitamente.


@Repository
public class JpaUserDAO implements UserDAO {

private EntityManager entityManager;

@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}

public EntityManager getEntityManager() {
return entityManager;
}

public User findByUsername(final String username) {
return (User) getEntityManager().
createQuery("from User where username = :username").
setParameter("username", username).
getSingleResult();
}





Como es JPA, es necesario el fichero META-INF/persistence.xml.


<persistence version="1.0">
xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">

<persistence-unit name="ACME_PU-TEST" transaction-type="RESOURCE_LOCAL">
<class>acme.core.users.User</class>
</persistence-unit>

</persistence>



Obsérvese que apenas hay que darle información. Tenemos que explicitar qué tenemos (podemos hacerlo en un fichero orm.xml, pero por simplicidad lo he dejado dentro).

Y finalmente, el pegamento de todo esto… el xml de Spring.


<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byName">

<context:component-scan base-package="acme.core"/>
<context:annotation-config/>

<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="org.hibernate.dialect.H2Dialect"/>
<property name="showSql" value="true"/>
<property name="generateDdl" value="true"/>
</bean>
</property>
<property name="persistenceUnitName" value="ACME_PU-TEST"/>
</bean>

<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_mem"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"/>

</beans>


Es importante señalar que hay dos partes en este xml (de hecho, los he fusionado para que resultara más compacto, pero realmente los tengo separados): los elementos del espacio de nombres “context” y “beans” respectivamente.

Las primeras son necesarias para poder usar y otras. Las otras son los únicos tres beans que necesitamos declarar en el xml (porque no son nuestros sino implementaciones que vienen con la librería de Spring).

Es importante declarar los tres y usar esos nombres para aprovechar la inyección automática “byName”.

Falta el xml con los datos para que DbUnit cree la tabla y rellene los datos de prueba (nuestra “fixture”).


<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<user id="0001" username="rob" name="Robert" surname="Smith" />
<user id="0002" username="rob2" name="Robert" surname="Jones" />
<user id="0003" username="peter" name="Peter" surname="Mullins" />
</dataset>



Espero que os sea útil. A continuación os explicaré cómo implementar una capa de servicios que use este repositorio de datos, con sus pruebas de aceptación (con Concordion, por supuesto) y todo… :-)

TS-3559

Este fin de semana, mi colega Stephane me ha enseñado la presentación que Adam Bien ha expuesto en la última JavaOne titulada (ahí va el título) :
Java 6 Platform, Java DB, Swing, JNLP / Java Web Start, Java Persistence API (JPA) / Enterprise JavaBeans (EJB) 3 : The New “Operating System” for Rich Internet Applications.

En esta presentación, el autor (que es un miembro de la comunidad de Java Champions) explica una arquitectura para aplicaciones basada en los siguientes principios:

  • Domain Driven Design (Diseño guiado por el dominio): se refiere a que los objetos que modelan el negocio (patrón Business Object) representan el estado del negocio e incluye cierto comportamiento (auto-validaciones, manejo de relaciones con otros objetos de dominio dependientes, CRUD). No sé si tiene algo que ver con los libros de igual título (“Domain-Driven Design” y “Applying Domain-Driven Design and Patterns“).
  • Implementación de los objetos de dominio como entidades JPA.
  • Otra lógica más allá de la que un objeto de dominio sea capaz de resolver por si mismo se debe implementar como una fachada (POJOs o stateless/stateful session beans). Estamos hablando del acceso al EntityManager de JPA, de la transaccionalidad, seguridad, trazas de auditoría…
  • No se usan DTOs (patrón Transfer Object) sino que los objetos de dominio viajan desde la capa de negocio hasta la capa de presentación sin el sobrecoste de las transformaciones o las copias de valores.


La diapositiva 18 es especialmente clarificadora. Sin embargo, le he preguntado directamente a Adam Bien cómo encaja esta propuesta en una arquitectura orientada a servicios (SOA). Su respuesta ha sido que los servicios deben implementar lógica no relacionada con los objetos de dominio, mientras que las fachadas (o gateways, implementadas como SFSB) se encargan de lógica independiente de la instancia (como el CRUD) y exponen directamente entidades JPA (los objetos de dominio), que se almacenan localmente.



Me queda pendiente preguntarle cómo resolveríamos los problemas de concurrencia que provocaría un webservice que modificara registros en la base de datos que, a su vez, correspondieran a objetos de dominio presentes en la sesión de una de esas SFSB.