Persistencia en BD con Spring Data JPA (III): Auditoría

Tercera parte del artículo dedicado a la persistencia en BD con Spring Data JPA:

  1. Primeros pasos
  2. Repositorios personalizados
  3. Auditoría

Spring Data JPA proporciona un mecanismo sencillo y elegante para dotar de un sistema automatico de auditoria
a las entidades. Los campos de auditoría contemplados son la fecha de creación, fecha de última modificación, el usuario creador y el último que modificó la entidad. Vamos a implementar este mecanismo en nuestra demo paso a paso.

  1. Comprobar que tenemos la dependencia spring-aspects. En función de los módulos de Spring que estemos usando es muy posible que ya la tengamos, si no la añadimos:

    <dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-aspects</artifactId>
    			<version>${spring.version}</version>
    		</dependency>
    

    En cualquier caso, si no tenemos esta librería en el classpath la excepcin que se produce estotalmente descriptiva:

    Configuration problem: Could not configure Spring Data JPA auditing-feature because spring-aspects.jar is not on the classpath!
  2. Añadir los campos de auditoria a las entidades. Generalmente crearemos una superclase con estos campos y haremos heredar de ella a todas las entidades que requieran auditoría. Los atributos correspondientes a la auditoría se anotarán con @CreatedBy, @LastModifiedBy, @CreatedDate y @LastModifiedDate. Para las fechas se puede utilizar Date, Calendar o los DateTime de Joda-Time, y para el usuario cualquier clase. En el ejemplo se utilizará un String por simplicidad pero en una aplicación real lo habitual será utilizar una clase User o similar del modelo de datos.
    package com.danielme.demo.springdatajpa.model;
    
    import java.util.Calendar;
    
    import javax.persistence.EntityListeners;
    import javax.persistence.MappedSuperclass;
    import javax.persistence.Temporal;
    import javax.persistence.TemporalType;
    
    import org.springframework.data.annotation.CreatedBy;
    import org.springframework.data.annotation.CreatedDate;
    import org.springframework.data.annotation.LastModifiedBy;
    import org.springframework.data.annotation.LastModifiedDate;
    import org.springframework.data.jpa.domain.support.AuditingEntityListener;
    
    @EntityListeners({AuditingEntityListener.class})
    @MappedSuperclass
    public class AuditableEntity
    {
        @CreatedDate
        @Temporal(TemporalType.TIMESTAMP)
        private Calendar createdDate;
    
        @LastModifiedDate
        @Temporal(TemporalType.TIMESTAMP)
        private Calendar lastModifiedDate;
        
        @CreatedBy
        private String createBy;
        
        @LastModifiedBy
        private String lastModifiedBy;
    
    	public Calendar getCreatedDate()
    	{
    		return createdDate;
    	}
    
    	public void setCreatedDate(Calendar createdDate)
    	{
    		this.createdDate = createdDate;
    	}
    
    	public Calendar getLastModifiedDate()
    	{
    		return lastModifiedDate;
    	}
    
    	public void setLastModifiedDate(Calendar lastModifiedDate)
    	{
    		this.lastModifiedDate = lastModifiedDate;
    	}	
    
    	public String getCreateBy()
    	{
    		return createBy;
    	}
    
    	public void setCreateBy(String createBy)
    	{
    		this.createBy = createBy;
    	}
    
    	public String getLastModifiedBy()
    	{
    		return lastModifiedBy;
    	}
    
    	public void setLastModifiedBy(String lastModifiedBy)
    	{
    		this.lastModifiedBy = lastModifiedBy;
    	}      
        
        
    }
    
    

    Esta clase además está anotada con @MappedSuperclass para que las entidades hijas incluyan sus atributos en la tabla correspondiente y con @EntityListeners para que se ejecute el listener de auditoría de Spring Data. Si hacemos que Country herede de AuditableEntity el modelo de datos quedará tal que así:

    class diagram

    Una alternativa consiste en hacer que la entidadaes auditables hereden de la clase AbstractAuditable de Spring Data JPA, equivalente a la clase AuditableEntity que hemos creado, o incluso hacer que implementen directamente la interfaz Auditable. En ambos casos perdemos flexibilidad y no obtenemos ninguna venataja aparente salvo que puntualmente haya que utilizar la interfaz al no disponer Java de herencia múltiple.

    spring data jpa auditable

  3. Implementar la interfaz AuditorAware. Esta implementación será un bean de Spring por lo que se anotará con @Component y tendrá un método que devolverá un objeto correspondiente al usuario que se utilizará en los campos de auditoría (en una misma transacción este meodo sólo se invocará la primera vez que sea necesario obtener el usuario). En el ejemplo el nombre de usuario se tomará de un atributo estático, en una aplicación real se obtendrá del sistema de gestión de usuarios que tengamos implementado, por ejemplo Spring Security.
    package com.danielme.demo.springdatajpa.listeners;
    
    import org.springframework.data.domain.AuditorAware;
    import org.springframework.stereotype.Component;
    
    import com.danielme.demo.springdatajpa.AuthenticationMockup;
    
    @Component
    public class CustomAuditorAware implements AuditorAware<String>
    {
    
    	@Override
    	public String getCurrentAuditor()
    	{
    		return AuthenticationMockup.UserName;
    	}
    
    }
    
    
  4. Definir nuestro AuditorAware en al applicationContext.xml de la siguiente forma:
    <jpa:auditing auditor-aware-ref="customAuditorAware" />	
    
    1. Siguiendo estos pasos ya tenemos la auditoría configurada. Se puede probar con el siguiente test:

      	@Test
      	public void testAudit() throws Exception
      	{
      		Country country = new Country();
      		country.setName("Bolivia");
      		country.setPopulation(10556105);
      
      		country = countryRepository.save(country);
      		assertTrue(country.getCreateBy().equals(country.getLastModifiedBy()));
      		assertTrue(country.getCreatedDate().equals(country.getLastModifiedDate()));
      
      		Thread.sleep(2000);
      
      		AuthenticationMockup.UserName = "update";
      		country.setName("Estado Plurinacional de Bolivia");
      		country = countryRepository.save(country);
      		assertTrue(country.getLastModifiedBy().equals(AuthenticationMockup.UserName));
      		assertTrue(country.getCreateBy().equals("dani"));
      		assertFalse(country.getCreatedDate().equals(country.getLastModifiedDate()));
      
      		AuthenticationMockup.UserName = "dani";
      		countryRepository.delete(country);
      
      	}
      ...
      

      Código de ejemplo

      El proyecto completo se encuentra disponible en GitHub. Para más información sobre cómo utilizar GitHub, consultar este artículo.

Persistencia en BD con Spring Data JPA (II): Repositorios personalizados

Segunda parte del artículo dedicado a la persistencia en BD con Spring Data JPA:

  1. Primeros pasos
  2. Repositorios personalizados
  3. Auditoría

En el caso de que sea necesario acceder directamente al EntityManager y codificar operaciones con él podemos implementar nuestro propio repositorio conservando todas funcionalidades proporcionadas por los repositorios de forma estándar. Vamos a implementar el método del proyecto original que eliminaba los objetos de la entidad almacenados en la caché. En primer lugar, se crea una nueva interfaz con la definición del método. Por defecto el nombre de esta interfaz debe tener el sufijo “Custom”.

package com.danielme.demo.springdatajpa.repository;

public interface CountryRepositoryCustom 
{	
	void clearEntityCache();	
}

Ahora se implementa esta interfaz en un bean estándar de Spring en el que se puede realizar inyecciones de dependencias. Debe tener el mismo nombre que la interfaz pero cambiando el sufijo Custom por Impl. Es fundamental seguir estas convenciones sino no funcionará (aunque se pueden cambiar estas convenciones en el applicationContext.xml). Asimismo, obsérvese que no es necesario anotar esta clase con @Repository o cualquier otro estereotipo de Spring IoC.

package com.danielme.demo.springdatajpa.repository;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.transaction.annotation.Transactional;

import com.danielme.demo.springdatajpa.Country;


public class CountryRepositoryImpl implements CountryRepositoryCustom
{
	@PersistenceContext
	private EntityManager entityManager;	
	
	@Override
	@Transactional
	public void clearEntityCache() 
	{
		SessionFactory sessionFactory = entityManager.unwrap(Session.class).getSessionFactory();
		sessionFactory.getCache().evictEntityRegion(Country.class);
	}	

}

El repositorio “original” debe heredar de la interfaz anterior.

public interface CountryRepository extends JpaRepository<Country, Long>, CountryRepositoryCustom

La primera vez la creación de repositorios personalizados puede resultar un poco confusa ya que es necesario definir una interfaz adicional con los métodos personalizados por lo que las funcionalidades del repositorio están “separadas” en dos interfaces. Con el siguiente diagrama de clases queda un poco más claro.

Spring Data JPA Custom Repository

Tras estos cambios, los métodos específicos del repositorio están disponibles en el bean correspondiente a CountryRepository que el contenedor de Spring devuelve por lo que no es necesario realizar cambio alguno en las clases en las que se consuma el repositorio salvo llamar a los nuevos métodos si fuera necesario.

Crear un repositorio genérico

