Saltar al contenido

Uso de genéricos en repositorios Spring Data JPA

Luego de consultar expertos en este tema, programadores de varias ramas y maestros dimos con la solución al problema y la dejamos plasmada en esta publicación.

Solución:

En primer lugar, sé que estamos subiendo un poco el listón, pero esto ya es muchísimo menos código del que tenías que escribir sin la ayuda de Spring Data JPA.

En segundo lugar, creo que no necesita la clase de servicio en primer lugar, si todo lo que hace es reenviar una llamada al repositorio. Recomendamos utilizar servicios frente a los repositorios si tiene una lógica empresarial que necesita la orquestación de diferentes repositorios dentro de una transacción o tiene otra lógica empresarial para encapsular.

En términos generales, por supuesto, puede hacer algo como esto:

interface ProductRepository extends CrudRepository 

    @Query("select p from ##entityName p where ?1 member of p.categories")
    Iterable findByCategory(String category);

    Iterable findByName(String name);

Esto le permitirá usar el repositorio en el lado del cliente de esta manera:

class MyClient 

  @Autowired
  public MyClient(ProductRepository carRepository, 
                  ProductRepository wineRepository)  … 

y funcionará como se esperaba. Sin embargo, hay algunas cosas a tener en cuenta:

Esto solo funciona si las clases de dominio usan herencia de tabla única. La única información sobre la clase de dominio que podemos obtener en el momento del arranque es que será Product objetos. Entonces, para métodos como findAll() e incluso findByName(…) las consultas relevantes comenzarán con select p from Product p where…. Esto se debe al hecho de que la búsqueda de reflexión nunca podrá producir Wine o Cara no ser que crea una interfaz de repositorio dedicada para que capture la información de tipo concreto.

En términos generales, recomendamos crear interfaces de repositorio por raíz agregada. Esto significa que no tiene un repositorio para cada clase de dominio per se. Aún más importante, una abstracción 1: 1 de un servicio sobre un repositorio también pierde completamente el sentido. Si crea servicios, no crea uno para cada repositorio (un mono podría hacer eso, y nosotros no somos monos, ¿verdad?). Un servicio está exponiendo una API de nivel superior, es mucho más una unidad de casos de uso y, por lo general, organiza las llamadas a varios repositorios.

Además, si crea servicios sobre repositorios, generalmente desea obligar a los clientes a usar el servicio en lugar del repositorio (un ejemplo clásico aquí es que un servicio para la administración de usuarios también activa la generación de contraseñas y el cifrado, por lo que de ninguna manera Sería una buena idea permitir que los desarrolladores utilicen el repositorio directamente, ya que trabajarían eficazmente en torno al cifrado). Por lo tanto, generalmente desea ser selectivo sobre quién puede persistir qué objetos de dominio para no crear dependencias en todo el lugar.

Resumen

Sí, puede crear repositorios genéricos y usarlos con varios tipos de dominio, pero existen limitaciones técnicas bastante estrictas. Aún así, desde un punto de vista arquitectónico, el escenario que describe arriba ni siquiera debería aparecer, ya que esto significa que está enfrentando un olor a diseño de todos modos.

¡Esto es muy posible! Probablemente llego muy tarde a la fiesta. Pero esto ciertamente ayudará a alguien en el futuro. ¡Aquí hay una solución completa que funciona a las mil maravillas!

Crear BaseEntity class para sus entidades de la siguiente manera:

@MappedSuperclass
public class AbstractBaseEntity implements Serializable

    @Id @GeneratedValue
    private Long id;
    @Version
    private int version;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;

    public AbstractBaseEntity() 
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    

    // getters and setters      

Cree una interfaz de repositorio JPA genérico para la persistencia de su DAO de la siguiente manera: NB. Recuerde poner el @NoRepositoryBean ¡para que JPA no intente encontrar una implementación para el repositorio!

@NoRepositoryBean
public interface AbstractBaseRepository
extends JpaRepository
    

Cree una clase de servicio base que utilice el repositorio JPA base anterior. Este es el que otras interfaces de servicio en su dominio simplemente extenderán de la siguiente manera:

