Saltar al contenido principal

MongoDB

Introducción​

MongoDB es una base de datos NoSQL orientada a documentos. En lugar de tablas y filas, los datos se guardan en documentos con un formato similar a JSON, lo que permite una gran flexibilidad en el modelo de datos.

Netuno ofrece el recurso _mongo para la integración con MongoDB, con una abstracción low-code sobre el cliente oficial de MongoDB para Java, disponible para los distintos lenguajes de programación soportados por Netuno: JavaScript, Python, Ruby, Kotlin y Groovy.

Antes de continuar, se recomienda saber cómo crear servicios en Netuno; consulte el tutorial REST - Servicios Web.

Un ejemplo de implementación de las operaciones presentadas aquí está disponible en el servicio de ejemplo mongo.js, que se encuentra en:

  • apps/demo/server/services/samples/javascript/mongo.js

Instalación de MongoDB​

Con Docker​

Suponiendo que tenga Docker instalado, basta con descargar la imagen de MongoDB e iniciar un nuevo contenedor:

docker pull mongo
docker run -d --name mongo -p 27017:27017 mongo

De esta forma, MongoDB queda accesible en localhost:27017 sin autenticación.

Para crear el usuario de la base de datos según la configuración de la aplicación, ejecute:

docker exec -it mongo mongosh productos_db --eval 'db.createUser({ user: "usuario_productos", pwd: "12345678", roles: [{ role: "readWrite", db: "productos_db" }] })'

Con Ubuntu​

MongoDB no está incluido en los repositorios oficiales de Ubuntu, por lo que es necesario añadir el repositorio oficial antes de la instalación:

sudo apt-get install gnupg curl
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor
echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] http://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org

Una vez instalado, puede iniciar el servicio con:

sudo systemctl start mongod

Con Windows​

Ejecute el instalador descargado y, en el paso Service Configuration, active Install MongoD as a Service para que MongoDB arranque automáticamente. MongoDB queda accesible en localhost:27017 sin autenticación.

Consulte la documentación oficial de MongoDB para otras distribuciones y versiones.

Configuración de la Aplicación​

La configuración de las conexiones de MongoDB se realiza en el archivo de configuración de la aplicación, según el entorno:

  • config/_development.json

Vea más sobre los archivos de configuración de la aplicación.

Dentro del archivo de configuración, añada el bloque mongo, donde cada clave identifica una conexión:

{
...
"mongo": {
"tienda": "mongodb://usuario_tienda:12345678@localhost/tienda_db",
"productos": {
"username": "usuario_productos",
"password": "12345678",
"host": "127.0.0.1",
"port": "27017",
"database": "productos_db"
}
},
...
}

Una conexión se puede definir de dos formas:

  • Como URL de conexión — el valor es simplemente una cadena, por ejemplo "mongodb://usuario_tienda:12345678@localhost/tienda_db".
  • Como objeto de configuración — con los campos:
    • url — si se define, se utiliza directamente como URL de conexión, ignorando los demás campos.
    • protocol — el protocolo de la conexión, por defecto mongodb.
    • username y password — las credenciales de acceso.
    • host — la dirección del servidor, por defecto localhost.
    • port — el puerto de la conexión, por defecto 27017.
    • database — el nombre de la base de datos, por defecto el nombre de la clave de la conexión.
    • params — parámetros adicionales de la conexión añadidos a la URL, por ejemplo { "authSource": "admin" }. Solo se aplican cuando la URL se construye a partir de los campos del objeto.

La conexión no tiene que ser la base de datos principal de la aplicación. A diferencia de la configuración db, el bloque mongo es independiente y permite varias conexiones simultáneas.

Inicializar la Conexión​

El primer paso es inicializar el cliente de MongoDB con el recurso _mongo.init(...), pasando el nombre de la clave de configuración o directamente una URL de conexión:

const mongo = _mongo.init("productos")

También puede inicializar la conexión directamente con una URL:

const mongo = _mongo.init("mongodb://localhost:27017/productos_db")

Al llamar a _mongo.init() sin argumentos se utiliza la clave de configuración default. Se recomienda pasar siempre el nombre de la clave o la URL, para que quede explícita la conexión utilizada.

Base de Datos y Colecciones​

Con el cliente inicializado, obtenga la instancia de la base de datos con database("nombre"). A continuación puede crear colecciones y acceder a una colección existente:

const db = mongo.database("productos_db")

db.createCollection("producto")

const coleccion = db.collection("producto")

Puede probar la conexión con la base de datos con ping(), y listar todas las colecciones existentes con collectionNames():