La estrategia anterior no es muy práctica en el caso de que se quieran añadir métodos a varios o incluso todos los repositorios pero es fácil crear un repositorio “base” que añada nuevos métodos a JpaRepository para que estén disponibles en más de un repositorio tal y como se mostrará a continuación paso a paso. Como ejemplo se incoporará a todos los repositorios que definamos un par de métodos que estaban disponibles en el Dao del proyecto original.

  1. Crear una interfaz hija de JpaRepository con los métodos que queramos. Para evitar que Spring instancie un repositorio correspondiente a esta interfaz se anotará con @NoRepositoryBean.

    package com.danielme.demo.springdatajpa.repository.base;
    
    import java.io.Serializable;
    import java.util.List;
    
    import org.springframework.data.domain.Pageable;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.repository.NoRepositoryBean;
    
    @NoRepositoryBean
    public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> 
    {
    	void clearHibenateCache();
    
    	List<T> getAllUsingCache(Pageable page);
    }
    
  2. Implementación de los métodos de la interfaz en una subclase de SimpleJpaRepository, obsérvese el constructor y los atributos de la clase.
    package com.danielme.demo.springdatajpa.repository.base;
    
    import java.io.Serializable;
    import java.util.List;
    
    import javax.persistence.EntityManager;
    import javax.persistence.Query;
    
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.jpa.QueryHints;
    import org.springframework.data.domain.Pageable;
    import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
    
    
    public class BaseRepositoryImpl <T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID>
    {
    	private EntityManager entityManager;
    	
    	private Class<T> clazz;
    	
    	public BaseRepositoryImpl(Class<T> domainClass, EntityManager entityManager)
    	{
    		super(domainClass, entityManager);
    		this.entityManager = entityManager; 
    		clazz = domainClass;
    	}
    	
    	@Override
    	public void clearHibenateCache() {
    		SessionFactory sessionFactory = entityManager.unwrap(Session.class).getSessionFactory();
    		sessionFactory.getCache().evictEntityRegions();
    		sessionFactory.getCache().evictCollectionRegions();
    		sessionFactory.getCache().evictDefaultQueryRegion();
    		sessionFactory.getCache().evictQueryRegions();
    	}
    	
    	@SuppressWarnings("unchecked")
    	@Override
    	public List<T> getAllUsingCache(Pageable page)
    	{
    		Query query = entityManager.createQuery("from " + clazz.getName());
    		query.setHint(QueryHints.HINT_CACHEABLE, true);
    		if (page != null)
    		{
    			query.setFirstResult(page.getPageNumber()*page.getPageSize());
    			query.setMaxResults(page.getPageSize());
    		}
    		return query.getResultList();
    	}
    
    }
    
    
  3. Creación de una factoría para que Spring Data JPA devuelva para todos los repositorios instancias de BaseRepositoryImpl en lugar de SimpleJpaRepository.
    package com.danielme.demo.springdatajpa.repository.base;
    
    import java.io.Serializable;
    
    import javax.persistence.EntityManager;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
    import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
    import org.springframework.data.repository.core.RepositoryMetadata;
    import org.springframework.data.repository.core.support.RepositoryFactorySupport;
    
    public class BaseRepositoryFactoryBean<T extends JpaRepository<S, ID>, S, ID extends Serializable> extends JpaRepositoryFactoryBean<T, S, ID>
    {
    
    	protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager)
    	{
    		return new BaseRepositoryFactory(entityManager);
    	}
    
    	private static class BaseRepositoryFactory extends JpaRepositoryFactory
    	{		
    		public BaseRepositoryFactory(EntityManager entityManager)
    		{
    			super(entityManager);			
    		}
    
    		@SuppressWarnings({ "unchecked", "rawtypes" })
    		@Override
    		protected <T, ID extends Serializable> JpaRepository<?, ?> getTargetRepository(RepositoryMetadata metadata, EntityManager entityManager)
    		{
    			return new BaseRepositoryImpl(metadata.getDomainType(), entityManager);
    		}
    
    		@Override
    		protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata)
    		{
    			return BaseRepositoryImpl.class;
    		}
    	}
    }
    
    
  4. En la configuración del repositorio se añade la factoria.
    	<jpa:repositories base-package="com.danielme.demo.springdatajpa.repository" factory-class="com.danielme.demo.springdatajpa.repository.BaseRepositoryFactoryBean"/>
    
    
  5. Para que cada repositorio concreto tenga la funcionalidad de BaseRepository deberá ser hijo del mismo y no de JpaRepository.
    public interface CountryRepository extends BaseRepository<Country, Long>, CountryRepositoryCustom
    

