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.

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

  1. Teniendo en cuenta que se trata de un proyecto Spring y no es necesario hacer un new de un objeto, como sería entonces el método main si quisieramos usarlo para probar los repositorios en vez de con un test JUnit?

    • danielme.com dice:

      Así es como estaba hecho en las primeras versiones del artículo antes de utilizar JUnit:

      public static void main(String[] args)
      {
      ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“/applicationContext.xml”);
      CountryRepository countryRepository = (CountryRepository) applicationContext.getBean(CountryRepository.class);

      • Pero entonces el archivo applicationContext.xml como quedaría?y en las últimas versiones de Spring Data JPA es necesario el archivo Persistence.xml o se “integra” con otros archivos de configuración?

      • danielme.com dice:

        El persistence.xml no es necesario en Spring aunque se puede utilizar si se quiere, se explica en el artículo Persistencia en BD con Spring. En este ejemplo concreto no se utiliza y lo único que tienes que configurar es la conexión a la base de datos en el fichero db.properties. Deberá proporcionarse la url de la base de datos y un usuario/contraseña con los permisos suficientes, al menos SELECT, CREATE, INDEX y UPDATE. Hibernate creará las tablas de forma automática (sólo las tablas, la base de datos ya deberá existir previamente).

  2. Julio dice:

    Hola Daniel.

    Muy interesante el artículo. Recientemente estoy investigando sobre SpringDataJPA planteándome si utilizarlo para un nuevo proyecto o utilizar los DAOs de manera tradicional tirando las queries como siempre, etc. En todos los sitios hablan maravillas y la verdad es que para un CRUD te facilita mucho el trabajo de escribir código.

    Muy bueno tu aporte para crear la interfaz que hereda del repositorio genérico para utilizar el EntityManager en todos los repositorios después.

  3. Nelson Tello dice:

    Saludos,

    Amigos, tengo el siguiente error y la verdad he pasado dias en esto y no se como resolverlo, necesito ayuda urgente por favor, el error es:

    No property clearEntityCache found for type E01Usuario!

    Por cierto muy interesante el tema, gracias por el aporte.

  4. promomoda dice:

    Saludos,

    Buen articulo, a alguien le salió un error en clearEntityCache(), en done indica que debe ser parte de la Entidad, por favor una ayuda.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: