A logo showing the text blog.marcnuri.com
English
Inicio»Java»Spring Data MongoDB: Implementación de un repositorio a medida

Entradas Recientes

  • Fabric8 Kubernetes Client 7.2.0 está disponible!
  • Conectarse a un servidor MCP con JavaScript y AI SDK
  • Conectarse a un servidor MCP con JavaScript y LangChain.js
  • El Futuro de las Herramientas para Desarrolladores en la era de la IA
  • Conectarse a un servidor Model Context Protocol (MCP) con Java y LangChain4j

Categorías

  • Antiguo
  • Front-end
  • Go
  • Herramientas
  • Industria y negocios
  • Inteligencia Artificial
  • Java
  • JavaScript
  • Operaciones
  • Personal
  • Proyectos personales

Archivos

  • mayo 2025
  • abril 2025
  • marzo 2025
  • febrero 2025
  • enero 2025
  • diciembre 2024
  • noviembre 2024
  • agosto 2024
  • junio 2024
  • mayo 2024
  • abril 2024
  • marzo 2024
  • febrero 2024
  • enero 2024
  • diciembre 2023
  • noviembre 2023
  • octubre 2023
  • septiembre 2023
  • agosto 2023
  • julio 2023
  • junio 2023
  • mayo 2023
  • abril 2023
  • marzo 2023
  • febrero 2023
  • enero 2023
  • diciembre 2022
  • noviembre 2022
  • octubre 2022
  • agosto 2022
  • julio 2022
  • mayo 2022
  • marzo 2022
  • febrero 2022
  • enero 2022
  • diciembre 2021
  • noviembre 2021
  • octubre 2021
  • septiembre 2021
  • agosto 2021
  • julio 2021
  • diciembre 2020
  • octubre 2020
  • agosto 2020
  • junio 2020
  • mayo 2020
  • marzo 2020
  • febrero 2020
  • enero 2020
  • noviembre 2019
  • octubre 2019
  • julio 2019
  • diciembre 2018
  • agosto 2018
  • julio 2018
  • junio 2018
  • mayo 2018
  • marzo 2018
  • febrero 2018
  • noviembre 2017
  • octubre 2017
  • agosto 2017
  • julio 2017
  • enero 2017
  • julio 2016
  • enero 2016
  • diciembre 2015
  • noviembre 2015
  • diciembre 2014
  • marzo 2014
  • febrero 2011
  • junio 2008
  • mayo 2008
  • abril 2008
  • enero 2008
  • junio 2007
  • mayo 2007
  • abril 2007
  • marzo 2007

Spring Data MongoDB: Implementación de un repositorio a medida

2018-03-14 en Java etiquetado Java / MongoDB / Spring Framework / Spring Boot / Spring Data / Testing por Marc Nuri | Última actualización: 2021-03-20
English version

Introducción

Spring Data facilita mucho el proceso de trabajo con entidades de datos y ofrece una implementación específica para MongoDB. Se pueden definir consultas simplemente creando interfaces con métodos que siguen una convención de nombres o anotándolos con @Query y Spring automágicamente generará una implementación por nosotros. En la mayoría de ocasiones esto nos bastará para operaciones CRUD o de consultas sencillas y no será necesario definir métodos adicionales. Esto nos permitirá finalizar el desarrollo de la aplicación o de la funcionalidad de acceso a datos de forma muy rápida sin tener que escribir código repetitivo.

Sin embargo, en muchas ocasiones esto no será suficiente y la interfaz que define el repositorio necesitará disponer de métodos no estándar e implementaciones específicas que nos permitirán dar respuesta a funcionalidades menos estándar.

En esta publicación veremos como desarrollar estas implementaciones a medida para un repositorio de libros en MongoDB y cómo definir tests unitarios que probarán dicha implementación a medida.

Definiendo la Entidad/Documento

El primer paso será definir el Documento principal de MongoDB que se devolverá en las consultas. Para el caso del ejemplo de este tutorial será un documento definido dentro de la clase Book con la información básica de la publicación de un libro.

1/* ... */
2@Document(collection = "books")
3public class Book {
4
5  @Id
6  private String id;
7  private String title;
8  private String isbn;
9  private List<String> authorNames;
10  private Date publishDate;
11  private List<String> subjects;
12  /* ... */
13}

Las partes más importantes de esta clase son la anotación @Document que indica que esta clase representa un documento dentro de la colección "books" de MongoDB, y la anotación @Id que indica a Spring que este campo debería de emplearse como el identificador único del documento/entidad.

Tal como se ha comentado, esta entidad contendrá información básica (título, número ISBN, nombres de autor, etc.) de un libro.

Definiendo el repositorio base

El siguiente paso es definir la interfaz base para el repositorio de libros.

1/* ... */
2public interface BookRepository extends MongoRepository<Book, String> {
3
4  List<Book> findByTitleContainingOrderByTitle(String titleContains);
5
6}

El repositorio extiende la interfaz MongoRepository, indicando a Spring que se trata de un repositorio específico de MongoDB, heredando todos los métodos disponibles en las interfaces padre (PagingAndSortingRepository, CrudRepository…).

Añadiremos un método findByTitleContainingOrderByTitle a la interfaz para mostrar como Spring auto-implementará este método sin necesidad de hacerlo en nuestra implementación a medida.

Definiendo los métodos personalizados del repositorio a medida

Para este tutorial queremos añadir la capacidad de hacer consultas dinámicas sobre la colección de libros. Con este propósito definiremos una clase DynamicQuery que contendrá los campos opcionales para los que se quiere poder filtrar la colección.

1/* ... */
2public class DynamicQuery {
3
4  private String authorNameLike;
5  private Date publishDateBefore;
6  private Date publishDateAfter;
7  private String subject;
8  /* ... */
9}

A continuación definiremos una interfaz que consuma un objeto tipo DynamicQuery y que devuelva una lista de Books. Esta interfaz se llamará BookRepositoryCustom, Es muy importante seguir la convención de nombres para que Spring pueda instanciar la implementación específica de esta nueva interfaz.

1/* ... */
2public interface BookRepositoryCustom {
3
4  List<Book> query(DynamicQuery dynamicQuery);
5
6}

Implementando los métodos del repositorio a medida

Es muy importante seguir la convención de nombres si queremos que Spring detecte nuestras implementaciones customizadas. Por defecto Spring escaneará el paquete donde se define la interfaz a medida y los paquetes por debajo de éste para encontrar una clase que extienda dicha interfaz. Si la configuración para la convención de nombres no se ha modificado, Spring por defecto buscará una clase con un sufijo "Impl" en el nombre. Por tanto, crearemos una clase BookRepositoryImpl que implemente la interfaz a medida BookRepositoryCustom.

1/* ... */
2public class BookRepositoryImpl implements BookRepositoryCustom {
3
4  private final MongoTemplate mongoTemplate;
5
6  @Autowired
7  public BookRepositoryImpl(MongoTemplate mongoTemplate) {
8    this.mongoTemplate = mongoTemplate;
9  }
10
11  @Override
12  public List<Book> query(DynamicQuery dynamicQuery) {
13    final Query query = new Query();
14    final List<Criteria> criteria = new ArrayList<>();
15    if(dynamicQuery.getAuthorNameLike() != null) {
16      criteria.add(Criteria.where("authorNames").regex(MongoRegexCreator.INSTANCE.toRegularExpression(
17          dynamicQuery.getAuthorNameLike(), Part.Type.CONTAINING
18      ), "i"));
19    }
20    if(dynamicQuery.getPublishDateBefore() != null) {
21      criteria.add(Criteria.where("publishDate").lte(dynamicQuery.getPublishDateBefore()));
22    }
23    if(dynamicQuery.getPublishDateAfter() != null) {
24      criteria.add(Criteria.where("publishDate").gte(dynamicQuery.getPublishDateAfter()));
25    }
26    if(dynamicQuery.getSubject() != null) {
27      criteria.add(Criteria.where("subjects").regex(MongoRegexCreator.INSTANCE.toRegularExpression(
28          dynamicQuery.getSubject(), Part.Type.SIMPLE_PROPERTY
29      ), "i"));
30    }
31    if(!criteria.isEmpty()) {
32      query.addCriteria(new Criteria().andOperator(criteria.toArray(new Criteria[criteria.size()])));
33    }
34    return mongoTemplate.find(query, Book.class);
35  }
36
37}

Esta clase se comportará como cualquier otro Bean de Spring por lo que podemos autowire sus dependencias. En este caso necesitaremos un MongoTemplate para construir nuestra query dinámica. Emplearemos la inyección basada en el constructor para inyectar una instancia de MonogTemplate.

Como puede observarse, esta clase implementa el método query y emplea el MongoTemplate inyectado para construir una consulta basada en Criteria.

La implementación del método comienza por creat una lista vacía de Criterias y comprueba si alguno de los campos proporcionados por DynamicQuery se han definido. Para cada uno de los campos definidos añade una nueva condición Criteria where. A continuación se fusionan todos los Criterias en la lista empleando un andOperator y añadiéndolos a la Query. Por último el método ejecuta la consulta en MongoTemplate para la colección especificada en la clase Book.