public interface AbstractBaseService
    public abstract T save(T entity);
    public abstract List findAll(); // you might want a generic Collection if u prefer

    public abstract Optional findById(ID entityId);
    public abstract T update(T entity);
    public abstract T updateById(T entity, ID entityId);   
    public abstract void delete(T entity);
    public abstract void deleteById(ID entityId);
    // other methods u might need to be generic
    

Luego, cree una implementación abstracta para el repositorio JPA base y los métodos CRUD básicos también recibirán sus implementaciones como se muestra a continuación:

@Service
@Transactional
public abstract class AbstractBaseRepositoryImpl
        implements AbstractBaseService
    
    private AbstractBaseRepository abstractBaseRepository;
    
    @Autowired
    public AbstractBaseRepositoryImpl(AbstractBaseRepository abstractBaseRepository) 
        this.abstractBaseRepository = abstractBaseRepository;
    
    
    @Override
    public T save(T entity) 
        return (T) abstractBaseRepository.save(entity);
    

    @Override
    public List findAll() 
        return abstractBaseRepository.findAll();
    

    @Override
    public Optional findById(ID entityId) 
        return abstractBaseRepository.findById(entityId);
    

    @Override
    public T update(T entity) 
        return (T) abstractBaseRepository.save(entity);
    

    @Override
    public T updateById(T entity, ID entityId) 
        Optional optional = abstractBaseRepository.findById(entityId);
        if(optional.isPresent())
            return (T) abstractBaseRepository.save(entity);
        else
            return null;
        
    

    @Override
    public void delete(T entity) 
        abstractBaseRepository.delete(entity);
    

    @Override
    public void deleteById(ID entityId) 
        abstractBaseRepository.deleteById(entityId);
    


Cómo utilizar el resumen anterior entity, service, repository, y implementation:

Ejemplo aquí será un MyDomain entidad. Cree una entidad de dominio que amplíe el AbstractBaseEntity como sigue: NB. ID, createdAt, updatedAt, version, etc se incluirán automáticamente en el MyDomain entidad de la AbstractBaseEntity

@Entity
public class MyDomain extends AbstractBaseEntity

    private String attribute1;
    private String attribute2;
    // getters and setters

Entonces crea un repository Para el MyDomain entidad que extiende el AbstractBaseRepository como sigue:

@Repository
public interface MyDomainRepository extends AbstractBaseRepository


Además, cree un service interfaz para el MyDomain entidad de la siguiente manera:

public interface MyDomainService extends AbstractBaseService


Luego proporcione una implementación para el MyDomain entidad que extiende el AbstractBaseRepositoryImpl implementación de la siguiente manera:

@Service
@Transactional
public class MyDomainServiceImpl extends AbstractBaseRepositoryImpl 
        implements MyDomainService
    private MyDomainRepository myDomainRepository;

    public MyDomainServiceImpl(MyDomainRepository myDomainRepository) 
        super(myDomainRepository);
    
    // other specialized methods from the MyDomainService interface


Now use your `MyDomainService` service in your controller as follows: 

@RestController // or @Controller
@CrossOrigin
@RequestMapping(value = "/")
public class MyDomainController 
    
    private final MyDomainService myDomainService;

    @Autowired
    public MyDomainController(MyDomainService myDomainService) 
        this.myDomainService = myDomainService;
    
   
    @GetMapping
    public List getMyDomains()
        return myDomainService.findAll();
       
    // other controller methods


NÓTESE BIEN. Asegúrese de que el AbstractBaseRepository está anotado con @NoRepositoryBean así que eso JPA no intenta encontrar una implementación para el bean. También el AbstractBaseServiceImpl debe marcarse como abstracto, de lo contrario, JPA intentará conectar automáticamente todos los datos secundarios del AbstractBaseRepository en el constructor de la clase que conduce a un NoUniqueBeanDefinitionException ¡ya que se inyectará más de 1 daos (repositorio) cuando se cree el bean! Ahora tu service, repository, y implementations son más reutilizables. ¡Todos odiamos la repetición!

Espero que esto ayude a alguien.

Te mostramos las comentarios y valoraciones de los usuarios

Puedes añadir valor a nuestra información colaborando tu veteranía en los comentarios.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *