Saltar a contenido

140. Spring Boot con Mongodb

Algunos puntos a tener en cuenta con MongoDB

  • Dado que MongoDB es una base de datos basada en documentos sin relaciones integradas como las que encontrarías en una base de datos relacional, debes gestionar manualmente estas relaciones en tu código.

140.1 Configuración del enlace : application.yml

Esta configuración sirve de ejemplo:

spring:
  data:
    mongodb:
      authentication-database: admin
      username: root
      password: example
      database: transporte
      port: 27017
      host: localhost

140.2 Entidades para el ejemplo

@Document("restaurants")
data class Restaurant(
    @Id
    val id: ObjectId = ObjectId(),
    val address: Address = Address(),
    val borough: String = "",
    val cuisine: String = "",
    val grades: List<Grade> = emptyList(),
    val name: String = "",
    @Field("restaurant_id")
    val restaurantId: String = ""
)

data class Address(
    val building: String = "",
    val street: String = "",
    val zipcode: String = "",
    @Field("coord")
    val coordinate: List<Double> = emptyList()
)

data class Grade(
    val date: Date = Date(),
    @Field("grade")
    val rating: String = "",
    val score: Int = 0
)
* @Document: This declares that this data class represents a document in Atlas. * @Field: This is used to define an alias name for a property in the document, like coord for coordinate in Address model.

140.3 Operación de lectura

@RestController
@RequestMapping("/restaurants")
class Controller(@Autowired val repo: Repo) {

    @GetMapping
    fun getCount(): Int {
        return repo.findAll().count()
    }
}
restaurantId no está marcado con @Id ( es un campo más)

Podemos declarar la búsqueda por este campo

interface Repo : MongoRepository<Restaurant, String> {
    fun findByRestaurantId(restaurantId: String): Restaurant?
}

(Obervacion: el campo se llama restauranId, que se añade como se muestra en la sentencia anterior)

140.4 Operación de escritura:

    val restaurant = Restaurant().copy(name = "sample", restaurantId = "33332")
    repo.insert(restaurant)

140.5 Operacion de actualización

MongoDB no tiene una operación Update por lo que se hace una lectura y escritura

    @PatchMapping("/{id}")
fun updateRestaurant(@PathVariable("id") id: String): Restaurant? {
    return repo.findByRestaurantId(restaurantId = id)?.let {
        repo.save(it.copy(name = "Update"))
    }
}

140.6 Borrado

    @DeleteMapping("/{id}")
fun deleteRestaurant(@PathVariable("id") id: String) {
    repo.findByRestaurantId(id)?.let {
        repo.delete(it)
    }
}

140.7 Modelo de relaciones entre documentos

140.7.1 Relación one-to-one

Usamos un modelo de datos embebido en otro. Por ejemplo:

// patron document
{
   _id: "joe",
   name: "Joe Bookreader"
}
// address document
{
   street: "123 Fake Street",
   city: "Faketon",
   state: "MA",
   zip: "12345"
}
// Lo usamos : 
{
   _id: "joe",
   name: "Joe Bookreader",
   address: {
              street: "123 Fake Street",
              city: "Faketon",
              state: "MA",
              zip: "12345"
            }
}

140.7.2 Relación One-to-Many

// patron document
{
   _id: "joe",
   name: "Joe Bookreader"
}

// address one
{
   street: "123 Fake Street",
   city: "Faketon",
   state: "MA",
   zip: "12345"
}

// address two
{
   street: "1 Some Other Street",
   city: "Boston",
   state: "MA",
   zip: "12345"
}
// de forma embebida:

{
   "_id": "joe",
   "name": "Joe Bookreader",
   "addresses": [
      {
         "street": "123 Fake Street",
         "city": "Faketon",
         "state": "MA",
         "zip": "12345"
      },
      {
         "street": "1 Some Other Street",
         "city": "Boston",
         "state": "MA",
         "zip": "12345"
      }
   ]
 }

140.7.3 Modelo One-to-Manu con referencias en documentos.

{
   name: "O'Reilly Media",
   founded: 1980,
   location: "CA",
   books: [123456789, 234567890, ...]
}

{
    _id: 123456789,
    title: "MongoDB: The Definitive Guide",
    author: [ "Kristina Chodorow", "Mike Dirolf" ],
    published_date: ISODate("2010-09-24"),
    pages: 216,
    language: "English"
}

{
   _id: 234567890,
   title: "50 Tips and Tricks for MongoDB Developer",
   author: "Kristina Chodorow",
   published_date: ISODate("2011-05-06"),
   pages: 68,
   language: "English"
}