En este método también se puede observar como emplear una query con filtrado por expresiones regulares para consultar un campo (subjects) ignorando mayúsculas y minúsculas, o si un array (authorNames) contiene determinado texto.

Modificar el repositorio inicial para que extienda la interfaz a medida

El último paso será modificar el repositorio que hemos definido inicialmente en la interfaz BookRepository para que también extienda la interfaz a medida:

1/* ... */
2public interface BookRepository extends MongoRepository<Book, String>, BookRepositoryCustom {
3/* ... */

Probando el repositorio a medida

La prueba unitaria de repositorios empleando una base de datos MongoDB embebida se mostrará en otro tutorial.

Vamos a crear una clase BookRepositoryTest que probará el método auto-implementado por Spring y nuestro método a medida query.

1/* ... */
2@RunWith(SpringJUnit4ClassRunner.class)
3@ContextConfiguration(classes = {BookRepository.class})
4@EnableMongoRepositories()
5@Import(EmbeddedMongoConfiguration.class)
6public class BookRepositoryTest {
7/* ... */
8  @Test
9  public void findByTitleContainingOrderByTitle_existingTitle_shouldReturnList() {
10    // Given
11    // DB with default books
12    final String existingBookPartialTitle = "lean Code";
13
14    // When
15    final List<Book> books = bookRepository.findByTitleContainingOrderByTitle(existingBookPartialTitle);
16
17    // Then
18    final int expectedCount = 1;
19    Assert.assertEquals(expectedCount, books.size());
20    Assert.assertEquals(books.size(), books.stream().filter(
21        b -> b.getTitle().contains(existingBookPartialTitle)).count());
22  }
23/* ... */
24  @Test
25  public void query_combinedQuery_shouldReturnList() {
26    // Given
27    // DB with default books
28    final String authorName = "Laakmann McDow";
29    final Date dateAfter = Date.from(LocalDate.of(2011, 8, 22)
30        .atStartOfDay().atZone(ZoneId.of(GMT_ZONE_ID)).toInstant());
31    final Date dateBefore = Date.from(LocalDate.of(2011, 8, 22)
32        .atTime(LocalTime.MAX).atZone(ZoneId.of(GMT_ZONE_ID)).toInstant());
33    final String subject = "JOB HUNTING";
34    final DynamicQuery dynamicQuery = new DynamicQuery();
35    dynamicQuery.setAuthorNameLike(authorName);
36    dynamicQuery.setPublishDateAfter(dateAfter);
37    dynamicQuery.setPublishDateBefore(dateBefore);
38    dynamicQuery.setSubject(subject);
39
40    // When
41    final List<Book> books = bookRepository.query(dynamicQuery);
42
43    // Then
44    final int expectedCount = 1;
45    Assert.assertEquals(expectedCount, books.size());
46  }
47/* ... */
48}

El primer test del fragmento mostrado (findByTitleContainingOrderByTitle_existingTitle_shouldReturnList) comprueba que el método auto-implementado findByTitleContainingOrderByTitle funciona como se espera, devolviendo una lista de libros cuyo título contenga una parte del texto proporcionado teniendo en cuenta mayúsculas y minúsculas.

El segundo test del fragmento (query_combinedQuery_shouldReturnList) comprueba que nuestra implementación a medida del método query funciona para un DynamicQuery con varios campos definidos.

Desde una perspectiva a más bajo nivel, podemos ver que el objeto representando la implementación de la interfaz BookRepository es sencillamente un Proxy:

BookRepository is Proxy
BookRepository is Proxy

Dependiendo del método que invoquemos, el proxy llamará a una implementación u otra (i.e. SimpleMongoRepository o BookRepositoryImpl).

Conclusión

En esta publicación hemos visto como añadir métodos a medida para un repositorio MongoDB de Spring-Data y como implementarlos.

Una de las principales cuestiones a tener en cuenta, si no se va a proporcionar una configuración específica, es la de respetar las convenciones de nombres para las interfaces a medida y sus respectivas implementaciones.

El código fuente completo de este artículo puede encontrarse en GitHub.

Spring-Data + MongoDB
Spring-Data + MongoDB
Twitter iconFacebook iconLinkedIn iconPinterest iconEmail icon

Navegador de artículos
Java 8 Streams: Convertir List en MapInyección de campos desaconsejada “Field injection is not recommended” – Spring IOC
© 2007 - 2025 Marc Nuri