La “arquitectura” final es la siguiente.

Spring Data JPA Custom Base Repository

Código de ejemplo

El proyecto completo se encuentra disponible en GitHub. Para más información sobre cómo utilizar GitHub, consultar este artículo.

Tareas programadas en Spring

logo spring

Es posible que al desarrollar una aplicación la mejor solución para ciertas funcionalidades sea la implementación de tareas programables, esto es, métodos que se ejecuten periódicamente en momentos determinados o bien cada cierto periodo de tiempo. El caso más típico consiste en la revisión de la base de datos para la generación de informes de seguimiento, el envío de notificaciones, realizar tareas de sicronización con otros sistemas, etc. Asimismo, estas tareas suelen formar parte de la propia aplicación ya que así se pueden utilizar los servicios, capa DAO, etc de la misma.

En Java se suele utilizar Quartz, sin embargo si utilizamos Spring y la casuística es sencilla no tenemos que recurrir a ninguna librería de terceros ya que con Spring 3 es sencillo definir ejecuciones períódicas de un método de un bean y es precisamente lo que se verá de forma práctica en este artículo.

Seguir leyendo

Diseño Android: Popup Menu


VERSIONES

  1. 08/03/2014 (Primera publicación)
  2. 17/05/2014:
    • Añadido color de fuente

android

El primer artículo de la serie “Diseño Android” de 2014 está dedicado al widget PopupMenu que permite mostrar un menú contextual en un popup ubicado junto al elemento que lo despliega. Este widget fue introducido en HoneyComb (API 11) pero puede ser utilizado en versiones anteriores ya que se encuentra incluido en la libreria de compatibilidad v7.

La siguiente captura de la app oficial de Youtube muestra un popup menu como el que se va a utilizar en el presente artículo.

youtube-popup menu

Entorno de pruebas:

Requisitos: Conocimientos básicos de Android SDK. Se utilizará Eclipse como IDE.

Seguir leyendo

Persistencia en BD con Spring Data JPA (I): Primeros pasos


VERSIONES

  1. 08/02/2014 (Primera publicación)
  2. 02/03/2014:
    • Añadido ejemplo de UPDATE
    • Actualización de librerias de Spring
  3. 01/11/2014:
    • Ampliación de la sección de consultas
    • Configuración de caché
    • División del artículo original en dos partes y creación de una tercera
    • Diagrama de clases

logo spring

Nota: Este tutorial es una continuación de Persistencia en BD con Spring: Integrando JPA, c3p0, Hibernate y EHCache. El objetivo es reutilizar el mismo proyecto de ejemplo e incluir Spring Data JPA.

Spring Data es el nombre de un módulo de Spring que a su vez engloba un gran número de sub-módulos cuyo objetivo es facilitar el acceso y explotación de datos en aplicaciones basadas en Spring, obteniéndose estos datos de fuentes tan dispares como servicios RestFUL, bases de datos relacionales a través de JPA, o bases de datos NoSQL como MongoDB o Neo4J, entre otras. Este artículo expondrá el uso básico del módulo para JPA utilizándose como implementación Hibernate y se divide en tres partes:

  1. Primeros pasos
  2. Repositorios personalizados
  3. Auditoría

Nota:A partir de la versión 1.7.0.RELEASE Spring Data JPA requiere Spring 4. En el artículo se utilizará Spring 3 y Spring Data JPA 1.6.4.RELEASE ya que Spring 3 se sigue utilizando ampliamente incluso en proyectos de nueva creación.

Seguir leyendo

Diseño Android: ViewPagerIndicator

android

El artículo anterior presentó el widget ViewPager para construir interfaces gráficas “paginadas”. Una vez que tenemos nuestro ViewPager funcionando, el siguiente paso será utilizar un mecanismo que nos permita saber en qué página estamos y/o si hay páginas adyacentes a las que se pueda navegar. Gracias a Jake Wharton (un prestigioso desarrollador Android al que recomiendo seguir en Twitter y G+) disponemos de una librería “open source” con licencia Apache 2.0 llamada ViewPagerIndicator. En este artículo veremos cómo usarla con el proyecto del artículo anterior y algunas de sus posibilidades, siendo en mi opinión la más interesante la posibilidad de utilizar pestañas y permitir la navegación en el ViewPager tanto mediante desplazamiento gestual como por pulsación de la pestaña.

Podemos echar un vistazo a todo lo que ofrece esta librería instalando su demo desde Google Play.

Entorno de pruebas:

Requisitos: Los conocimientos presentados en el artículo anterior (uso básico de un ViewPager).

Seguir leyendo

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

Únete a otros 43 seguidores