140.8 Modelo de relacion spring-data-mongodb

Documentación

Usando @DocumentReference

class Publisher {
    // …
    @DocumentReference
    List<Book> books;
}
Por defecto, la declaración anterior indica a la capa de 'mapping' extraer el id de la entidad refenciada para almacenar.

{
    
    "books" : ["617cfb",  ]
}

La relación inversa entre Book y Plublisher se puede modelar de la misma forma.

class Book {
    // …
    @DocumentReference(lazy=true)
    private Publisher publisher;
}

Observación: Cuando se añade un nuevo Book se debe añadir también a la lista de books en Publisher para establecer el enlace. Y para asegurar la atomizidad de la operación debemos usar Transaciones

140.9 One-To-Many

Es posible guardar la relación solamente en el documento Book sin insertar la invera en Publisher.
Para esto necesitamos dos cosas: indicar a la capa de mapping que omita guardar el enlace en Publisher y actualizar la consulta de busqueda cada vez que se recupere Books.

La primera parte se consigue con la anotación en lapropiedad books de Publisher: @ReadOnlyPorperty y la segunda añadiendo el atributo lookup en la anotación @DocumentReference con una consulta personalizada:

class Publisher {
    // …
    @ReadOnlyProperty
    @DocumentReference(lookup="{'publisher':?#{#self._id} }")
    List<Book> books;
}
  • lazy = true: Este atributo indica que la carga de la referencia es perezosa (lazy loading). Es decir, el documento referenciado (Customer en este caso) no se cargará desde la base de datos hasta que sea explícitamente accedido. Esto puede mejorar el rendimiento al evitar cargas innecesarias de datos, especialmente cuando no se necesitan todos los detalles del documento referenciado de inmediato.

  • lookup = "{ 'primaryAddress' : ?#{#self._id} }": La sintaxis de lookup utiliza la notación de consulta de MongoDB. Por ejemplo, lookup="{'fieldName': ?#{#self.fieldName}}" define una consulta donde fieldName en el documento referenciado debe coincidir con el valor de fieldName en el documento actual (#self hace referencia al documento actual). Esto significa que para cada Publisher, la lista books se llenará con documentos Book cuyo campo publisher coincida con el _id del Publisher en cuestión. La consulta dentro de lookup especifica este comportamiento, permitiendo una forma flexible de referenciar documentos relacionados.

  • @ReadOnlyProperty: Esta anotación indica que la propiedad customer no debe ser considerada en las operaciones de guardado o actualización del documento Address. En otras palabras, aunque tengas una referencia al Customer en la clase Address, cualquier cambio hecho a customer no se reflejará en la base de datos cuando guardes un objeto Address. Esto asegura la integridad de los datos al mantener la relación de referencia unidireccional desde Address hacia Customer sin modificar el documento Customer al guardar Address.

¿Como sería el código de actualización?

  • Paso 1: Crear o Actualizar Publisher
    Al crear o actualizar un Publisher, después de guardar el Publisher en la base de datos, necesitarás asegurarte de que todos los Book relevantes estén correctamente asociados con este Publisher.

  • Paso 2: Actualizar la Relación Inversa en Book
    Para cada libro que deba estar asociado con el Publisher, debes actualizar su referencia a publisher para asegurarte de que apunte al Publisher correcto. Esto podría implicar buscar todos los libros que deberían estar asociados con este Publisher y actualizarlos individualmente.

Primero creamos publisher, puede que no tengamos aun libros.
A continuación insertamos book con su relación al publisherid

val publisher = PublisherWithoutBooks(name = "Editorial XYZ")
val savedPublisher = publisherRepository.save(publisher)

val book = Book(title = "El Gran Libro de Kotlin", publisherId = savedPublisher.id!!)
val savedBook = bookRepository.save(book)

140.10 Many-to-Many

Se crean colecciones en cada entidad implicada sin necesidad de tablas intermedias

140.11 Kotlin y MongoDB

ver tutorial

140.12 Fundamentos de MongoDB

140.12.1 BASES DE datos y colecciones

Los datos se organizan en MongoDB:

  • Databaes: raíz en la organización
  • Colecciones: las bases de datos se organican en colecciones que contenen Documentos
  • Documentos: Contienen datos (enteros, string, etc)

140.13 NOTAS

  • id en Mongo es un ObjectId que podemos representar por un string de 12 caracteres
  • Para operaciones financieras, mejor "Bigdecimal" que Double

140.14 Apendice

Enlaces * Directo de Mongodb *