29 de abril de 2020

Añadir método personalizado en repositorio

Ya conocemos lo básico de Spring Data Rest y cómo nos facilita exponer los recursos que tenemos almacenados en nuestra BD con JPA. Sin embargo todos los métodos que hemos usado hasta ahora tienen una relación directa con las columnas de la BD (por ejemplo busco Participantes usando su campo nombre) y en nuestro proyecto seguramente habrá aspectos que no podamos solucionar con lo visto hasta ahora. Como ejemplo nos gustaría recuperar los Partidos de un Participante con un texto en su nombre. En este caso tenemos un problema: en la tabla PARTIDOS no están los nombres de los participantes, sólo sus ids y ya no es suficiente con usar los Query Methods de JPA para solucionarlo.

En este caso necesitamos hacer un método personalizado para nuestro repositorio.

Para implementar esta funcionalidad vamos a hacer lo siguiente:
  1. Crear una búsqueda para buscar los participantes conteniendo un texto (eso ya lo tenemos: ParticipanteDAO.findByNombreIgnoreCaseContaining)
  2. Con los participantes recuperados buscaremos los partidos en los que aparecen
  3. Añadiremos todos los partidos de esos participantes a una lista
  4. Esa lista será el payload de nuestra respuesta HTTP
Para añadir un método personalizado Data Rest permite que nuestro DAO se complete usando fragmentos implementados de varias interfaces, además de la que estamos usando JpaRepository. Esas interfaces tendrán los métodos que queremos añadir. En nuestro caso me creo la interface EventoDAOCustom:
public interface EventoDAOCustom<T extends Evento> {
    List<T> getEventosConParticipanteConTexto(String txt);
}
De esta forma esta interface me servirá para otro tipo de eventos, no sólo para partidos.
Ahora me creo una clase que implemente este método. Lo más importante es que debe tener el sufijo "Impl" e implementar nuestra nueva interface. Nos quedará así:
class PartidoDAOImpl implements EventoDAOCustom<PartidoConId> {

    @Autowired
    ParticipanteDAO participanteDAO;

    @PersistenceContext
    EntityManager entityManager;

    @Override
    public List<PartidoConId> getEventosConParticipanteConTexto(String txt) {
        List<Participante> participantes = participanteDAO.findByNombreIgnoreCaseContaining(txt);
        Set<PartidoConId> partidos = new HashSet<PartidoConId>();
        Query query = entityManager.createNativeQuery(
            "SELECT p.* FROM partidos as p " +
            "WHERE p.local = ?1 OR p.visitante = ?1", PartidoConId.class);
        participantes.forEach(p -> {
            query.setParameter(1, p.getIdentificador());
            partidos.addAll(query.getResultList());
        });

        return new ArrayList<PartidoConId>(partidos);
    }

}
Para implementar este método usamos ParticipanteDAO para recuperar los participantes y luego una Query que es bastante parecida a lenguaje SQL que ya conocemos como se ve en el ejemplo.

NOTA: la query necesita del EntityManager y lo inyectamos con @PersistenceContext.

Usamos la query para ir llamando a nuestra BD con cada participante que cumpla con el texto pasado y lo añadimos a un Set para que no se repita. Finalmente devolvemos todos los partidos. Podrían hacerse más cosas como ordenar por algún campo, etc...

Sólo nos queda añadir la interface a nuestro DAO para que Spring lo complete:
public interface PartidoDAO extends JpaRepository<PartidoConId, Long>, EventoDAOCustom<PartidoConId> {...}

Lo importante de todo esto es que puedo utilizar código propio para implementar aquello que ya se escapa de los Query Methods de JPA. Sin necesidad de añadirlo como bean ni nada parecido, Data Rest va a escanear las clases con el sufijo Impl (se puede configurar) buscando la implementación de EventoDAOCustom que no puede hacer automáticamente. Así irá formando con fragmentos de código el DAO completo.

Ahora probamos que todo funciona correctamente en nuestro main:
partidoDAO.getEventosConParticipanteConTexto("m").stream()
    .map(PartidoConId::toString)
    .forEach(log::trace);
Para que esto funcione de esta forma debemos indicar que este método es transaccional. Como habitualmente estas consultas personalizadas son lecturas de datos, marcamos toda la clase como sólo lectura.
@Transactional(readOnly = true)
class PartidoDAOImpl implements EventoDAOCustom<PartidoConId> {...}
NOTA: ver más sobre cómo funciona @PersistentContext y @Transactional.

Puedes conseguir el código hasta aquí en su repositorio.

Añadida la funcionalidad a nuestra persistencia, lo que nos falta es exponerla en nuestra API. En la siguiente entrada vamos a ver cómo hacerlo.

Práctica: mejorar cómo se consiguen los participantes
Ahora mismo si buscamos un equipo donde su nombre incluya una tilde en el fragmento buscado y no coincida en el valor de txt (ejemplo: buscando "?txt=atletico" no devolverá "Atlético...") parece que no obtenemos el resultado esperado. Se pide modificar el código para que no sólo sea IgnoreCase sino que también ignore las tildes.

NOTA: aparte de ParticipanteDAO tenemos otro origen de datos disponible para ello en forma de bean. Para facilitar el tratamiento de las tildes existe un método en lanyu.commons que se encarga de ello: StringUtils.eliminarTildes.

La solución está en github.

No hay comentarios:

Publicar un comentario

Compárteme

Entradas populares