Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
gestión, administración
y desarrollo de aplicaciones
www.fullengineeringbook.net
Capítulo 1. Introducción a JSON
1 Objetos o documentos en Mongodb con Java
1.1 Alternativa 1: org.bson.BSONObject
1.2 Alternativa 2: org.bson.Document
1.3 Alternativa 3: org.bson.BsonDocument
1.3.1 Un ejemplo: campos de tipo fecha
1.3.2 El ObjectId
Capítulo 2. Esquemas de almacenamiento de datos
1 Incrustación de objetos
2 Objetos no incrustados
3 Manipulación de una base de datos sintética con objetos incrustados
3.1 Creación de la base de datos desde Java
3.2 Consultas desde la consola de MongoDB
3.2.1 Proyecciones: consulta de unos pocos campos
3.2.2 Consultas con OR
3.2.3 Consultas con AND
3.2.4 Consultas mezclando AND y OR
3.2.5 Consultas con !=
3.2.6 Otros operadores lógicos
3.2.6.1 Ejemplos
3.2.7 Cursores
3.2.7.1 Ejemplos
Capítulo 3. Agregaciones
1 Operaciones de propósito simple
1.1 count()
1.2 distinct()
1.3 group()
1.4 Pipelines
1.4.1 $unwind
1.4.2 $group
www.fullengineeringbook.net
1.4.2.1 Agrupación por fechas
1.4.3 Cuenta del número total de llamadas
1.4.3.1 Número de llamadas recibidas por el 600000079
1.4.4 Operadores para $group
1.4.5 Operadores para los filtros
1.4.5.1 Operadores booleanos
1.4.5.2 Operadores para conjuntos
1.4.5.3 Operadores de comparación
1.4.5.4 Operadores aritméticos
1.4.5.5 Operadores de cadena
1.4.5.6 Operadores de búsqueda de texto
1.4.5.7 Operador para arrays
1.4.5.8 Operadores para variables
1.4.5.9 Operador para literales
1.4.5.10 Operadores para fechas
1.4.5.11 Operadores condicionales
1.4.5.12 Operadores de acumulación y totalización
Capítulo 4. Reglas MapReduce
1 Ejemplo básico: map, reduce y out
2 Prefiltrado con query
3 Adición de una función de finalización: finalize
4 Cálculo de la facturación mensual con MapReduce
5 Almacenamiento de scripts en el servidor
6 Más sobre el comando mapReduce
Capítulo 5. Índices
1 Uso de ensureIndex para crear índices
1.1 Índices únicos
1.2 Indexado de objetos y arrays incrustados
2 Consulta de los índices
3 Eliminación de índices
Capítulo 6. Arranque del servidor y control de acceso
1 Roles por defecto
www.fullengineeringbook.net
1.1 Roles a nivel de base de datos
1.2 Roles a nivel de servidor
1.3 Roles a nivel de clúster
1.4 Rol de súperusuario: root
2 Creación de usuarios
3 Modificación de permisos
4 Creación de roles
5 Otros comandos de administración
Capítulo 7. Gestión de réplicas
1 Conjuntos de réplica
1.1 Nodo primario
1.2 Nodos secundarios
1.3 Árbitros
1.4 Votaciones
1.5 Regreso al conjunto de réplicas de un nodo primario
2 Operaciones de lectura y escritura en los conjuntos de réplica
2.1 Configuración de operaciones de lectura: read preferences
2.2 Configuración de operaciones de escritura: el write concern
3 Creación de un conjunto de réplica
3.1 Creación del conjunto inicial
3.2 Caída de un nodo
3.3 Regreso del antiguo nodo primario
3.4 Adición de nodos
3.5 Consulta y modificación de la configuración del conjunto
3.5.1 Modificación de la configuración de la réplica
3.6 Cambio de la configuración de lectura
3.7 Cambio de la configuración de escritura
4 Arranque de los servidores con ficheros de configuración
Capítulo 8. Gestión de la base de datos desde una aplicación web Java
1 Creación de la base de datos sintética
1.1 Creación de la colección de Tarifas
1.2 Creación de la colección de Clientes
www.fullengineeringbook.net
1.3 Creación de la colección de Llamadas
2 Diseño de la aplicación
3 Un vistazo en detalle a una clase DAO
3.1 Delete
3.2 Update
3.3 Materialización de instancias (SELECT)
4 Ejecución de operaciones MapReduce
5 El MongoBroker
6 Utilización de réplicas
Capítulo 9. Índice alfabético
www.fullengineeringbook.net
www.fullengineeringbook.net
www.fullengineeringbook.net
Capítulo 1. Introducción a JSON
MongoDB es un sistema de gestión de bases de datos no relacional. Lo que Mongo
almacena son objetos (a los que se llama documentos) en lugar de filas. El formato de
almacenamiento de objetos es del estilo de JSON.
JSON (JavaScript Object Notation) es un formato estandarizado para
representación de objetos, muy utilizado en aplicaciones web, en las que deben
intercambiarse objetos entre el cliente (normalmente un navegador) y el servidor
(desarrollado en cualquiera de las muchas tecnologías disponibles: PHP, J2EE, ASPX,
etcétera).
JSON es un mecanismo de representación de objetos estandarizado por ECMA
(European Computer Manufacturers Association) y ampliamente utilizado.
Un objeto JSON es una colección no ordenada de pares (nombre, valor). Un objeto
empieza y termina, respectivamente, por {}; entre medias, se colocan los pares separados
por comas: {“nombre” : “Pepe”} es un objeto con un campo nombre cuyo valor es la
cadena Pepe.
En la figura siguiente se muestra una clase Cliente y la represetnación en JSON de
un objeto de esta clase. El campo id es de tipo numérico (el valor no va entrecomillado),
mientras que el resto son de tipo cadena.
{
“id” : 1,
“nombre” : “Pepe”,
“apellido : “Pérez”,
“dni” : “37373737X”,
“telefono” : “985123456”
}
Figura 1
int “edad” : 30
Objeto “amigo” : {
www.fullengineeringbook.net
“id” : 1,
“nombre” : “Pepe”,
“apellido : “Pérez”,
“dni” : “37373737X”,
“telefono”: “985123456”
}
Tabla 1
Para incluir caracteres especiales en los tipos de datos de tipo String disponemos
de los siguientes caracteres de escape:
\”
\
\/
\b
\f
\n
\r
\t
\u four-hex-digits: se usa para enviar cadenas con caracteres dependientes
del idioma. Por ejemplo: {“apellido” : “Žužemberk”} se codifica como
{“apellido” : “\u017du\u017eemberk”}. Véase Figura 2.
Figura 2
{
“id” : 1,
“nombre” : “Pepe”,
“apellido : “Pérez”,
“dni” : “37373737X”,
www.fullengineeringbook.net
“telefono” : “985123456”,
“llamadas” :
[ {
“origen” : “985123456”,
“destino” : “612123456”,
“duracion” : 12
},
{
“origen” : “985123456”,
“destino” : “985654321”,
“duracion” : 35
} ],
“tarifa” : {
“tipo” :
“TarifaFinDeSemana”,
“cuotaFija”: 25,
“establecimiento” :
0.35,
“importePorSegundo” : 0.01
}
}
Figura 3
jso.put(“nombre”, nombre);
jso.put(“apellido”, apellido);
jso.put(“dni”, dni);
jso.put(“telefono”, telefono);
jsa.add(llamada.toJSONString());
}
jso.put(“llamadas”, jsa);
jso.put(“tarifa”, tarifa.toJSONObject());
return jso;
www.fullengineeringbook.net
}
Figura 4
Igual que a partir de una instancia de cualquier clase se construye una instancia de
JSONObject siguiendo el método indicado en la Figura 4 (el cual se representa como una
cadena de caracteres del estilo de la mostrada en la Figura 3), se puede ejecutar el proceso
inverso: es decir, construir un JSONObject a partir de una cadena de caracteres. Para ello,
en la librería json-simple se utiliza un JSONParser. La siguiente figura construye una
instancia de tipo Llamada a partir de una cadena recibida en formato JSON que representa,
en efecto, una llamada como las de la Figura 3. Si se produce algún error (por que la
cadena no tenga el formato esperado), se lanza una ParseException, que habrá que tratar
adecuadamente en donde corresponda.
public Llamada(String llamadaEnJSON) throws ParseException {
JSONParser parser=new JSONParser();
this.duracion=Integer.parseInt(jso.get(“duracion”).toString());
}
Figura 5
www.fullengineeringbook.net
1 OBJETOS O DOCUMENTOS EN MONGODB CON JAVA
Una base de datos MongoDB puede guardar los objetos en formatos diversos,
aunque muy parecidos todos:
Como instancias de alguno de los subtipos de la interfaz
org.bson.BSONObject.
Como instancias de org.bson.Document.
Como instancias de org.bson.BsonDocument.
1.1 Alternativa 1: org.bson.BSONObject
Las especializaciones de BSONObject (sobre todo BasicDBObject y BasicDBList,
Figura 6) son el tipo de objeto utilizado en las versiones de MongoDB anteriores a la 3.0.
Las versiones posteriores aún soportan estos tipos, pero se prefiere cualquiera de los otros
dos mecanismos. Por tanto, no los utilizaremos en estos apuntes.
Figura 6
www.fullengineeringbook.net
Figura 7
r.append(“id”, id);
r.append(“nombre”, nombre);
r.append(“apellido”, apellido);
r.append(“dni”, dni);
r.append(“telefono”, telefono);
r.append(“llamadas”, dLlamadas);
r.append(“tarifa”, tarifa.toDocument());
return r;
www.fullengineeringbook.net
public Document toDocument() {
r.put(“destino”, destino);
r.put(“duracion”, duracion);
r.put(“fecha”, fecha.getTime());
return r;
r.append(“tipo”, this.getClass().getSimpleName());
r.append(“cuotaFija”, this.getCuotaFija());
r.append(“establecimiento”, this.getEstablecimiento());
r.append(“importePorSegundo”, this.getImportePorSegundo());
return r;
}
Figura 8
Document BsonDocument
www.fullengineeringbook.net
Figura 9. El mismo objeto, visto de dos formas distintas
Si nos fijamos ahora en el elemento de índice [1] (cuya key es “tarifa”), vemos en
el lado izquierdo que su value es de tipo Document, y de tipo BsonDocument en el lado
derecho. Si expandimos este campo value en ambos lados (Figura 10), vemos a la
izquierda que el campo tipo del objeto es directamente la cadena “Tarifa50Minutos” y que
el campo establecimiento es un Double; a la derecha, sin embargo, el tipo es un
BsonString y el establecimiento un BsonDouble.
Document BsonDocument
www.fullengineeringbook.net
Es decir que, aunque con ambos tipos de documentos (Document y
BsonDocument) podemos representar las mismas realidades, BsonDocument nos da un
grado adicional de “blindaje”, pues nos obliga a especificar el tipo Bson de cada uno de
los campos del objeto. Así pues, en la medida de lo posible, procuraremos utilizar
BsonDocument en nuestros desarrollos.
Los tipos BsonString, BsonDouble y el mismísimo BsonDocument son
especializaciones de BsonValue: el tipo de cualquier campo de un objeto representado en
forma de BsonDocument debe ser una de las especializaciones de BsonValue. Esta
jerarquía de tipos se muestra en la Figura 11: los campos de un objeto representado como
un BsonDocument pueden ser de cualquiera de esos tipos que se muestran: el id es un
BsonInt32; el nombre, un BsonString; la lista de llamada, un BsonArray; la tarifa, un
BsonDocument. Estos nuevos tipos de datos hacen que, probablemente, el BsonDocument
sea el formato más apropiado para manipular los objetos.
Document “amigo” : {
“id” : 1,
“nombre” : “Pepe”,
“apellido : “Pérez”,
“dni” : “37373737X”,
“telefono”: “985123456”
}
www.fullengineeringbook.net
Boolean “permitido” : true
ObjectId 12 bytes
Tabla 2
r.append(“llamadas”, llamadas);
r.append(“tarifa”, tarifa.toBsonDocument());
return r;
return r;
}
www.fullengineeringbook.net
r.append(“tipo”, new BsonString(this.getClass().getSimpleName()));
Figura 12
return r;
}
Figura 13
1.3.2 El ObjectId
Los campos de tipo ObjectId identifican unívocamente a cada objeto, lo que en
bases de datos es fundamental. Este tipo consta de 12 bytes que se interpretan de la
siguiente forma:
0 1 2 3 4 5 6 7 8 9 10 11
56 34 cd 30 4e 89 7e 2d c4 03 1e 5c
56 34 cd 32 4e 89 7e 2d d4 03 1e 5d
Figura 14
Cada vez que insertamos un objeto nuevo en la base de datos, el propio MongoDB
le asigna un campo “_id” de tipo ObjectId.
www.fullengineeringbook.net
www.fullengineeringbook.net
Capítulo 2. Esquemas de almacenamiento de
datos
Como hemos dicho, Mongo almacena los objetos como documentos en la base de
datos, bien como instancias de BSONObject, de Document o de BsonDocument. Igual que
sucede con otros modelos de datos (el relacional, por ejemplo), para un mismo diagrama
de clases existen diferentes formas de almacenamiento de sus objetos en Mongo.
Utilizaremos como ejemplo el ya conocido diagrama de clases de los clientes, las
llamadas y las tarifas:
Figura 15
www.fullengineeringbook.net
1 INCRUSTACIÓN DE OBJETOS
En este modelo, los objetos de tipo Cliente se almacenan en Mongo con su tarifa y
su colección llamadas en un solo documento (como el ejemplo del cliente de la Figura 3).
La figura siguiente muestra un pantallazo de la consola de Mongo en la que se ven
los datos del cliente CRISTIAN SANTANA, que realizó dos llamadas el 1 y 2 de junio de
2015: además de ilustrarse el campo _id (que, por cierto, no tiene nada que ver con el id
que le sigue), se muestra el BsonArray con estas llamadas.
Figura 16
www.fullengineeringbook.net
2 OBJETOS NO INCRUSTADOS
Al usar objetos no incrustados, el esquema de datos es más parecido a una base de
datos relacional clásica: en efecto, guardaremos los clientes en una colección, las tarifas en
otra y las llamadas en otra. Como cada documento de cada colección va a tener su _id,
cada Cliente conocerá el _id de su Tarifa, y cada Llamada almacenará el _id del Cliente
que la ha realizado.
www.fullengineeringbook.net
3 MANIPULACIÓN DE UNA BASE DE DATOS SINTÉTICA CON
OBJETOS INCRUSTADOS
Antes de continuar, debemos poner en marcha el servidor de base de datos, cuyo
modo más básico se explica en el Capítulo 6 (página 67).
3.1 Creación de la base de datos desde Java
Con el fin de ir aprendiendo a manipular bases de datos Mongo desde Java, en esta
sección crearemos una con datos sintéticos (es decir, artificiales). Como origen de datos,
utilizaremos el listado de los 100 nombres (de hombres y mujeres) y los 100 apellidos más
frecuentes publicados en la página web del Instituto Nacional de Estadística[1]. En la web
original, estos datos están todos en mayúsculas y sin tildes, y así los hemos mantenido.
En esta sección utilizaremos org.bson.Document como formato de almacenamiento
y manipulación de los objetos. Utilizaremos la estructura de clases de la Figura 17. Es
muy parecida a la Figura 15, pero:
Los clientes tienen dos apellidos (no solamente uno) y una fecha de
nacimiento, y habrá cuatro tipos de tarifas.
Se han sustituido las asociaciones UML por composiciones. Las
composiciones (o “agregaciones fuertes”) indican que el objeto agregado
“está dentro” del objeto agregante. Es decir, nace después del objeto
agregante y muere, como muy tarde, a la vez. En nuestro modelo de objetos
incrustados, tanto las llamadas como la tarifa están realmente dentro del
objeto cliente, con lo que es más correcto especificar estas relaciones como
composiciones que como asociaciones.
www.fullengineeringbook.net
3) Iteraremos en dos bucles anidados sobre los nombres y apellidos. En cada
iteración crearemos un objeto Java de tipo Cliente de la siguiente manera:
1. Al azar, obtendremos de los dos vectores un nombre, un apellido1 y un
apellido2.
2. Generaremos un DNI al azar, que será el valor 5.000.000 al que
sumaremos un número al azar entre 0 y 5.000.000.
3. Genraremos un número de teléfono autoincrementable, que empezará
por 600-000-000 (el 600-000-000 será el número del primer cliente, el
600-000-001 el del segundo, etcétera).
4. Generaremos una fecha de nacimiento al azar.
5. Generaremos un número al azar entre 0 y 3: si es 0, el cliente tendrá
Tarifa plana; si 1, Tarifa 50 minutos; si 2, Tarifa fin de semana; si 3,
Tarifa de tardes.
6. A continuación, generaremos para el cliente llamadas al azar, desde
mayo hasta octubre de 2015. Conseguiremos que cada cliente realice un
tercio de sus llamadas a otro cliente, simulando así un número favorito
de cada cliente (un familiar, por ejemplo).
7. Ya que en este punto tendremos toda la información necesaria de un
cliente, lo guardaremos en la base de datos e iteraremos de nuevo.
El código de carga de datos lo escribiremos en una clase CargadoraSintetica, que
se muestra en las siguientes figuras:
public static void main(String[] args) throws IOException {
final int NUMERO_DE_CLIENTES = 10;
MongoClient mongoClient=new MongoClient(“localhost”, 27017);
MongoDatabase db = mongoClient.getDatabase(“telefonosIncrustados”);
if (db.getCollection(“clientes”)==null)
db.createCollection(“clientes”);
MongoCollection<Document> clientes=db.getCollection(“clientes”);
String apellidosFileName=“/apellidos.txt”;
Vector<String> nombres=read(nombresFileName);
Vector<String> apellidos=read(apellidosFileName);
int cont=0;
Random dado=new Random();
int posNombre=dado.nextInt(nombres.size());
int posApellido1=dado.nextInt(apellidos.size());
int posApellido2=dado.nextInt(apellidos.size());
int iDNI=5000000+dado.nextInt(5000000);
int iTelefono=600000000+i;
int tarifa=dado.nextInt(4);
String nombre=nombres.get(posNombre);
www.fullengineeringbook.net
String apellido1=apellidos.get(posApellido1);
String apellido2=apellidos.get(posApellido2);
String dni=””+iDNI;
String telefono=””+iTelefono;
Cliente c=new Cliente((cont+1), nombre, apellido1, apellido2, dni, telefono, tarifa);
int favorito; String sFavorito;
do {
favorito=(600000000 + dado.nextInt(NUMERO_DE_CLIENTES));
sFavorito=”” + favorito;
} while (sFavorito.equals(telefono));
clientes.insertOne(d);
cont++;
if (cont==NUMERO_DE_CLIENTES)
break;
}
mongoClient.close();
result.add(linea);
}
fis.close();
return result;
Figura 19. Función auxiliar para leer los ficheros nombres.txt y apellidos.txt
int n=dado.nextInt(numClientes);
String telefonoDestino=”” + (600000000 + n);
if (c.getTelefono().equals(telefonoDestino))
www.fullengineeringbook.net
continue;
int duracion=dado.nextInt(600);
int year=2015;
}
}
result.set(GregorianCalendar.DAY_OF_YEAR, dayOfYear);
return result;
}
private static int randBetween(int start, int end) {
return start + (int)Math.round(Math.random() * (end - start));
Figura 21. Función auxiliar para generar una fecha de nacimiento (tomada de
http://stackoverflow.com/questions/3985392/generate-random-date-of-birth)
Figura 22
Figura 23
www.fullengineeringbook.net
Figura 24
Además del nombre aparece el _id. Este campo aparece por defecto; si queremos
excluirlo:
Figura 25
Figura 26
Figura 27
El OR, por tanto, es un objeto JSON cuyo nombre de campo es $or y cuyo valor es
un array JSON que contiene los campos y valores por los que queremos restringir el
filtrado de datos:
{
www.fullengineeringbook.net
$or : [
{ nombre : “MANUEL”},
{ nombre : “MIRIAM”}
]
}
Figura 28
Podemos buscar con OR de otra manera: si nos interesan todas las personas cuyo
primer apellido sea “BENITEZ” o “SANZ”, podemos usar el operador $in para que
busque el apellido1 en un array de valores:
Figura 29
Figura 30
www.fullengineeringbook.net
Figura 32
Figura 33
Figura 34
www.fullengineeringbook.net
Figura 35
Figura 36
Figura 37
Operador Significado
www.fullengineeringbook.net
$gt >
$gte >=
$lt <
$lte <=
$ne !=
Tabla 3
3.2.6.1 Ejemplos
Personas cuyo nombre empieza por letras anteriores a la “O”:
a) db.clientes.find({nombre : {$lt : “O”}}, {nombre : 1, _id : 0})
b) db.clientes.find({nombre : {$lte : “N”}}, {nombre : 1, _id : 0})
Personas que no se llaman ni “MIRIAM” ni “GUILLERMO”:
a) db.clientes.find({nombre : {$ne : “MIRIAM”, $ne : “GUILLERMO”}}, {nombre : 1, _id : 0})
b) db.clientes.find({nombre : {$nin : [“MIRIAM”, “GUILLERMO”]}}, {nombre : 1, _id : 0})
3.2.7 Cursores
Los datos devueltos por la operación find se llaman cursores. Un cursor es una
colección de documentos. O sea que, algebraicamente:
find : Collection Cursor
Los cursores ofrecen multitud de operaciones que pueden devolver también
cursores. De cualquiera de las operaciones find de las secciones anteriores obtenemos un
cursor. Podemos usar el cursor para, por ejemplo, sacar un listado ordenado: lo primero
que hacemos es obtener el cursor con el find y, luego, aplicarle a éste un sort. A
continuación ordenamos los clientes por apellidos y nombre. Primero, de manera
ascendente (Figura 38); luego, de manera descendente (Figura 39). Obsérvese de paso, en
estas figuras, que la consola permite introducir comandos en varias líneas.
Figura 38
www.fullengineeringbook.net
Figura 39
Podríamos hacer algo un tanto absurdo: sacar el listado ordenado por apellidos y
nombre y, el resultado, ordenarlo por nombre:
Figura 40
Los tipos de datos que tenemos en cada llamada son los siguientes:
Database
Collection
Cursor
Cursor
Cursor
Figura 41
Los cursores se recorren de principio a fin. Las operaciones más interesantes que
tenemos para manipular cursores son las siguientes:
www.fullengineeringbook.net
count(), length() y size(): devuelven el número de objetos en el cursor.
limit(n : natural): devuelve los n primeros objetos del cursor.
skip(n : natural): salta al objeto enésimo.
sort(criterio : JSON): ordena por el criterio especificado.
hasNext() : boolean: devuelve true si hay más elementos después del
elemento al que punta el puntero del cursor, y false en caso contrario.
next() : Document: devuelve el documento actual (aquel al cual apunta el
puntero del cursor).
3.2.7.1 Ejemplos
Número de elementos en una colección:
a) db.clientes.find().size()
b) db.clientes.find().length()
c) db.clientes.find().count()
Todos los datos del primer cliente de la colección:
db.clientes.find().next()
Nombre del primer cliente de la colección:
db.clientes.find().next().nombre
En este último ejemplo, obsérvese que:
Database
Collection
Cursor
Document
String
Figura 42
www.fullengineeringbook.net
Nombre y apellidos del primer cliente de la colección. En este caso, asignaremos
el cliente a una variable utilizando notación JavaScript. Luego, accedemos a los campos
que nos interesen:
Figura 43
www.fullengineeringbook.net
www.fullengineeringbook.net
Capítulo 3. Agregaciones
Las agregaciones son conjuntos de operaciones que procesan conjuntos de objetos
y realizan algún tipo de cálculo con ellos. Hay tres mecanismos básicos para realizar
agregaciones:
Operaciones de propósito simple
Pipelines
Reglas MapReduce
www.fullengineeringbook.net
1 OPERACIONES DE PROPÓSITO SIMPLE
En esta categoría encontramos operaciones muy sencillas y limitadas, pero que
ofrecen resultados potentes de forma muy cómoda.
1.1 count()
Devuelve el número de objetos (o documentos) en la colección.
db.clientes.count()
1.3 group()
Esta operación equivale al Select… group by de SQL. Es decir, agrupa los
elementos de la colección de acuerdo a algún campo y realiza con ellos un cálculo que
normalmente es sencillo pero que puede ser muy complicado.
Saquemos, a partir del esquema de la figura Figura 17, un listado de los clientes
con el número de llamadas que ha hecho cada uno:
Figura 44
En el código de la Figura 44, group toma como argumento un JSON que puede
tener hasta cuatro campos. En el ejemplo estamos utilizando estos tres:
key: objeto JSON en el que listamos los campos que queremos mostrar. En
el ejemplo, son los campos nombre, apellido1 y apellido2. Los ponemos a
1, pero realmente da igual el valor que asignemos.
reduce: función en JavaScript con la operación que se desea hacer. Esta
función toma siempre dos argumentos:
El primero es el objeto actual, sobre el que se está iterando. Lo
hemos llamado cliente porque, en este ejemplo, la función de
www.fullengineeringbook.net
agrupación se ejecuta sobre la colección de clientes, con lo que en
cada iteración se procesa un cliente.
El segundo es un objeto que representa el resultado, y por eso lo
llamamos así. En este objeto creamos uno o más campos (en este
caso sólo uno, llamadas) al que asignamos el cómputo deseado (la
longitud del array de llamadas del cliente actual).
initial: aquí asignamos el valor inicial al campo o campos del objeto
resultado que utilizamos en la función reduce.
La función del ejemplo devuelve un array JavaScript de objetos (lado izquierdo de
la Figura 45). Si el comando de la Figura 44 lo hubiéramos asignado a una variable x (es
decir: x=db.clientes.group(…) ), podríamos acceder a cualquier elemento del array mediante su
posición (lado derecho de la Figura 45).
Figura 45
Arrays en JavaScript
Es importante tener en cuenta que el resultado devuelto por group() es, como
hemos dicho, un array, no una collection ni un cursor, por lo que no
podemos utilizar operaciones como findOne, find, next, etcétera. Para
manipularlo este resultado desde la consola de Mongo, podemos usar las
funciones de JavaScript para arrays: entre otras, las de la Tabla 4:
www.fullengineeringbook.net
sort(funciónDeOrdenaciónEnJS) Ordena los elementos utilizando para ello la
función en JavaScript que se pasa como
parámetro
Tabla 4
Figura 46
Figura 47
Otro de los posibles campos que podemos usar en el group es cond, con el que
especificamos una condición sobre los objetos que deseamos agrupar. Por ejemplo:
número de llamadas de los clientes nacidos a partir del 1 de enero de 1997 (o sea, aquellos
cuya fecha de nacimiento sea posterior al 31/12/1996: ojo al formato de la fecha, que ha
de ir como mes/día/año: en otro caso, Mongo no nos advierte de ningún error, pero los
resultados pueden no ser los deseados):
www.fullengineeringbook.net
Figura 48
Figura 49
Obsérvese que, además del código que hemos añadido a la propia función
(declaración de la variable duracionMaxima, el bucle y la asignación al campo
resultado.duracionMaxima), en el campo initial establecemos el valor inicial del campo
resultado.duracionMaxima.
Igualmente, podemos agrupar o poner condiciones sobre campos de objetos
incrustados. Si queremos restringir la consulta de la Figura 49 a aquellos clientes que
tengan la tarifa Tarifa tardes, restringimos por tarifa.tipo=“TarifaTardes” mediante el
campo cond:
www.fullengineeringbook.net
Figura 50
Figura 51
www.fullengineeringbook.net
Figura 52
Para finalizar con el group, en el siguiente ejemplo restringimos un poco más: los
mismos datos pero para los clientes que tengan la TarifaTardes o la Tarifa50Minutos:
Figura 53
1.4 Pipelines
Un pipeline es una secuencia de comandos. En la consola de Unix, por ejemplo, se
utilizan mucho, separando los distintos comandos con la barra vertical (|).
A los pipelines se los llama también modelo de tuberías y filtros: un filtro recibe
datos de entrada de una tubería, los procesa y los saca por otra tubería. Los Diagramas de
Flujo de Datos se corresponden también con modelos de pipeline. En Mongo, a las
ocurrencias de cada filtro se las llama “etapas” (stages).
Un pipeline se inicia con la operación aggregate aplicada a una colección.
aggregate toma como parámetros una lista de los filtros que desean aplicarse. Así pues, la
colección es la primera “tubería”, y a estos datos se les van aplicando los diversos filtros
www.fullengineeringbook.net
hasta llegar al último, que produce la salida que se haya programado.
El filtro utilizado en el ejemplo del cuadro anterior anterior es el $match, que
devuelve sin cambios cada documento (u objeto) que casa o “matchea” con la condición.
Pero hay varios más:
www.fullengineeringbook.net
$limit Limita el nº de objetos en el db.clientes.aggregate(
resultado
{ $limit : 20}
)
Tabla 5. Filtros
1.4.1 $unwind
Supongamos que tenemos la colección de la figura siguiente, que muestra
acortadamente una posible lista de clientes. El primero tiene un array de 10 llamadas, el
segundo de 8 llamadas y el tercero, 12. Al aplicar $unwind, se obtiene un array formado
por 10+8+12=30 objetos. En cada uno de estos objetos se almacenan los campos del
cliente y los campos de la llamada:
www.fullengineeringbook.net
{ nombre : “Pepe”, [
llamadas : [ {
{ destino : “600000000”, duracion : 20}, nombre : “Pepe”,
{ destino : “607000000”, duracion : 15}, llamadas : { destino : “600000000”,
… duracion : 20}
]}, },
{ nombre : “Ana”, {
llamadas : [ nombre : “Pepe”,
{ destino : “600245000”, duracion : 18}, llamadas : { destino : “607000000”,
{ destino : “604500000”, duracion : 11}, duracion : 15}
… },
]}, …
… {
{ nombre : “Juan”, nombre : “Ana”,
llamadas : [ llamadas : { destino : “600245000”,
{ destino : “601234000”, duracion : 20}, duracion : 18}
{ destino : “607028000”, duracion : 45}, },
… …
]} {
nombre : “Juan”,
llamadas : { destino : “607028000”,
duracion : 45}
}
]
Figura 54
www.fullengineeringbook.net
Figura 55
Figura 56
www.fullengineeringbook.net
Figura 57
www.fullengineeringbook.net
Figura 59. Proyección del año en que se realizan las llamadas
Del mismo modo que utilizamos la función $year podemos utilizar cualquiera de
las otras. Pero, para nuestro particular, nos viene muy bien la función $dateToString, que
transforma una fecha a una cadena con el formato que le especifiquemos. Esta función
toma dos parámetros (format y date), de esta manera:
{ $dateToString: { format: <formatString>, date: <dateExpression> } }
Como primer parámetro pasamos una cadena con una o más máscaras de
extracción de campos de la fecha (Tabla 6); como segundo parámetro, el campo de tipo
fecha que deseamos manipular.
%Y Año 0000-9999
%m Mes 01-12
%d Día 01-31
%H Hora 00-23
%M Minuto 00-59
%S Segundo 00-60
%L Milisegundo 000-999
A nosotros nos interesa agrupar por día, mes y año. Por tanto, modificaremos la
www.fullengineeringbook.net
agregación de la Figura 59, sustituyendo la última proyección (la que extraía el año) de
esta manera:
Figura 60
El resultado es una lista con las fechas (en formato año-mes-día, porque así lo
hemos impuesto con la máscara %Y-%m-%d) de cada llamada. Ahora sí que estamos en
disposición de agrupar por este valor. Para ello, añadimos por fin un filtro $group:
Figura 61
$group toma al menos un parámetro, el _id, que representa el campo por el cual
vamos a agrupar. El resto de parámetros son opcionales y se utilizan para realizar cálculos
de totalización o acumulación (sumas, cuentas, medias, etcétera):
{ $group: {
_id: <expresión>,
<campo1>: { <acumulador1> : <expresión1> }, …
}
El pipeline de la Figura 61
En el $group que hemos añadido a la Figura 61 agrupamos por el
campo fechaDeLlamada. Este campo procede del $project previo: el
campo fechaDeLlamada es el resultado de aplicar la función
$dateToString a la fecha de cada llamada.
www.fullengineeringbook.net
Es claro entonces el encadenamiento, en el más estricto sentido del
pipes and filters, que produce la expresión de la figura:
{$unwind : “$llamadas”} produce una lista de campos _id, nombre, …,
llamadas
{$project : { “llamadas.fecha” : 1 …}} produce una lista de
llamadas.fecha, eliminando el resto de campos que produjo el
$unwind
{$project : { fechaDeLlamada : …}} crea un nuevo campo
fechaDeLlamada, que es el que se procesa en el $group.
{$group: {_id : “$fechaDeLlamada”}} realiza la agrupación mediante el
campo especificado en el _id. Los campos que estaban accesibles
en los filtros previos (el nombre del cliente, por ejemplo), no
están ya accesibles aquí.
En la Figura 60 veíamos que aparecen valores de fecha duplicados porque, como
allí no agrupábamos, Mongo mostraba la fecha de cada una de las llamadas, incluidas las
que se producían el mismo día. En la Figura 61 ya sí estamos agrupando, por lo que no
hay valores repetidos. Para comprobarlo, le podemos añadir un filtro $sort para que
ordene por la fechaDeLlamada. Primero, hagámoslo como en la Figura 62, a la que
agregamos un filtro {$sort : {fechaDeLlamada : 1}}. Los resultados son exactamente
iguales a los de la Figura 61:
¿Qué es lo que falla en el aggregate de la Figura 62? Si el lector no cae, tal vez le
venga bien releer el comentario sobre El pipeline de la Figura 61.
La solución aparece en la Figura 63: es una sútil pero importante diferencia.
www.fullengineeringbook.net
Figura 63. El pipeline de la Figura 62, corregido para que haga lo que queremos
En la figura siguiente pondremos derecha la Figura 63, pero añadiendo ahora uno
de los parámetros opcionales al filtro $group: hemos dicho que $group toma un primer
parámetro _id que indica el campo por el que agrupamos, y que puede tomar uno o más
parámetros opcionales para hacer cálculos de acumulación o totalización, y esto es
precisamente lo que queremos, puesto que deseábamos contar el número de llamadas
realizadas en cada día del periodo: lo que haremos es crear un campo totalLlamadas que
sume el valor 1 por cada llamada:
Figura 64
www.fullengineeringbook.net
Figura 65
Figura 66
Existen por supuesto otras maneras de contar, como la del cuadro siguiente, y
como la que veremos en la Figura 80 de la sección 1.4.5, dedicada a los operadores para
los filtros.
Como sabemos (sección 3.2.7 del Capítulo 2, página 24), skip(n) salta
hasta el objeto n-simo, devolviendo un cursor formado por todos los
objetos desde el n-simo hasta el último. En el
ejemplo, db.clientes.find().skip(57) devuelve un cursor formado por todos los
clientes desde el 57º hasta el 87º (hay 87 clientes en este momento, en
www.fullengineeringbook.net
el estado actual de la base de datos). En efecto:
Figura 67
Figura 68
Figura 69
Figura 70
Figura 71
www.fullengineeringbook.net
1.4.3.1 Número de llamadas recibidas por el 600000079
Al final de la sección 1.4.1 hemos visto cómo recuperar el número de llamadas
realizadas al 600000079 mediante el acceso al campo length del objeto result devuelto por
el aggregate, lo cual funciona para versiones antiguas de Mongo. En el ejemplo anterior,
hemos contado el número de llamadas realizado en cada día del período, para lo que
hemos utilizado la función {$sum : 1} como segundo campo de un filtro $group.
Pues ahora haremos algo parecido a lo que hacíamos allá: aplanaremos la lista de
llamadas, extraeremos con un $project el campo llamadas.destino, filtraremos con un
$match aquellas llamadas realizadas al 600000079 y, por último, las agruparemos con un
$group y las contaremos con $sum:
Figura 72
Figura 73
Operador Descripción
www.fullengineeringbook.net
{$first: expr} Primer elemento
{$push: expr} Devuelve un array con los valores resultantes de aplicar la expresión a
los documentos de cada grupo devueltos por el $group
{$addToSet: expr} Devuelve un array de valores sin repetir, que es el resultado de aplicar
la expresión a los elementos de cada grupo devueltos por el $group
Figura 74
Figura 75
www.fullengineeringbook.net
Figura 76
Figura 77
Duración de las llamadas más larga y más corta de cada cliente ($max y
$min):
Figura 78
Listado de los teléfonos, cada uno con un array de las duraciones de sus
llamadas ($push):
Figura 79
www.fullengineeringbook.net
Para calcular el tamaño de un campo de tipo array, por ejemplo, se utiliza el operador
$size. Si queremos calcular el número de llamadas realizado por cada cliente de una
manera distinta a como lo hemos hecho en ejemplos anteriores, podemos escribir el
aggregate de la Figura 80, en la que hacemos una proyección de dos campos: el nombre y
n, siendo n un campo “artificial” al que asignamos como valor el tamaño del array de
llamadas.
Figura 80. Todavía una forma adicional de contar las llamadas de cada cliente
Figura 81. Clientes menores de edad (a fecha de 26/11/2015) que llaman más de 200 veces, resuelto con dos $match
Figura 82. Como en la Figura 81, pero con un solo $match y un $and
Queremos enviar un SMS a los clientes que cumplan años cierto día o que sea su
santo. Supondremos que hoy se celebra San Antonio y que es, aunque no coincida la
fecha, es 28 de octubre.
Buscaremos con un $or a todos los clientes que cumplan uno de estos dos criterios.
Pero vayamos poco a poco: primero, saquemos los teléfonos de aquellos clientes que
cumplan años: es decir, que el día y el mes de su fechaDeNacimiento coincidan con los de
www.fullengineeringbook.net
la fecha buscada, el 28 de octubre: proyectamos los tres campos que nos interesan
(telefono, dia y mes: estos dos últimos los extraemos con dos operaciones de fecha) y
luego filtramos con un $match para extraer los objetos cuyos mes y día casen con los
buscados (28 y 10):
Figura 83
Ahora, por otro lado, saquemos aquellosclientes que se llaman ANTONIO: una
búsqueda rápida con db.clientes.find({nombre : “ANTONIO”}, {nombre : 1, _id : 0}).size() devuelve 0,
indicando aparentemente que no hay Antonios; pero tal vez haya algún cliente que lo
tenga de segundo nombre. Busquemos utilizando una expresión regular:
Figura 84
En la figura anterior, estamos buscando todos los objetos cuyo campo nombre case
con la expresión regular que empieza por cualquier cosa, termina por cualquier cosa, pero
tiene ANTONIO entre medias. Obsérvese que tenemos a un tal JOSE ANTONIO.
Utilizando un aggregate, con la siguiente consulta obtenemos el mismo resultado
que con el find de la Figura 84:
Figura 85
www.fullengineeringbook.net
Figura 86
Figura 87
www.fullengineeringbook.net
Figura 88
Figura 89
Figura 90
1.4.5.2 Operadores para conjuntos
Los operadores para conjuntos operan sobre los campos de tipo array de cada
objeto, tratándolos como conjuntos (es decir, ignorando los elementos duplicados).
Disponemos de los operadores que aparecen en la Tabla 8:
Operador Descripción
www.fullengineeringbook.net
$setUnion Devuelve la unión de los conjuntos pasados
como parámetros.
Tabla 8
Utilizando estos operadores, queremos sacar la lista de números que han llamado
al 600000079. Para ello, vamos a pedirle a Mongo que nos dé los números de teléfono de
aquellos clientes cuyos arrays de llamadas contengan al array [“600000079”]. En otras
palabras, pediremos aquellos valores del campo telefono de los clientes en los que se
verifique que [“600000079”] es un subconjunto del campo llamadas.
Primer paso: sacar el valor del campo existente telefono y del campo artificial
haLlamado: éste es un valor booleano (véase Tabla 8) que devuelve, para cada objeto, si
se ha llamado o no al número buscado. Obsérvese que se devuelve un objeto por cada
cliente: el campo haLlamado vale true a veces y false otras.
Figura 91
Segundo paso: añadamos un filtro $match al pipeline anterior para que sólo
muestre los números que sí han llamado al número buscado:
www.fullengineeringbook.net
Figura 92
Figura 93
En el siguiente pipeline calculamos, para cada teléfono, parte del importe total de
la factura: de momento, sumamos a la cuotaFija de la tarifa el producto de multiplicar el
numeroDeLlamadas (un campo artificial que recuperamos en el primer $project) por el
establecimiento de llamada correspondiente a la tarifa del cliente. Obsérvese que add,
multiply, etcétera toman un solo parámetro de tipo array, que contiene los valores que se
quieren sumar, multiplicar, etcétera:
www.fullengineeringbook.net
Figura 94
Operación Descripción
Tabla 9
www.fullengineeringbook.net
Figura 95
Figura 96
Figura 98
www.fullengineeringbook.net
Figura 99
www.fullengineeringbook.net
www.fullengineeringbook.net
Capítulo 4. Reglas MapReduce
Las reglas MapReduce permiten manipular grandes cantidades de datos,
transformándolos en pequeñas agregaciones con las que resulta más sencillo operar.
Las colecciones disponen de la operación mapReduce, que puede tomar los
siguientes parámetros:
mapReduce(
map : función JavaScript,
reduce : función JavaScript,
out : colección,
query : documento,
sort : documento,
scope : documento,
limit : número,
finalize : función JavaScript,
jsMode : booleano,
verbose : booleano)
Fijémonos de momento en los tres primeros:
La función map extrae de la colección los campos que estemos interesados
en tratar, devolviéndolos en forma de pares clave, valor, en donde el valor
es un array de todos los objetos asociados a la clave.
reduce aplica el cálculo en el que estemos interesados a los datos
producidos por la función map.
Finalmente, out representa la colección en la que Mongo dejará los
resultados obtenidos.
www.fullengineeringbook.net
1 EJEMPLO BÁSICO: MAP, REDUCE Y OUT
Hagamos un primer ejemplo para calcular cuánto tiempo ha hablado cada cliente.
Como sabemos, nuestra colección de clientes tiene la estructura que se muestra en la
Figura 100. Nuestra regla MapReduce ha de producir una colección nueva, que
llamaremos por ejemplo “duraciones”, en la que figure el número de teléfono de cada
cliente y la suma total de las duraciones de sus llamadas.
La función map es una función JavaScript sin parámetros que se aplica a cada
elemento de la colección: realiza el procesamiento que se indique con cada objeto y
produce un par (clave, valor) que se coloca en un mapa:
La primera vez que al mapa le llega un valor con cierta clave, se le crea una
entrada en el mapa para esa clave y se coloca el valor en su lista de valores.
La segunda o sucesiva vez que le llega un valor al mapa para una clave ya
existente en el mapa, se añade el valor a su lista de valores.
Para los datos de la colección de clientes de la Figura 100, si se utiliza el valor del
campo telefono como clave y la duración de las llamadas como valor, se obtendría el
siguiente mapa tras ejecutar la función map:
www.fullengineeringbook.net
Clave Valor
Figura 101
La función reduce, por otro lado, procesa cada entrada del mapa de la manera que
se especifique en su código. reduce sí toma parámetros: la clave y el valor. Según el
enunciado de nuestro ejemplo, reduce debería transformar el mapa de la Figura 101 y
dejarlo de la siguiente manera:
Clave Valor
“600123456” 35
“600123457” 29
“600123458” 65
Figura 102
Figura 103
www.fullengineeringbook.net
los recoge de éste. El código de reduce se muestra en la Figura 104: la función toma los
parámetros clave y valor; el segundo es el array de duraciones correspondiente a cada
entrada del mapa: si, en el mapa de la Figura 101, la primera entrada (clave, valor) es
(“600123456”, [20, 15]), la función debe reducir el array a un solo valor que se asociará a
su clave. En la Figura 104 se muetra el código de la posible función de reducción, que
acumula en la variable tiempoCharlando la suma de las duraciones de todas las llamadas,
que vienen en el segundo parámetro de la función.
Figura 104
Una vez definidas las dos funciones map y reduce, que han sido asignadas a dos
variables JavaScript en la consola de Mongo, podemos llamar a la función mapReduce
sobre la colección a la que deba aplicarse: en el ejemplo de la Figura 105 se aplican las
dos funciones a la colección clientes para producir una nueva colección llamada
duraciones. Los resultados se resumen en las líneas siguientes: se han procesado 1000
objetos (correspondientes a 1000 clientes), se han ejecutado 270.275 emits
(correspondientes a 270.0275 llamadas), han llegado 1000 pares (clave, valor) a la función
reduce y, ésta, ha producido 1000 objetos como salida.
Figura 105
Figura 106
www.fullengineeringbook.net
2 PREFILTRADO CON QUERY
Supongamos que deseamos hacer el mismo cálculo que en la sección anterior, pero
restringiéndonos a las llamadas de los clientes que tengan un cierta tarifa. Mediante el
parámetro query se realiza una selección previa de los objetos a los que se aplicará la
función map.
Copiemos y pequemos de nuevo el ejemplo de la Figura 100, que extendemos
ahora con el campo tarifa (que, por comodidad, la escribimos en una sola cadena) de cada
cliente:
www.fullengineeringbook.net
Figura 108
www.fullengineeringbook.net
3 ADICIÓN DE UNA FUNCIÓN DE FINALIZACIÓN: FINALIZE
Se puede añadir una función de finalización para hacer un postproceso de los
objetos devueltos por la función reduce, de modo que los resultados que se viertan en la
colección especificada en el parámetro out habrán sido modificados.
Con la siguiente función, la duración total de las llamadas de cada cliente se
expresa en formato de horas, minutos y segundos:
Figura 109
Figura 110
Figura 111
www.fullengineeringbook.net
4 CÁLCULO DE LA FACTURACIÓN MENSUAL CON
MAPREDUCE
En la Figura 112 se muestra abreviadamente el campo fecha de cada llamada de
cada cliente. Supongamos que se desean facturar las llamadas del mes 6.
Figura 112
Clave Valor
www.fullengineeringbook.net
Figura 114
Figura 115
www.fullengineeringbook.net
Figura 116
www.fullengineeringbook.net
5 ALMACENAMIENTO DE SCRIPTS EN EL SERVIDOR
Todas las funciones JavaScript que vamos creando las podemos ir almacenando en
el servidor para dejarlas accesibles a los programas externos que manejen la base de datos.
Las dos funciones llamadasDeJunio y facturacionJunio creadas en la sección anterior las
guardamos, dentro de la base de datos, en una colección separada a la que podemos llamar
scripts:
Figura 117
www.fullengineeringbook.net
6 MÁS SOBRE EL COMANDO MAPREDUCE
En la sección 4 del Capítulo 8 (página 102) se muestran más opciones tanto del
comando mapReduce como de la lectura y ejecución de scripts almacenados en el
servidor.
www.fullengineeringbook.net
www.fullengineeringbook.net
Capítulo 5. Índices
Los índices permiten realizar búsquedas más rápidas, pero ralentizan las
inserciones y actualizaciones. En efecto, un índice se almacena como una estructura de
datos ordenada y separada: cuando se hace un búsqueda por un campo indexado, se
localiza el valor en el índice, el cual posee un puntero hacia la ubicación del objeto en el
fichero de datos. Al hacer una inserción, además de añadir el objeto al fichero de datos, el
valor o valores indexados deben insertarse en el fichero o ficheros de índice, lo que hace
un poco más lenta la operación.
www.fullengineeringbook.net
1 USO DE ENSUREINDEX PARA CREAR ÍNDICES
Para una colección, los índices se crean con ensureIndex, como en el siguiente
ejemplo, que acelerará las búsquedas de clientes:
Figura 118
ensureIndex recibe como parámetro la lista de campos que desean utilizarse para
crear el índice: en el ejemplo, los dos apellidos y el nombre. El valor pasado indica si el
índice se guarda en orden ascendente (+1) o descendente (-1). Todas las colecciones tienen
por defecto un índice (el del campo _id); por ello, en la salida que da Mongo en la Figura
118, nos dice que, tras ejecutar la operación, la colección clientes tiene dos índices.
1.1 Índices únicos
Si no se indica explícitamente, los índices admiten valores duplicados. Para crear
un índice único, se pasa un segundo parámetro {unique : true}. En el siguiente ejemplo
intentamos crear un índice único para el campo nombre; obviamente, Mongo devuelve un
error porque hay clientes que tienen el mismo nombre:
Figura 119
Figura 120
www.fullengineeringbook.net
2 CONSULTA DE LOS ÍNDICES
Podemos consultar qué índices tiene una colección mediante getIndexes():
Figura 121
www.fullengineeringbook.net
3 ELIMINACIÓN DE ÍNDICES
Si, de los tres índices de la figura anterior, queremos eliminar el tercero, cyo name
es “dni_1”, escribimos lo siguiente:
Figura 122
Figura 123
www.fullengineeringbook.net
www.fullengineeringbook.net
Capítulo 6. Arranque del servidor y control de
acceso
La puesta en marcha del servidor de bases de datos es bien sencilla: vamos al
directorio bin de la instalación de Mongo y escribimos el comando mongod. Sin más
modificadores, esto pondrá a la escucha el servidor en el puerto 27017. En la Figura 124,
desde un Mac:
Una vez lanzado el servidor, en otra terminal vamos al directorio bin y ejecutamos
el comando mongo (Figura 125).
En principio, el arranque del servidor como hemos hecho arriba no habilita ningún
mecanismo de control de acceso: cualquiera puede conectarse a nuestro servidor y hacer
cualquier manipulación sobre las bases de datos y colecciones que tengamos. Para impedir
el acceso anónimo a nuestro servidor, debemos crear usuarios y asignarles roles, y asignar
los permisos necesarios a cada rol.
www.fullengineeringbook.net
1 ROLES POR DEFECTO
Un rol define los privilegios de acceso que los usuarios con este rol tienen a los
diferentes recursos de una base de datos. El nivel máximo de granularidad es la colección:
es decir, puede permitirse que un usuario de un cierto rol tenga acceso a la colección
Clientes pero, una vez en ésta, no se le puede prohibir que lea los datos de un cierto
cliente. Existen roles a nivel de base de datos, a nivel de servidor, de copia y restauración
y a nivel de clúster. Además, existe el rol de súperusuario y el _system, que no debiera ser
asignado a personas ni aplicaciones.
1.1 Roles a nivel de base de datos
Los roles predefinidos a nivel de base de datos se resumen en la Tabla 10.
www.fullengineeringbook.net
createCollection.
Sobre las colecciones de datos, puede ejecutar las siguientes:
collMod, collStats, compact, convertToCapped,
createCollection, createIndex, dbStats, dropCollection,
dropDatabase, dropIndex, enableProfiler, indexStats, reIndex,
renameCollectionSameDB, repairDatabase, storageDetails y
validate
www.fullengineeringbook.net
2 CREACIÓN DE USUARIOS
El proceso habitual para crear usuarios es: arrancar el servidor sin control de
acceso (Figura 124), arrancar la consola (Figura 125) y crear a continuación un usuario
administrador (Figura 126). Obsérvese el campo customData, que puede contener
cualquier información. Hecho esto, se reinicia el servidor con control de acceso(se arranca
el servidor con el parámetro —auth ; es decir, en la consola del sistema operativo se
escribe: ./mongod —auth ), se autentica el usario desde la consola (Figura 127) y se crean
los usuarios que sean precisos (Figura 128).
Como se acaba de ver, los usuarios se crean con el comando createUser, que toma
como parámetro un objeto con los siguientes campos: user, pwd y roles, que es un array
formado por objetos con los campos role y db. En el ejemplo de la figura anterior, se
asigna al usuario el rol userAdminAnyDatabase, pasándose admin como base de datos.
admin es una base de datos en la que se guarda toda la información de administración del
servidor: usuarios, otras bases datos, roles, etcétera.
Figura 127. Conexión al servidor con nombre de usuario (-u) y contraseña (-p)
www.fullengineeringbook.net
Figura 128. El administrador crea un nuevo usuario con acceso readWrite para una base de datos determinada
www.fullengineeringbook.net
3 MODIFICACIÓN DE PERMISOS
Supongamos que el usuario maco (creado en la Figura 126 con el rol
userAdminAnyDatabase) desea realizar conocer cuántos clientes hay en
telefonosIncrustados. Además de mediante los parámetros –u y –p, los usuarios
administradores pueden autenticarse conectando a la base de datos de administración y
ejecutando el comando db.auth. En la Figura 130, maco se conecta a un servidor que
requiere autenticación, abre la base de datos admin y se autentica sobre la marcha con
db.auth:
Figura 131
Puesto que el usuario fue creado con el rol userAdminAnyDatabase, que permite
crear y modificar usuarios y roles, este usuario se asignará a sí mismo el rol de
readWriteAnyDatabase mediante el comando grantRolesToUser, que toma el nombre del
usuario y la lista de nuevos roles que se le quieren asignar. En la Figura 132, el usuario se
asigna el rol y, luego, recupera la lista de usuarios de admin. El usuario pepe, que creó en
la Figura 128, reside en la colección telefonosNoIncrustados.
www.fullengineeringbook.net
Figura 132. Concesión de un nuevo rol y consulta de usuarios en admin
www.fullengineeringbook.net
4 CREACIÓN DE ROLES
Además de los roles predefinidos, en Mongo se pueden crear roles nuevos y
asignarles privilegios específicos mediante el comando createRole.
createRole toma tres parámetros: role (el nombre del rol), privileges (un arrya de
privilegios) y roles (una lista de roles de los que heredar permisos, que puede ser la lista
vacía: []).
El campo privileges está formado por pares (resource, actions), en donde redouces
es la lista de los recursos sobre los que se concede permiso y actions la lista de acciones
permitidas.
En la base de datos sintética telefonosNoIncrustados, que se creará en el 0, hay tres
colecciones: clientes, llamadas y tarifas. Supongamos que deseamos crear un role llamado
atencionAlCliente que tenga acceso de lectura a llamadas, y acceso de lectura y
actualización a clientes.
En la Figura 133 se crea este rol: la lista privileges tiene dos elementos, cada uno
formado por un par (resource, actions). El primer elemento concede los permisos de
buscar y actualizar (find y update) sobre clientes; el segundo, permiso de lectura sobre
llamadas.
www.fullengineeringbook.net
Figura 134. Creación de un usuario con el nuevo rol
Cada elemento del array privileges tiene dos campos: resource y actions. resource
puede hacer referencia a una colección de una base de datos (como en la Figura 133) o a
un cluster: en este caso, el valor de resource sería {cluster : true} .
Respecto de las acciones (campo action), existen varias categorías. Las más
comunes son las siguientes:
Acción Descripción
find
insert
Permisos para ejecutar la operación indicada
remove
update
createCollection
www.fullengineeringbook.net
createRole y usuarios
createUser
dropCollection
Permisos para borrar colecciones, roles y
dropRole
usuarios
dropUser
www.fullengineeringbook.net
5 OTROS COMANDOS DE ADMINISTRACIÓN
Se puede modificar completamente una cuenta de usuario mediante el comando
updateUser, que reemplaza por completo los datos preexistentes de la cuenta. Los usuarios
se borran mediante dropUser.
El comando changeUserPassword permite la modificación de la contraseña.
Supongamos que el operador01, usuario que se ha creado en la Figura 134, desea cambiar
su password a “1234”. Puesto que el rol atencionAlCliente no tiene concedido el permiso
para modificar su propia password, el comando fallará:
Figura 137.
www.fullengineeringbook.net
www.fullengineeringbook.net
Capítulo 7. Gestión de réplicas
Una réplica es una copia idéntica de los datos de un servidor primario que se
conserva en otro servidor secundario. El servidor primario recibe las peticiones de los
clientes, mientras que los secundarios conservan solamente copias de los datos a partir de
la información que les va enviando el primario. Si el servidor primario cae, uno de los
secundarios pasa a ser el primario.
www.fullengineeringbook.net
1 CONJUNTOS DE RÉPLICA
Un conjunto de réplica es un grupo de servidores que proporcionan redundancia
respecto de los datos almacenados en otro servidor primario y alta disponibilidad de los
datos, pues si el servidor primario queda inaccesible o fuera de servicio, uno de los
secundarios pasa a ocupar su lugar mediante un sistema de votación entre los nodos
secundarios y los árbitros. Un árbitro es un nodo que no almacena copia de los datos, pero
que conoce la disponibilidad de los nodos y puede participar en la votación.
Un conjunto de réplicas requiere al menos un servidor primario, un secundario y
un árbitro, si bien lo habitual es disponer al menos de dos secundarios. El número máximo
de nodos en un conjunto de réplicas es de 50 miembros, si bien únicamente 7 tendrán
derecho a voto.
1.1 Nodo primario
El nodo primario es el único que recibe peticiones de escritura de los clientes.
Cuando se recibe una petición de escritura, el nodo primario registra la operación en una
colección de sistema llamada oplog, una colección en la que se almacenan las últimas
operaciones realizadas. De vez en cuando, los nodos secundarios leen este log y aplican a
sus datos las operaciones registradas para mantenerlos actualizados.
1.2 Nodos secundarios
Los nodos secundarios almacenan copias de la información del primario mediante
la ya mencionada lectura de la colección oplog del nodo primario. oplog es una capped
collection, es decir, una colección que tiene un tamaño máximo predeterminado[2]:
cuando la colección alcanza su tamaño máximo, comienza a eliminarse la información
más antigua, por lo que los secundarios deben actualizarse antes de que oplog empiece a
“vaciarse”.
Los secundarios no admiten peticiones de escritura desde los clientes y, por
defecto, tampoco de lectura, aunque este comportamiento puede cambiarse. No obstante,
es necesario tener en cuenta que un nodo secundario puede no estar completamente
actualizado con el primario (es decir, quizás las últimas escrituras habidas en el primario
no hayan sido todavía reflejadas en el secundario del que está leyendo). La lectura de
nodos secundarios proporciona a los clientes lo que se llama consistencia eventual; la
lectura del primario, que devuelve siempre información actualizada, se llama consistencia
estricta.
Además, los nodos secundarios se pueden configurar para que desempeñen papeles
específicos dentro del conjunto de réplicas:
De prioridad 0: son nodos que admiten peticiones de lectura y tienen
derecho a voto, pero ni pueden ser primarios ni convocar votaciones.
De réplica oculta: son nodos secundarios de prioridad 0 (no pueden ser
primarios) que, además, están ocultos para los clientes.
De réplica postergada: son nodos de réplica oculta (ni pueden ser
primarios ni estar accesibles para los clientes) que, además, contienen
copias de los datos de cierta antigüedad: es decir, leen el oplog del primario
www.fullengineeringbook.net
cada cierto tiempo. Para que estos nodos no pierdan información respecto
del nodo primario, y ya que oplog es una capped collection de tamaño
limitado, deben leer los datos de esta colección antes de que se supere su
tamaño máximo.
1.3 Árbitros
Un árbitro es un nodo que conoce al nodo primario pero que no mantiene copia de
sus datos. Si el primario cae, el árbitro puede participar en la votación con 1 voto y, puesto
que no tiene copia de los datos, no es elegible para primario. Estos nodos se utilizan para
evitar empates en las votaciones al nuevo nodo primario.
Supongamos un conjunto de réplicas con un nodo primario P, un árbitro A y tres
secundarios S0, S1, S2. Supongamos que P cae, que se produce una votación y que cada Si
vota a S(i+1)%3 (es decir, al siguiente): en este escenario, cada secundario recibe un voto,
pero el empate se resuelve gracias al “voto de calidad” de A.
1.4 Votaciones
Las votaciones se pueden celebrar en tres circunstancias:
La primera vez que se arranca el conjunto de réplicas.
Cuando un nodo secundario pierde el contacto con el primario.
Cuando el servidor primario pasa a un estado de no disponible. Un nodo
primario pasa a estar no disponible cuando recibe un comando
replSetStepDown, cuando hay votaciones y las gana un nodo secundario o
cuando el primario no tiene contacto con la mayoría de los nodos del
conjunto.
Durante el “periodo electoral” (es decir, durante el tiempo en que los nodos están
celebrando la votación) no hay nodo primario y, por tanto, el conjunto de réplicas no
admite operaciones de escritura, aunque sí de lectura.
Los nodos del conjunto de réplicas se envían constantemente (cada 2 segundos)
heartbeats (“latidos”, que son señales semejantes al ping), en las que avisan de que están
vivos. Si un latido enviado no recibe respuesta en 10 segundos, el nodo emisor marca al
receptor como inaccesible.
Cuando se crea el conjunto de réplicas se establece una prioridad para cada nodo,
de modo que, en la votación, los nodos secundarios y los árbitros preferirán votar a los
nodos que tengan más prioridad. Por defecto, la prioridad es 1, pero puede incrementarse
la prioridad de, por ejemplo, el nodo que esté geográficametne más cercano a los clientes
si se desea que éste tome el rol de nodo primario.
Tienen “derecho a voto” los nodos primarios, los secundarios, los árbitros, los
secundarios de recuperación y los secundarios de rollback.
Una vez realizada la votación, el nodo elegido puede ser vetado si no se encuentra
completamente actualizado: es decir, si el primario tiene operaciones más recientes que el
secundario. Esto se sabe mediante el optime, una marca de tiempo que almacena el
instante en que un nodo secundario ha realizado la última lectura de la colección oplog del
nodo primario.
www.fullengineeringbook.net
1.5 Regreso al conjunto de réplicas de un nodo primario
Es posible que un nodo primario reciba operaciones de escritura, no llegue a
enviarlas a ninguno de los secundarios, caiga y, por algún motivo, pasado un tiempo
regrese al conjunto de réplicas, cuando ya hay un nuevo nodo primario en funcionamiento.
En este caso, el nodo que acaba de regresar deshace (es decir, hace rollback) las
operaciones que había realizado pero de las que el resto de nodos no se habían enterado.
Esta información “deshecha” se almacena en forma de ficheros bson en la carpeta
rollbacks. En algún momento, el administrador decidirá qué hacer con estos objetos.
www.fullengineeringbook.net
2 OPERACIONES DE LECTURA Y ESCRITURA EN LOS
CONJUNTOS DE RÉPLICA
Aunque, por defecto, todas las operaciones de lectura y escritura se ejecutan sobre
el nodo primario del conjunto de réplica, estos comportamientos pueden cambiarse. A
continuación vemos algunas opciones para hacerlo.
2.1 Configuración de operaciones de lectura: read preferences
Como se ha dicho, todas las operaciones de lectura se envían, por defecto, al nodo
primario, aunque esta preferencia se puede configurar en la conexión del cliente. En este
caso existe, obviamente, el riesgo de que un cliente lea información de un nodo secundario
que no haya sido actualizada desde el primario.
Mongo permite cinco modos de especificar la lectura de datos desde los clientes:
primary, que es el modo por defecto: todas las lecturas se lanzan sobre el
nodo primario.
primaryPreferred, en la que las lecturas se hacen del nodo primario salvo
que esté fuera de servicio.
secondary, que indica al cliente que debe leer de nodos secundarios.
secondaryPreferred, para que el cliente lea de nodos secundarios salvo que
no haya ninguno disponible.
nearest, para que el cliente lea del nodo que le proporciona menor tiempo
de respuesta.
En la sección 3.6 veremos cómo cambiar esta preferencia.
2.2 Configuración de operaciones de escritura: el write concern
Respecto de las operaciones de escritura, al cliente le da igual que se estén
recibiendo en un servidor aislado o en el servidor primario de un conjunto de réplicas.
El comportamiento por defecto de una operación de escritura (por ejemplo, un
insert) espera que le llegue la confirmación de la operación sólo desde el nodo primario.
Al lanzar la operación se puede, sin embargo, esperar a que se confirme también la
actualización de los datos en más de un nodo del conjunto.
Si se desea modificar ese comportamiento por defecto para todas las operaciones,
se puede alterar el fichero de configuración del cojunto de réplica. Veremos estas
operaciones en la sección 3.7 de este mismo capítulo.
www.fullengineeringbook.net
3 CREACIÓN DE UN CONJUNTO DE RÉPLICA
En esta sección crearemos un conjunto que inicialmente estará formado por un
nodo primario y dos réplicas que correrán en la misma máquina física: ubicaremos los
servidores en los puertos 27010, 37017 y 47017 de localhost. Más adelante (sección 3.4)
añadiremos dos nodos adicionales.
3.1 Creación del conjunto inicial
Cada servidor almacenará los datos en una ubicación distinta de la misma
máquina. En este ejemplo, en tres subcarpetas (a las que, por claridad, llamaremos con el
puerto de cada host) dentro la carpeta /Users/Maco/Desktop/dbsReplica.
La figura siguiente muestras tres consolas, cada una de las cuales arranca un
servidor mongod en los puertos citados: amarilla en 27017, verde en 37017 y azul en
47017. Los parámetros que pasamos al comando mongod son:
—port para especificar el puerto
—dbpath para especificar la ubicación del directorio de datos de este
servidor
—replSet para indicar el nombre del conjunto de réplica
(replica3EnElMismoHost en este ejemplo). Como se ve, el parámetro toma
el mismo valor en las tres consolas, pues todos los servidores se refieren al
mismo conjunto.
www.fullengineeringbook.net
réplica tienen que tener o todos los nodos o ninguno en localhost. Como
añadiremos un nodo en una dirección diferente en la sección 3.4, debemos
evitar utilizar cualquiera de esos dos valores.
Una vez creada la variable que usaremos para pasar a rs.initiate, se ejecuta el
comando. Tras unos instantes, las tres consolas de servidor comienzan a mostrar mensajes
de conexión. Como recordamos (sección 1.4, sobre Votaciones, en la página 76), al crearse
un conjunto de réplicas se inicia una votación y, como vemos en el prompt de la consola
cliente, el host amarillo ha pasado a ser el servidor primario.
Si abrimos consolas clientes a los otros dos servidores, su prompts muestran que
sus correspondientes hosts son secundarios. En la Figura 141 abrimos una consola cliente
al servidor verde (puerto 37017: véase el parámetro —port pasado al ejecutar el comando
mongo) y otra al azul (puerto 47017). Sus prompts, en efecto, muestran la palabra
SECONDARY. Como sabemos, la opción por defecto impide operaciones de escritura en
estos nodos: en la misma Figura 141, los servidores secundarios verde y azul devuelven un
mensaje de error al intentar guardar un objeto en la colección prueba; en la Figura 140, sin
embargo, conseguimos insertar con éxito en el nodo primario.
Figura 140
www.fullengineeringbook.net
Figura 141. Apertura de clientes a hosts secundarios e intentos de escritura, que son rechazados
www.fullengineeringbook.net
Figura 143. Las operaciones de escritura ya se pueden ejecutar sobre un nodo secundario que ha pasado a ser
primario
www.fullengineeringbook.net
mediante el comando rs.add(“host:puerto”), tal y como hacemos en la Figura 146.
“set” : “replica3EnElMismoHost”,
“date” : ISODate(“2015-12-10T19:35:59.422Z”),
“myState” : 1,
“members” : [
{
“_id” : 1,
“name” : “192.168.1.130:27017”,
“health” : 1,
“state” : 1,
“stateStr” : “PRIMARY”,
“uptime” : 5180,
“optime” : Timestamp(1449775720, 1),
“optimeDate” : ISODate(“2015-12-10T19:28:40Z”),
“configVersion” : 5,
www.fullengineeringbook.net
“self” : true
},
{
“_id” : 2,
“name” : “192.168.1.130:37017”,
“health” : 1,
“state” : 2,
“stateStr” : “SECONDARY”,
“uptime” : 5039,
“optimeDate” : ISODate(“2015-12-10T19:28:40Z”),
“lastHeartbeat” : ISODate(“2015-12-10T19:35:58.637Z”),
“lastHeartbeatRecv” : ISODate(“2015-12-10T19:35:57.578Z”),
“pingMs” : 0,
“configVersion” : 5
},
{
“_id” : 3,
“name” : “192.168.1.130:47017”,
“health” : 1,
“state” : 2,
“stateStr” : “SECONDARY”,
“uptime” : 5039,
“optime” : Timestamp(1449775720, 1),
“optimeDate” : ISODate(“2015-12-10T19:28:40Z”),
“lastHeartbeat” : ISODate(“2015-12-10T19:35:58.637Z”),
“lastHeartbeatRecv” : ISODate(“2015-12-10T19:35:57.578Z”),
“pingMs” : 0,
“configVersion” : 5
},
{
“_id” : 4,
“name” : “192.168.1.136:27017”,
“health” : 0,
“state” : 8,
“optimeDate” : ISODate(“1970-01-01T00:00:00Z”),
“lastHeartbeat” : ISODate(“2015-12-10T19:35:50.489Z”),
“lastHeartbeatRecv” : ISODate(“1970-01-01T00:00:00Z”),
“configVersion” : -1
},
“_id” : 5,
www.fullengineeringbook.net
“name” : “alarcosj.esi.uclm.es:27017”,
“health” : 0,
“state” : 8,
“lastHeartbeatRecv” : ISODate(“1970-01-01T00:00:00Z”),
“configVersion” : -1
}
],
“ok” : 1
}
www.fullengineeringbook.net
{
“_id” : “replica3EnElMismoHost”,
“version” : 6,
“members” : [
{
“_id” : 1, “host” : “192.168.1.130:27017”, “arbiterOnly” : false,
“slaveDelay” : 0, “votes” : 1
},
“slaveDelay” : 0, “votes” : 1
},
{
},
{
“_id” : 5, “host” : “alarcosj.esi.uclm.es:27017”, “arbiterOnly” : true,
],
“settings” : {
“chainingAllowed” : true, “heartbeatTimeoutSecs” : 10, “getLastErrorModes” : { },
“getLastErrorDefaults” : {
“w” : 1,
“wtimeout” : 0
}
Como se ve, cfg tiene un campo members con la lista de nodos. Cualquiera de
estos campos puede modificarse y, luego, reaplicar la configuración con reconfig. Este es
el procedimiento que se sigue en la Figura 151.
www.fullengineeringbook.net
Por otro lado, la consulta mediante rs.conf() que hemos hecho en la Figura 150 nos
sirve para conocer los diferentes campos que pueden establecerse para determinar la
configuración del conjunto: host, arbiterOnly, buildIndexes, hidden, tags, etcétera.
3.6 Cambio de la configuración de lectura
Ya sabemos que el comportamiento por defecto impide leer de los nodos
secundarios (Figura 152), pero también sabemos que esta opción puede modificarse en las
conexiones que establecen los clientes.
En la sección 2.1 (página 77) se enunciaban los cinco modos de lectura: primary,
primaryPreferred, secondary, secondaryPreferred y nearest. Para indicar al cliente del
host azul que deseamos leer con el modo secondary, establecemos la propiedad mediante
el comando db.getMongo().setReadPref: en la Figura 153 se cambia el modo y, a
continuación, se ejecuta el find que fallaba en la figura anterior.
www.fullengineeringbook.net
escritura. Por defecto, es 1. También puede ser 0: en este caso, no se espera
confirmación, aunque sí se recibirán mensajes de error en caso de fallo de
la red.
majority: indica que se espera confirmación de escritura por la mayoría de
los nodos con derecho a voto.
Se puede especificar también un conjunto de etiquetas para indicar los
nodos de los que se espera confirmación.
www.fullengineeringbook.net
4 ARRANQUE DE LOS SERVIDORES CON FICHEROS DE
CONFIGURACIÓN
En la Figura 138 arrancábamos los servidores utilizando el parámetro —replSet en
la línea de comando. En su lugar, podemos establecer la configuración de cada servidor en
un fichero de configuración.
En la figura siguiente se muestran los contenidos de los ficheros de configuración
de los tres servidores locales. Los campos que se han incluido (puede haber muchos más)
indican la ubicación de las bases de datos de cada uno (un directorio con el nombre de su
puerto de escucha que cuelga de /Users/Maco/Desktop/dbsReplica), el puerto (27017,
37017, 47017), si admiten conexiones por http (es decir, mediante un navegador web: cada
servidor Mongo lanza un servidor web en su puerto de escucha +1000: es decir, en los
puertos 28017, 38017 y 48017), el tamaño de la colección oplog y el nombre del conjunto
de réplica.
www.fullengineeringbook.net
www.fullengineeringbook.net
Capítulo 8. Gestión de la base de datos desde
una aplicación web Java
En esta sección crearemos una aplicación web que ofrezca un pequeño conjunto de
funcionalidades para manipular una base de datos Mongo. En este caso, utilizaremos una
base de datos similar a la anterior (clientes, llamadas y tarifas) pero, ahora, de objetos no
incrustados: es decir, tendremos las tres colecciones separadas. El esquema conceptual de
la base de datos será el siguiente:
Como se ve, cada cliente conoce a su tarifa y, cada llamada, al cliente que la ha
realizado. La relación 1:1 desde Llamada a Cliente está modelando una relación 1:1 desde
Cliente hasta Llamada: en la implementación de la base de datos, cada llamada contendrá
un campo que apuntará al identificador del cliente que ha efectuado la llamada. Respecto
de la relación entre las clases Cliente y Tarifa sucede algo muy parecido: son muchos los
clientes que tienen la misma tarifa; así pues, esta relación 1:n desde Tarifa hacia Cliente la
representamos como una relación 1:1 desde Cliente hacia Tarifa: en Cliente habrá un
campo que apunte al identificador de la Tarifa.
www.fullengineeringbook.net
1 CREACIÓN DE LA BASE DE DATOS SINTÉTICA
En la sección 3.1 del Capítulo 2 (página 17) creamos la base de datos de objetos
incrustados utilizando el tipo Document. Para variar un poquito, utilizaremos ahora
BsonDocument (recuérdese la jerarquía de herencia de la Figura 11, página 12), que es un
tipo de datos quizá más incómodo de utilizar, pero más versátil y con menos propensión a
introducir errores.
Crearemos por tanto una base de datos a la que llamaremos
telefonosNoIncrustados con las tres colecciones mencionadas.
1.1 Creación de la colección de Tarifas
La creación de esta colección es muy sencilla, pues basta con insertar directamente
4 objetos fijos correspondientes a las 4 tarifas indicadas en el diargama de clases de la
Figura 156.
Como se ve en el código siguiente, tras conectar al servidor con el objeto
mongoCliente y recuperar la base de datos en db (recuérdese que la operación
getDatabase crea, si no existía, la base de datos cuyo nombre se pasa como parámetro), se
crea directamente la colección y se recupera en el objeto tarifas.
Puesto que en la colección almacenaremos los objetos en forma de instancias de
BsonDocument, debemos explicitar este tipo de datos en el segundo parámetro pasado a
db.getCollection, de modo que la sentencia queda así:
MongoCollection<BsonDocument> tarifas=
db.getCollection(“tarifas”, BsonDocument.class);
MongoDatabase db = mongoClient.getDatabase(“telefonosNoIncrustados”);
if (db.getCollection(“tarifas”)==null)
db.createCollection(“tarifas”);
MongoCollection<BsonDocument> tarifas=
db.getCollection(“tarifas”, BsonDocument.class);
www.fullengineeringbook.net
tPlana.append(“establecimiento”, new BsonDouble(0.15));
…
}
db.createCollection(“clientes”);
www.fullengineeringbook.net
String apellidosFileName=“/Users/Maco/…/apellidos.txt”;
Vector<String> nombres=read(nombresFileName);
Vector<String> apellidos=read(apellidosFileName);
int cont=0;
int posNombre=dado.nextInt(nombres.size());
int posApellido1=dado.nextInt(apellidos.size());
int posApellido2=dado.nextInt(apellidos.size());
int iDNI=5000000+dado.nextInt(5000000);
int iTelefono=600000000+i;
int tarifa=dado.nextInt(4);
String tipoTarifa=“TarifaPlana”;
if (tarifa==1) tipoTarifa=“Tarifa50Minutos”;
else if (tarifa==2) tipoTarifa=“TarifaTardes”;
String nombre=nombres.get(posNombre);
String apellido1=apellidos.get(posApellido1);
String apellido2=apellidos.get(posApellido2);
String dni=””+iDNI;
String telefono=””+iTelefono;
cliente.append(“fechaDeNacimiento”,
new BsonDateTime(fechaDeNacimiento.getTimeInMillis()));
clientes.insertOne(cliente);
cont++;
if (cont==NUMERO_DE_CLIENTES)
break;
}
…
www.fullengineeringbook.net
1.3 Creación de la colección de Llamadas
Aquí crearemos 50.000 llamadas que habrán sido realizadas por los 1.000 clientes
(y además entre ellos: es decir, no habrá llamadas a teléfonos de otras compañías)
insertados en la sección anterior.
Iteraremos 50.000 veces creando una llamada en cada iteración: el cliente que la
efectúe será uno de los 1.000 e irá dirigida a un número de otro cliente al azar. La duración
será aleatoria y la fecha será generada aleatoriamente y establecida a uno de los 300
primeros días de 2015, con una versión ligeramente modificada de la función fechaAlAzar.
En esta ocasión, dejaremos que Mongo asigne valor al campo _id, así que no
explicitaremos su valor al construir el BsonDocument.
… // Código de creación de Tarifas y Clientes
if (db.getCollection(“llamadas”)==null)
db.createCollection(“llamadas”);
MongoCollection<BsonDocument> llamadas=db.getCollection(“llamadas”, BsonDocument.class);
double auxi=dado.nextDouble();
int duracion;
if (auxi<0.4) {
duracion=dado.nextInt(200);
} else if (auxi<0.6) {
duracion=dado.nextInt(800);
} else if (auxi<0.8) {
duracion=dado.nextInt(1800);
} else {
duracion=dado.nextInt(3000);
}
GregorianCalendar fecha = fechaAlAzar(2015, 2015, 300);
llamada.append(“origen”, cliente.get(“telefono”));
llamada.append(“destino”, new BsonString(””+telefonoDestino));
llamadas.insertOne(llamada);
}
mongoClient.close();
www.fullengineeringbook.net
result.set(GregorianCalendar.DAY_OF_YEAR, dayOfYear);
return result;
www.fullengineeringbook.net
2 DISEÑO DE LA APLICACIÓN
Desarrollaremos una aplicación web que ofrecerá el siguiente conjunto de
funcionalidades:
Gestión de clientes (altas, bajas, modificaciones y consultas).
Consultar las llamadas de un cliente, tanto las totales como las de un
periodo.
Construir facturas de un determinado mes.
Mostrar las facturas de un cliente.
Mantendremos la separación de las capas de presentación y dominio respecto de la
base de datos a través de una capa de persistencia formada por un conjunto de clases
DAO, que serán las únicas con acceso al servidor de MongoDB. Es decir: en la medida de
lo posible, evitaremos utilizar objetos de los paquetes com.mongodb en el dominio y la
presentación. Así, cuando desde una página web del sistema se recupere, por ejemplo, un
cliente, la página obtendrá y mostrará un objeto de tipo Cliente y no un BsonDocument.
Esto supone una mayor carga para el servidor web (que debe hacer transformaciones de
BsonDocument a Cliente, a Llamada, etcétera), pero independiza la presentación y la
lógica del dominio respecto de la tecnología de base de datos utilizada.
Como se ha sugerido, la aplicación se estructura en tres capas: presentación,
dominio y almacenamiento. En la presentación hay un conjunto de páginas jsp (Figura
160) que permiten al usuario interactuar con los datos. La aplicación arranca por la página
index.jsp y, a partir de ahí, el usuario puede listar clientes, buscar clientes y lanzar el
proceso de facturación: desde listarClientes.jsp el usuario puede ver la ficha de un cliente
(recurso fichaCliente.jsp), consultar sus llamadas (verLlamadas.jsp) o ver los detalles de
su tarifa (detalleTarifa.jsp). Cuando se está en fichaCliente.jsp se puede ir también a
verLlamadas.jsp, así como actualizar los datos del cliente mediante actualizarCliente.jsp,
y eliminarlo con eliminarCliente.jsp. Estos dos últimos recursos reciben el cliente que se
desea actualizar, envían la petición al servidor web y le devuelven la respuesta al recurso
solicitante (fichaCliente.jsp) en formato JSON.
Desde index.jsp también se puede buscar a un cliente (buscarCliente.jsp, que lleva
a fichaCliente.jsp) y lanzar el proceso de facturación (facturacion.jsp) que, a su vez, se
ejecuta a través de facturar.jsp, que le devuelve los resultados en JSON. Cuando se han
construido las facturas, éstas pueden consultarse mediante consultarFacturas.jsp.
www.fullengineeringbook.net
Figura 160. Diseño de la capa de presentación
Respecto de las relaciones entre las tres capas, se muestran en la figura siguiente:
las flechas de dependencia en azul son las llamadas directas que las páginas jsp hacen
www.fullengineeringbook.net
diractamente a clases DAO. Nótese, no obstante, que la presentación no manipula en
ningún caso objetos de Mongo, sino solo objetos de nuestra aplicación. La dependencia
respecto de la tecnología de almacenamiento de datos reside solamente en las clases DAO.
www.fullengineeringbook.net
3 UN VISTAZO EN DETALLE A UNA CLASE DAO
Hagamos un seguimiento del listado de clientes, que se corresponde con una serie
de interacciones entre objetos del fragmento del diagrama de clases siguiente:
MongoBroker.get().getDB().getCollection(“clientes”, BsonDocument.class);
BsonDocument criterioOrdenacion=new BsonDocument(“apellido1”, new BsonInt32(1));
skip(inicio).limit(10).iterator();
…
Figura 164
www.fullengineeringbook.net
versiones de find, aggregate, mapReduce, etcétera.
www.fullengineeringbook.net
Figura 166. MongoIterable, FindIterable y MongoCursor
MongoBroker.get().getDB().getCollection(“clientes”, BsonDocument.class);
www.fullengineeringbook.net
BsonDocument criterioOrdenacion=new BsonDocument(“apellido1”, new BsonInt32(1));
int id;
Cliente cliente;
BsonDocument bso;
while (clientes.hasNext()) {
bso=clientes.next();
id=bso.get(“_id”).asInt32().getValue();
nombre=bso.get(“nombre”).asString().getValue();
apellido1=bso.get(“apellido1”).asString().getValue();
apellido2=bso.get(“apellido2”).asString().getValue();
dni=bso.get(“dni”).asString().getValue();
telefono=bso.get(“telefono”).asString().getValue();
fechaDeNacimiento=new GregorianCalendar();
fechaDeNacimiento.setTimeInMillis(
bso.get(“fechaDeNacimiento”).asDateTime().getValue());
cliente=new Cliente(id, nombre, apellido1, apellido2,
dni, telefono, fechaDeNacimiento);
result.add(cliente);
}
return result;
3.1 Delete
Por otro lado, a la fichaCliente.jsp se llega desde listarClientes.jsp. Desde aquí se
puede eliminar un cliente (y sus llamadas) y modificar sus datos. Estas operaciones se
ejecutan mediante dos operaciones que fichaCliente.jsp envía a eliminarCliente.jsp y a
actualizarCliente.jsp mediante sendas peticiones AJAX.
www.fullengineeringbook.net
Figura 168. Clases involucradas en la modificación y eliminación de clientes
getDB().getCollection(“clientes”, BsonDocument.class);
BsonDocument criterio=new BsonDocument(“_id”, new BsonInt32(idCliente));
DeleteResult resultCliente = colClientes.deleteOne(criterio);
MongoCollection<BsonDocument> colLlamadas=MongoBroker.get().
getDB().getCollection(“llamadas”, BsonDocument.class);
criterio=new BsonDocument(“idCliente”, new BsonInt32(idCliente));
DeleteResult resultLlamadas=colLlamadas.deleteMany(criterio);
return new long[] {
resultCliente.getDeletedCount(),
resultLlamadas.getDeletedCount()
};
El resultado devuelto por delete (es decir, el array de dos long) se devuelve al
recurso web que lo ejecutó: eliminarCliente.jsp recibe estos dos datos y los devuelve, en
formato JSON, al recurso web original (fichaCliente.jsp). Como se ve en la Figura 170,
eliminarCliente.jsp recibe en la request (procedente de fichaCliente.jsp) el idCliente, llama
a delete, recoge el resultado de delete en la variable resultados y coloca los dos valores
que llegan en el objeto jso, que es lo que se devuelve a fichaCliente.jsp.
<%@ page language=“java” contentType=“application/json; charset=UTF-8”
pageEncoding=“UTF-8”%>
www.fullengineeringbook.net
<%
String sIdCliente=request.getParameter(“idCliente”);
long[] resultados=DAOCliente.delete(Integer.parseInt(sIdCliente));
JSONObject jso=new JSONObject();
jso.put(“resultado”, “OK”);
jso.put(“clientesEliminados”, resultados[0]);
jso.put(“llamadasEliminadas”, resultados[1]);
%>
<%= jso.toJSONString() %>
Figura 171. Flujo de mensajes desde que el usuario decide eliminar un cliente hasta que efectivamente se elimina
3.2 Update
La actualización de datos es parecida. No obstante, y como se mostraba en la
Figura 168, la operación se ejecuta a través de la clase Cliente: actualizarCliente.jsp
recibe de fichaCliente todos los datos del cliente (entre ellos el _id, que no se puede
cambiar, como tampoco el número de teléfono), lo instancia y ejecuta sobre éste su
operación update. El update de Cliente delega su ejecución a la clase que le gestiona la
persistencia: es decir, a DAOCliente.
<%@ page language=“java” contentType=“application/json; charset=UTF-8”
pageEncoding=“UTF-8”%>
<%@ page import=“edu.uclm.esi.mongo.isotel.dominio.Cliente,
edu.uclm.esi.mongo.isotel.auxiliares.*,
java.util.*” %>
www.fullengineeringbook.net
<%@page import=“org.json.simple.JSONObject”%>
<%
String sIdCliente=request.getParameter(“idCliente”);
String dni=request.getParameter(“dni”);
String nombre=request.getParameter(“nombre”);
String apellido1=request.getParameter(“apellido1”);
String apellido2=request.getParameter(“apellido2”);
String sFechaDeNacimiento=request.getParameter(“fechaDeNacimiento”);
String idTarifa=request.getParameter(“tarifa”);
Integer idCliente=Integer.parseInt(sIdCliente);
Cliente cliente=new Cliente(idCliente);
cliente.setDni(dni);
cliente.setNombre(nombre);
cliente.setApellido1(apellido1);
cliente.setApellido2(apellido2);
GregorianCalendar fechaDeNacimiento=Fechas.getFecha(sFechaDeNacimiento);
cliente.setFechaDeNacimiento(fechaDeNacimiento);
cliente.setTarifa(idTarifa);
cliente.update();
DAOCliente.update(this);
www.fullengineeringbook.net
antiguo objeto por el nuevo. Aunque hemos dicho que el teléfono no puede cambiar, si no
añadiésemos a la variable criterioActualizacion, el cliente perdería su número de teléfono
al actualizar en la base de datos.
public static void update(Cliente cliente) {
MongoCollection<BsonDocument> colClientes=MongoBroker.get().getDB().
getCollection(“clientes”, BsonDocument.class);
BsonDocument criterioActualizacion=
criterioActualizacion.append(“idTarifa”,
new BsonString(cliente.getTarifa().getClass().getSimpleName()));
colClientes.replaceOne(criterio, criterioActualizacion);
}
En este proceso, fichaCliente.jsp recibe como parámetro el _id del cliente y le pide
a la clase DAO que le devuelva la instancia de Cliente que corresponde a ese _id. Las
clases que participan en este proceso son las de la Figura 176: al llegar a DAO cliente se
ejecuta una operación de materialización una instancia de tipo Cliente, que es lo que se
devuele a fichaCliente.jsp: la materialización consiste en crear una instancia a partir de la
información almacenada en la base de datos.
www.fullengineeringbook.net
Figura 176. Clases involucradas en la recuperación de un cliente
getCollection(“clientes”, BsonDocument.class);
BsonDocument criterio=new BsonDocument(“_id”, new BsonInt32(idCliente));
BsonDocument bso = colClientes.find(criterio).first();
int id;
String nombre, apellido1, apellido2, dni, telefono;
GregorianCalendar fechaDeNacimiento;
id=bso.get(“_id”).asInt32().getValue();
nombre=bso.get(“nombre”).asString().getValue();
apellido1=bso.get(“apellido1”).asString().getValue();
apellido2=bso.get(“apellido2”).asString().getValue();
dni=bso.get(“dni”).asString().getValue();
telefono=bso.get(“telefono”).asString().getValue();
fechaDeNacimiento=new GregorianCalendar();
fechaDeNacimiento.setTimeInMillis(
bso.get(“fechaDeNacimiento”).asDateTime().getValue());
cliente.setIdCliente(id);
cliente.setNombre(nombre);
cliente.setApellido1(apellido1); cliente.setApellido2(apellido2);
cliente.setDni(dni); cliente.setTelefono(telefono);
cliente.setFechaDeNacimiento(fechaDeNacimiento);
www.fullengineeringbook.net
Figura 177. Código para leer y construir (materializar) una instancia
www.fullengineeringbook.net
4 EJECUCIÓN DE OPERACIONES MAPREDUCE
Desde Java, y sobre una colección, podemos ejecutar de dos maneras comandos
MapReduce:
Mediante el comando mapReduce de MongoCollection.
Mediante un objeto MapReduceCommand del tipo DBCollection. Este
mecanismo está actualmente deprecated, por lo que no lo utilizaremos.
Como recordamos del Capítulo 4 (página 55), las reglas MapReduce constan de
una función JavaScript map, otra reduce y una colección de salida, out. Pueden llevar
también una función JavaScript finalize y, también, un scope, en el que se declaran
variables globales que vayan a ser utilizadas por las funciones.
En nuestro ejemplo, el proceso de facturación se ejecutará en dos pasos:
En el primero, una regla MapReduce extraerá las llamadas realizadas por
los clientes y las colocará en un mapa en el que tendremos, por cada
cliente, su número de llamadas y la duración total.
En el segundo paso, construiremos una factura por cada cliente, según su
tarifa y el consumo que haya realizado.
El proceso de facturación se lanzará mediante el botón Facturar de la sencilla
página web de la Figura 178.
Al hacer clic en el botón, se envía una petición AJAX a facturar.jsp que, a su vez,
llama al método facturarMR de DAOFactura, que es el que nos interesa en estos
momentos.
Las funciones map y reduce las tenemos almacenadas en una colección scripts en
la base de datos (recuérdese la sección 5 del Capítulo 4, página 62). El código de estas dos
funciones es el de la Figura 179:
La función llamadasDeUnMes (nuestro map) emite (es decir, añade a la
colección correspondiente a cada clave) dos objetos por cada llamada: uno
con las duraciones y el valor 0. El valor 0 se envía porque Mongo no
ejecuta la función reduce para colecciones del mapa en las que haya un
solo valor. Por tanto, si un cliente hubiese realizado solamente una llamada,
la función reduce no se le aplicaría y no todos los objetos de la colección
de facturación resultante quedarían con el mismo formato.
www.fullengineeringbook.net
La función facturacion (nuestra reduce) recibe cada uno de los pares clave,
valor emitidos por llamadasDeUnMes. Puesto que por cada llamada ha
recibido dos valores (la duración de la llamada y el 0), divide por dos para
calcular el número real de llamadas realizadas. La duración total se calcula
sumando las duraciones recibidas en la colección (es decir, en el campo
valor).
// Función map
emit(this.idCliente, this.duracion);
emit(this.idCliente, 0);
}
// Función reduce
var facturacion=function(clave, valor) {
var numeroDeLlamadas=valor.length/2;
var duracionTotal = 0;
var result = {
numeroDeLlamadas : numeroDeLlamadas,
duracionTotal : duracionTotal
};
return result;
}
Manteniendo ese segundo emit, sin embargo, los resultados tienen el aspecto que
deseamos:
www.fullengineeringbook.net
Figura 181. La colección resultante, con el segundo emit
El lector habrá observado que la función llamadasDeUnMes (que, como todas las
funciones map, no toma parámetros), compara el mes y el año de la fecha de cada llamada
con dos variables mes y year que, de alguna manera, se han debido pasar en la llamada a
mapReduce. En efecto, estos valores se pasan en el parámetro scope; desde la consola, el
comando es el siguiente:
El comando de la Figura 182 declara para las dos funciones utilizadas las variables
mes y year, con valores respectivos de 9 y 2015, para calcular la facturación del mes de
octubre de 2015. Aunque octubre es el 10º mes del año, pasamos un 9 porque, en
JavaScript, la función getMonth() del tipo Date devuelve un valor entre 0 (enero) y 11
(diciembre).
Con todas estas consideraciones estamos ya listos para invocar el comando
mapReduce desde la aplicación Java. Como decíamos, a esta regla se la invoca desde la
operación facturaMR de DAOFactura. Las primeras líneas de este método se muestran en
la Figura 183:
Inicialmente se recupera (en forma de JavaScript: nótense las llamadas a
asJavaScript) de la colección scripts el código de las dos funciones
llamadasDeUnMes y facturacion, que se asignan a dos variables map y
reduce.
A continuación, se recupera una referencia a la colección llamadas en la
variable colLlamadas.
En el bloque grande de instrucciones se prepara la llamada a la regla
mapReduce: sobre colLlamadas llamamos a mapReduce pasando el código
de ambas funciones como parámetros. La ejecución de esta operación aún
no ejecuta la regla, pero devuelve un objeto de tipo MapReduceIterable,
que nos sirve para especificar el contexto de ejecución: el nombre de la
colección de salida (facturacion2015_10, si pasamos 2015 y 10 como
valores de los parámetros mes y year), los valores de las variables mes y
year para la ejecución de la regla (es decir, el valor del scope, según
presentamos unas líneas arriba y mostramos en la Figura 182) y la acción
www.fullengineeringbook.net
que se debe ejecutar. La acción puede ser MERGE, REPLACE o REDUCE.
Las comentamos un poquito más abajo.
Una vez que toda la regla ha sido preparada, para que ésta se ejecute
efectivamente invocamos a la operación first().
Respecto del parámetro pasado a la operación action, MERGE, REPLACE y
REDUCE actúan de la siguiente manera:
Si la colección resultante ya existía, MERGE le añade los nuevos
resultados, respetando los preexistentes. En caso de conflicto en algún _id,
el objeto antiguo es eliminado.
REPLACE reemplaza toda la colección con los nuevos resultados: es decir,
los antiduos desaparecen.
Si la colección resultante ya existía, REDUCE añade también los nuevos
resultados, respetando los preexistentes. No obstante, en caso de conflicto
en algún _id, la función reduce se aplica tanto al nuevo como al antiguo,
sobreescribiéndose el objeto con el resultado nuevo.
public static Vector<Factura> facturarMR(int mes, int year) throws Exception {
MongoCollection<BsonDocument> colScripts=MongoBroker.get().getDB().
getCollection(“scripts”, BsonDocument.class);
BsonDocument criterio=new BsonDocument(“_id”, new BsonString(“llamadasDeUnMes”));
String map=colScripts.find(criterio).first().get(“codigo”).asJavaScript().getCode();
criterio=new BsonDocument(“_id”, new BsonString(“facturacion”));
String reduce=colScripts.find(criterio).first().
get(“codigo”).asJavaScript().getCode();
MongoCollection<BsonDocument> colLlamadas=MongoBroker.get().getDB().
getCollection(“llamadas”, BsonDocument.class);
reduccion=reduccion.action(MapReduceAction.REPLACE);
reduccion.first();
MongoCollection<BsonDocument> colFacturacion=MongoBroker.get().getDB().
getCollection(“facturacion”+year+“_”+mes, BsonDocument.class);
Tarifa tarifa;
Vector<Factura> result=new Vector<>();
while (facturas.hasNext()) {
bso=facturas.next();
idCliente=(int) bso.get(“_id”).asDouble().getValue();
numeroDeLlamadas=(int) bso.get(“value”).asDocument().
get(“numeroDeLlamadas”).asDouble().getValue();
duracionTotal=(int) bso.get(“value”).asDocument().
get(“duracionTotal”).asDouble().getValue();
tarifa=DAOTarifa.loadTarifa(idCliente);
factura.insert();
result.add(factura);
}
return result;
www.fullengineeringbook.net
De la inserción de las facturas se encarga el método insert de Factura que, a su
vez, llama al insert de DAOFactura. El código de esta clase es muy sencillo (Figura 185),
pues construye un BsonDocument a partir de la información contenida en la instancia que
recibe como parámetro.
Así como llamábamos materialización al hecho de construir una instancia de
nuestro dominio a partir de un BsonDocument, se llama desmaterialización a la
construcción de un BsonDocument a partir de un objeto de nuestro dominio. Son
desmaterializaciones de objetos lo que hacemos en la Figura 185.
public static void insert(Factura factura) {
MongoCollection<BsonDocument> colFacturas=MongoBroker.get().getDB().
getCollection(“facturas”, BsonDocument.class);
bso.append(“establecimientosDeLlamada”,
new BsonDouble(factura.getCostesEstablecimiento()));
bso.append(“importePorConsumo”, new BsonDouble(factura.getCostesPorConsumo()));
bso.append(“tipoDeTarifa”,
new BsonString(factura.getTarifa().getClass().getSimpleName()));
colFacturas.insertOne(bso);
www.fullengineeringbook.net
5 EL MONGOBROKER
El MongoBroker es una clase singleton (es decir, sólo existe una única instancia de
ella) que representa el punto común de acceso a la base de datos: siempre que alguien
quiera acceder al servidor de Mongo, recuperará la conexión a través de esta clase. Su
implementación puede ser complicada pero, para nuestro caso, es tan sencilla como en la
Figura 186:
Al ser un singleton, dispone de un campo estático y privado (yo) del mismo
tipo que la propia clase.
Al ser un singleton, el constructor tiene visibilidad reducida: private, en
este caso.
La única instancia existente se recupera a través del método estático y
público get(): si el objeto yo es nulo (es decir, no existe todavía porque ésta
es la primera vez que se llama a este método), entonces se crea llamando al
constructor privado y se devuelve; si sí existía, se devuelve directamente.
El método getDB es el único método de negocio de la clase: cuando un
objeto cualquiera necesita acceder al servidor, recupera la referencia a la
MongoDatabase mediante una sentencia del tipo MongoDatabase
database=MongoBroker.get().getDB().
package edu.uclm.esi.mongo.isotel.persistencia;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoDatabase;
public class MongoBroker {
private MongoBroker() {
MongoClient mongoClient=new MongoClient(“localhost”, 27017);
this.db = mongoClient.getDatabase(“telefonosNoIncrustados”);
}
public static MongoBroker get() {
if (yo==null)
yo=new MongoBroker();
return yo;
www.fullengineeringbook.net
6 UTILIZACIÓN DE RÉPLICAS
En principio, la utilización de una base de datos replicada es transparente para el
cliente. Sin embargo, éste debe ser informado de la ubicación de los servidores que
conforman el conjunto de réplica.
En esta sección crearemos la base de datos telefonosNoIncrustados, con sus
diversas colecciones de clientes, llamadas y tarifas, en un conjunto de réplica creado en
los puertos 27017, 37017 y 47017 de la máquina 192.168.1.130.
Prácticamente todo el código de creación de la base de datos (Figura 157, Figura
158 y Figura 159) sigue siendo válido: el único cambio que tenemos que hacer se
encuentra en las primeras líneas: en lugar de conectar al servidor usando una sola
dirección( MongoClient mongoClient=new MongoClient(“localhost”, 27017); ), usamos varias (Figura 187). El resto
es exactamente igual.
public static void main(String[] args) throws IOException {
final int NUMERO_DE_CLIENTES = 1000;
import java.util.List;
import java.util.Vector;
import com.mongodb.MongoClient;
import com.mongodb.ServerAddress;
import com.mongodb.client.MongoDatabase;
public class MongoBroker {
www.fullengineeringbook.net
ServerAddress server2=new ServerAddress(“192.168.1.130”, 37017);
if (yo==null)
yo=new MongoBroker();
return yo;
}
public MongoDatabase getDB() {
return db;
}
}
www.fullengineeringbook.net
www.fullengineeringbook.net
Capítulo 9. Índice alfabético
www.fullengineeringbook.net
búsqueda de texto, 52
cadenas, 51
Caída de un nodo, 80
capped collection, 76
caracteres de escape, 5
changeUserPassword, 74
clases DAO, 91
clave, valor, 56
colecciones del sistema, 68
Collection, 24
comparación, 50
cond, 30
condicionales, 53
conf, 84, 85
configuración de escritura, 85
configuración de la réplica, 83
configuración de lectura, 85
conjuntos, 48
consistencia, 75
consistencia estricta, 75
contraseña, 74
count, 26, 27
Cursores, 24, 96
DAO, 91, 92, 93, 94, 97, 100, 101
dateToString, 37
db.auth, 70
de tuberías y filtros, 32
Delete, 97
diseño por capas, 91
disponibilidad, 75
distinct, 27
distinto, 23
www.fullengineeringbook.net
Document, 87
org.bson.Document, 8
dominio, 91
dominio (capa arquitectónica), 91
dropIndex, 65
elecciones, 80, 81
emit, 57, 103
ensureIndex, 64
escritura (configuración), 85
Fechas, 13, 29, 36, 37, 40, 53, 60, 89, 100, 104
filtros, 45
finalize, 55, 59, 102
find , 20, 25, 95, 101
FindIterable, 95
first , 43
getCollection, 88
getDatabase, 88
getIndexes, 65
grantPrivilegesToRole, 74
group, 27, 28, 29, 31, 33, 36, 38, 39, 42, 43, 54
hasNext, 26, 96, 97, 105
heartbeats, 76
host, 13, 78, 81, 82, 83, 84, 85
initial, 28
initiate, 78, 82, 83
java.util.Calendar, 13
JavaScript, 42, 104
JSON, 5
JSONObject, 7
JSONParser, 7
json-simple, 6
jsp, 91
www.fullengineeringbook.net
key, 9, 10, 28
last , 43
lectura (configuración), 85
length, 26, 28, 32, 36, 42, 71, 72, 103
limit, 26, 33, 41, 55, 94, 95, 96, 97
literales, 53
localhost, 18
majority, 86
map, 52, 55, 56, 57, 58, 60, 61, 102, 103, 104, 105
mapa, 56
MapReduce, 1, 27, 56, 57, 60, 102
MapReduceCommand, 102
match , 32, 33, 35, 42, 45, 46, 49
Materialización, 100
max , 43
Máximo, 43
Media, 43
members, 78, 84
MERGE, 104
min , 43
Mínimo, 43
mongo, 67
MongoBroker, 95, 97, 98, 100, 101, 105, 106, 107, 108
MongoCollection , 18, 88, 89, 90, 95, 96, 97, 98, 100, 101, 102, 105, 106
MongoCursor, 96
mongod, 67, 78, 81, 83
nearest, 77, 85
next, 26, 28, 96, 97, 105
nodo primario, 75, 76, 77, 79, 80, 81, 83, 85
nodo secundario, 75
ObjectId, 13
Operadores aritméticos, 50
www.fullengineeringbook.net
Operadores booleanos, 45
Operadores de cadena, 51
Operadores de comparación, 50
Operadores de totalización, 43
operadores lógicos, 24
oplog, 75, 76, 77
OR, 21, 31
org.bson.Document, 17
out, 55
pares (clave, valor), 56
password, 74
persistencia (capa arquitectónica), 91
ping, 76, 82
pipeline, 45, 50
Pipelines, 32
port, 78, 79, 82
presentación, 91, 92, 93
presentación (capa arqutiectónica), 91
primario, 75
primary, 77, 85
primaryPreferred, 77, 85
prioridad, 76
privileges, 72, 73
project , 32
prompt, 80
Proyecciones, 20
puerto, 79
push , 43
query, 58
read preferences, 77
reconfig, 83, 84
reduce, 28, 29, 55, 56, 57, 59, 60, 61, 102, 103, 104, 105
www.fullengineeringbook.net
REDUCE (action), 104
redundancia, 75
remove, 83
replace, 100, 104
replaceOne, 100
réplica oculta, 76
réplica postergada, 76
replica set, 82
réplicas
uso en la aplicación, 107
rol, 67, 68, 69, 70, 71, 72, 73, 74, 76
rollback, 77
root, 69
rs, 82
rs.add, 81
rs.conf, 84, 85
rs.initiate, 78
rs.reconfig, 83
rs.remove, 83
rs.status, 82
scripts, 62
secondary, 77, 85
secondaryPreferred, 77, 85
secundario, 75, 76, 77, 79, 80, 85
SELECT, 100
separación de las capas, 91
servidor
arranque, 67
servidor secundario, 75
singleton, 106
size, 18, 26, 45, 46, 52, 89
skip , 26, 33, 41, 90, 94, 95, 96, 97
www.fullengineeringbook.net
sort, 25, 26, 28, 33, 39, 55, 94, 95, 96, 97
status, 82
sum , 43
Suma, 43
súperusuario, 68
this, 7, 9, 12, 13, 57, 100, 103, 107, 108
timeout, 85
tipos de datos, 25
totalización, 43, 54
unique, 64
unwind , 33, 34, 35, 38
Update, 99
usuarios
creación, 69
modificación, 70, 74
value (en mapReduce), 105
verbose, 61
VirtualBox, 81
votación, 75, 76, 78, 80
votaciones, 76
w, 85
web, 87
write concern, 77
writeConcern, 85
wtimeout, 85
www.fullengineeringbook.net