db.ping()

const nombres = db.collectionNames()

Puede renombrar una colección con renameCollection(), pasando el nombre completo con la base de datos, o el nombre de la base de datos y el nuevo nombre de la colección por separado:

coleccion.renameCollection("productos_db.producto_archivo")

coleccion.renameCollection("archivo_db", "producto_archivo")

Para eliminar una colección y todos sus documentos, utilice drop():

coleccion.drop()

Insertar Documentos​

Los datos se representan con el recurso _val, mediante _val.map() para documentos y _val.list() para listas de valores.

Insertar un único documento​

Con insertOne() inserta un documento y obtiene el ID generado:

const id = coleccion.insertOne(
_val.map()
.set("nombre", "Portátil")
.set("cantidad", 22)
.set("precio", 100)
.set("categoria", "ordenadores")
)

Los documentos pueden contener estructuras anidadas, como listas y otros documentos:

coleccion.insertOne(
_val.map()
.set("nombre", "Tableta")
.set("etiquetas",
_val.list()
.add("promo")
.add("nuevo")
)
.set("detalles",
_val.map()
.set("color", "azul")
.set("stock", 10)
)
)

Insertar múltiples documentos​

Con insertMany() inserta varios documentos de una sola vez, recibiendo la lista de IDs generados:

const ids = coleccion.insertMany(
_val.list()
.add(
_val.map()
.set("nombre", "Smartphone")
.set("cantidad", 18)
)
.add(
_val.map()
.set("nombre", "Monitor")
.set("cantidad", 8)
)
)

Consultar Documentos​

El método find() devuelve un MongoFindIterable, a partir del cual se obtienen los documentos con all() (todos) o first() (el primero, o null si no hay ninguno).

Para obtener todos los documentos de la colección:

const documentos = coleccion.find().all()

Para consultar con un filtro, utilice la factoría _mongo.filters():

const documentos = coleccion.find(
_mongo.filters().eq("nombre", "Portátil")
).all()

Filtros​

La factoría _mongo.filters() ofrece los principales operadores de consulta de MongoDB:

OperadorDescripción
eq("campo", valor)Igual al valor.
ne("campo", valor)Distinto del valor.
gt("campo", valor)Mayor que el valor.
gte("campo", valor)Mayor o igual que el valor.
lt("campo", valor)Menor que el valor.
lte("campo", valor)Menor o igual que el valor.
in("campo", valor1, valor2, ...)Igual a cualquiera de los valores.
nin("campo", valor1, valor2, ...)Distinto de todos los valores.
and(filtro1, filtro2, ...)Coincide con todos los filtros.
or(filtro1, filtro2, ...)Coincide con al menos uno de los filtros.
not(filtro)No coincide con el filtro.
regex("campo", "patrón")Coincide con la expresión regular.
exists("campo")El campo existe en el documento.
text("búsqueda")Búsqueda de texto.
type("campo", "tipo")El campo es del tipo especificado.
mod("campo", divisor, resto)El resto de la división del campo por el divisor es igual al valor.
where("expresión")Coincide con los documentos que satisfacen una expresión JavaScript, por ejemplo "this.precio > 100".
size("campo", n)El campo de matriz tiene exactamente n elementos.

Por ejemplo, para obtener los productos con precio superior a 50 y cantidad inferior a 30:

const documentos = coleccion.find(
_mongo.filters().and(
_mongo.filters().gt("precio", 50),
_mongo.filters().lt("cantidad", 30)
)
).all()

O para obtener los productos cuyo nombre coincide con una expresión regular:

const documentos = coleccion.find(
_mongo.filters().regex("nombre", "^[PS]")
).all()

Recorrer los Resultados​

Con el resultado de la consulta puede recorrer todos los documentos encontrados:

for (const documento of coleccion.find().all()) {
_out.println(documento.getString("nombre"))
}

También puede recorrer los resultados con forEach() pasando una función:

coleccion.find().forEach((documento) =>
_out.println(documento.getString("nombre"))
)

Ordenación​

Con la factoría _mongo.sorts() se define el orden de los resultados, de forma ascendente o descendente:

const documentos = coleccion.find().sort(
_mongo.sorts().descending("precio")
).all()

Para ordenar por varios campos, utilice orderBy():

const documentos = coleccion.find().sort(
_mongo.sorts().orderBy(
_mongo.sorts().descending("precio"),
_mongo.sorts().ascending("cantidad")
)
).all()

Proyección de Campos​

Con la factoría _mongo.projections() se limitan los campos devueltos en los documentos:

const documentos = coleccion.find().projection(
_mongo.projections().include("nombre", "cantidad")
).all()

Para combinar varias proyecciones:

const documentos = coleccion.find().projection(
_mongo.projections().fields(
_mongo.projections().include("nombre", "cantidad"),
_mongo.projections().excludeId()
)
).all()

Los tipos de proyección disponibles son los siguientes:

ProyecciónDescripción
include("campo", ...)Incluye únicamente los campos especificados.
exclude("campo", ...)Excluye los campos especificados.
excludeId()Excluye el campo _id.
fields(proyección1, ...)Combina proyecciones.
slice("campo", n)Incluye únicamente los primeros n elementos del campo de matriz.
slice("campo", salto, n)Incluye n elementos del campo de matriz, a partir de después de los primeros salto elementos.
elemMatch("campo")Incluye únicamente el primer elemento del campo de matriz.
elemMatch("campo", filtro)Incluye únicamente el primer elemento de la matriz que coincide con el filtro.
computed("campo", expresión)Añade un campo calculado a partir de una expresión de agregación.

Límites y Saltos​

Con limit() y skip() se controla la cantidad de resultados devueltos:

const documentos = coleccion.find()
.skip(1)
.limit(2)
.all()

Actualizar Documentos​

Con la factoría _mongo.updates() se definen los cambios a aplicar, y con updateOne() o updateMany() se aplican los cambios en los documentos que coinciden con el filtro:

coleccion.updateOne(
_mongo.filters().eq("nombre", "Portátil"),
_mongo.updates().set("cantidad", 42)
)

coleccion.updateMany(
_mongo.filters().eq("categoria", "ordenadores"),
_mongo.updates().set("categoria", "destacado")
)

La factoría _mongo.updates() ofrece los operadores de actualización:

OperadorDescripción
set("campo", valor)Define el valor de un campo.
unset("campo")Elimina un campo del documento.
rename("campo", "nuevoNombre")Renombra un campo.
push("campo", valor)Añade un valor a un array.
combine(actualización1, actualización2, ...)Combina varias actualizaciones en una sola.

Para aplicar varios cambios de una sola vez, utilice combine():

const combinada = _mongo.updates().combine(
_mongo.updates().set("cantidad", 42),
_mongo.updates().rename("otro", "mas")
)

coleccion.updateOne(
_mongo.filters().eq("nombre", "Portátil"),
combinada
)

Encontrar y actualizar​

Con findOneAndUpdate() se encuentra y actualiza un documento de forma atómica, devolviendo el documento original (o null si no hay ninguno):

const anterior = coleccion.findOneAndUpdate(
_mongo.filters().eq("nombre", "Portátil"),
_mongo.updates().set("cantidad", 42)
)

Sustituir Documentos​

Con replaceOne() se sustituye el documento completo (a diferencia de las actualizaciones, no puede contener operadores de actualización):

coleccion.replaceOne(
_mongo.filters().eq("nombre", "Portátil"),
_val.map()
.set("nombre", "Portátil")
.set("cantidad", 50)
.set("precio", 150)
.set("categoria", "destacado")
)

Para encontrar y sustituir de forma atómica, devolviendo el documento antiguo, utilice findOneAndReplace():

const anterior = coleccion.findOneAndReplace(
_mongo.filters().eq("nombre", "Portátil"),
_val.map()
.set("nombre", "Portátil")
.set("cantidad", 50)
)

Eliminar Documentos​

Con deleteOne() y deleteMany() se eliminan los documentos que coinciden con el filtro:

coleccion.deleteOne(
_mongo.filters().eq("nombre", "Portátil")
)

coleccion.deleteMany(
_mongo.filters().eq("categoria", "inactivo")
)

Para encontrar y eliminar un documento de forma atómica, devolviendo el documento eliminado, utilice findOneAndDelete():

const anterior = coleccion.findOneAndDelete(
_mongo.filters().eq("nombre", "Tableta")
)

Para eliminar todos los documentos de la colección, pase un filtro vacío con la ayuda de _mongo.valToDoc():

coleccion.deleteMany(_mongo.valToDoc(_val.map()))

Contar Documentos​

Con countDocuments() se obtiene el número de documentos de la colección, opcionalmente con un filtro. Por su parte, estimatedDocumentCount() es más rápido porque utiliza los metadatos de la colección, pero sin filtro:

const total = coleccion.countDocuments()

const totalPrincipal = coleccion.countDocuments(
_mongo.filters().eq("categoria", "ordenadores")
)

const estimado = coleccion.estimatedDocumentCount()

Índices​

Con la factoría _mongo.indexes() se crean índices para mejorar el rendimiento de las consultas. Para crear un índice basta con pasar las especificaciones a createIndex():

coleccion.createIndex(
_mongo.indexes().ascending("cantidad")
)

coleccion.createIndex(
_mongo.indexes().compoundIndex(
_mongo.indexes().descending("precio"),
_mongo.indexes().ascending("cantidad")
)
)

Puede indicar a MongoDB qué índice utilizar en una consulta, con hint():

const documentos = coleccion.find().hint(
_mongo.valToDoc(
_val.map().set("cantidad", 1)
)
).all()

Con min() y max() se limita el rango de valores del índice recorridos por la consulta:

const documentos = coleccion.find()
.min(
_mongo.valToDoc(
_val.map().set("cantidad", 10)
)
)
.max(
_mongo.valToDoc(
_val.map().set("cantidad", 50)
)
)
.all()

Agregaciones​

Para análisis más avanzados, aggregate() permite ejecutar un pipeline de agregación, donde cada etapa se define con la factoría _mongo.aggregates() y los acumuladores con _mongo.accumulators().

Por ejemplo, para agrupar los productos por categoría y sumar el precio de cada grupo:

const documentos = coleccion.aggregate(
_mongo.aggregates().match(
_mongo.filters().eq("categoria", "ordenadores")
),
_mongo.aggregates().group(
"$categoria",
_mongo.accumulators().sum("total", "$precio")
),
_mongo.aggregates().sort(
_mongo.sorts().descending("total")
)
).all()

Las etapas de agregación disponibles son las siguientes:

EtapaDescripción
match(filtro)Selecciona únicamente los documentos que coinciden con el filtro.
group("$campo", acumuladores)Agrupa los documentos por el campo y aplica los acumuladores.
project(proyección)Añade, elimina o modifica campos en los documentos.
sort(ordenación)Ordena los documentos.
limit(n)Limita a los primeros n documentos.
skip(n)Salta los primeros n documentos.
count()Cuenta el número de documentos.
lookup("colección", "campoLocal", "campoExtranjero", "como")Ejecuta un join con otra colección.

Y los acumuladores más utilizados:

AcumuladorDescripción
sum("campo", "$expresión")Suma de los valores.
avg("campo", "$expresión")Media de los valores.
min("campo", "$expresión")Valor mínimo.
max("campo", "$expresión")Valor máximo.
first("campo", "$expresión")Valor del primer documento.
last("campo", "$expresión")Valor del último documento.
top("campo", ordenación, "$expresión")Valor superior según la ordenación.
bottom("campo", ordenación, "$expresión")Valor inferior según la ordenación.

En los acumuladores, la expresión con prefijo $ hace referencia al campo de un documento de entrada, por ejemplo "$precio".

Conversión entre Values y BSON​

En ocasiones es necesario convertir entre los objetos _val de Netuno y documentos BSON de MongoDB:

const doc = _mongo.valToDoc(
_val.map().set("nombre", "Portátil")
)

const values = _mongo.docToVal(doc)

Operaciones con Opciones​

Las factorías *Options() del recurso _mongo devuelven los objetos de opciones estándar del driver de MongoDB, y puede encadenar sus métodos directamente. Por ejemplo, upsert(true) inserta el documento cuando el filtro no coincide con ningún documento existente:

const anterior = coleccion.findOneAndUpdate(
_mongo.filters().eq("nombre", "Monitor"),
_mongo.updates().set("cantidad", 8),
_mongo.findOneAndUpdateOptions().upsert(true)
)

Cuando el documento se inserta, findOneAndUpdate() devuelve null.

Las factorías de opciones disponibles son: insertOneOptions(), insertManyOptions(), updateOptions(), findOneAndUpdateOptions(), replaceOptions(), findOneAndReplaceOptions(), deleteOptions(), findOneAndDeleteOptions(), countOptions(), estimatedDocumentCountOptions(), dropCollectionOptions() y textSearchOptions().

Cerrar la Conexión​

Al terminar, es una buena práctica cerrar la conexión con MongoDB:

_mongo.close()

Conclusión​

Con el recurso _mongo, Netuno ofrece una abstracción low-code y políglota sobre el cliente oficial de MongoDB para Java, que permite trabajar con documentos, filtros, ordenaciones, proyecciones, actualizaciones, índices y agregaciones en JavaScript, Python, Ruby, Kotlin y Groovy.

Para más detalles sobre todos los métodos disponibles, consulte la documentación del recurso _mongo y de la clase MongoCollection.

¡Feliz desarrollo!