MongoDB是一个文档数据库,由C++语言编写,旨在为WEB应用提供可扩展的高性能数据存储解决方案。它支持的数据结构非常松散,数据格式是BSON,一种类似ISON的二进制形式的存储格式,简称BinaryJSON。MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

MongoDB在数据库总排名第5,仅次于Oracle、MySQL等RDBMS,在NoSQL数据库排名首位。从诞生以来,其项目应用广度、社区活跃指数持续上升。

MongoDB概念与关系型数据库(RDBMS)非常类似。在SQL概念中,一个数据库包含多个不同名称的集合;一个集合可以存放多个不同的文档;每个文档相当于数据表中的一行,由多个不同的字段组成;字段等同于列;索引与SQL概念一致;每个文档中都拥有一个唯一的id字段等同于SQL中的主键。

MongoDB是一种非关系型数据库,它具有半结构化、弱关系等特点。与传统关系型数据库相比,MongoDB在亿级以上数据量的轻松支持方面具有优势,而在灵活表结构的支持方面则需要分库分表等措施。MongoDB基于灵活的SON文栏模型,非常适合敏捷式的快速开发。与此同时,其与生俱来的高可用、高水平扩展能力使得它在处理海量、高并发的数据应用时颇具优势。

以下是对您提到的内容进行重构后的结果:

MongoDB是一种非关系型数据库,其文档所拥有的字段并不需要是相同的,而且也不需要对所用的字段进行声明。因此,MongoDB具有很明显的半结构化特点。除了松散的表结构,文档还可以支持多级的嵌套、数组等灵活的数据类型,非常契合面向对象的编程模型。然而,MongoDB没有外键约束,也没有非常强大的表连接能力。类似的功能需要使用聚合管道技术来弥补。

从目前阿里云 MongoDB 云数据库上的用户看,MongoDB的应用已经渗透到各个领域:游戏场景、金融场景等。例如,游戏场景中使用 MongoDB 存储游戏用户信息时,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、更新;金融场景中使用 MongoDB 存储交易记录时,可以通过聚合计算等方式实现复杂的数据分析。

总之,MongoDB作为一种非关系型数据库具有自己独特的优势和特点,并且已经在各行各业得到了广泛应用。

MongoDB在不同场景下的应用如下:

1. 物流场景:使用MongoDB存储订单信息,将订单状态内嵌到数组中进行实时更新,一次查询即可获取所有变更信息。

2. 社交场景:利用MongoDB存储用户信息和朋友圈动态,通过地理位置索引实现附近的人、地点等功能。

3. 物联网场景:将所有接入的智能设备信息以及设备汇报的日志信息存储于MongoDB,并对这些数据进行多维度分析。

4. 视频直播场景:使用MongoDB存储用户信息和礼物信息等。

5. 大数据应用:将MongoDB作为大数据的云存储系统,随时进行数据提取和分析,掌握行业动态。

国内外知名互联网公司均在使用MongoDB。

考虑是否选择MongoDB?没有某个业务场景必须要使用MongoDB才能解决问题,但通常情况下使用MongoDB可以以更低的成本解决问题。如果你不确定当前业务是否适合使用MongoDB,可以通过做几道选择题来辅助决策。

以下是一些应用特征与MongoDB的匹配程度问题(Yes/No):

- 应用不需要复杂/长事务及join支持(必须Yes)

- 新应用且需求可能会变化,数据模型无法确定,想要快速迭代开发(?)

- 应用需要2000-3000以上的读写QPS(更高也可以?)

- 应用需要TB甚至PB级别的数据存储(?)

- 应用发展迅速,需要能够快速水平扩展(?)

- 应用要求存储的数据不丢失(?)

- 应用需要99.999%的高可用性(?)

- 应用需要大量的地理位置查询、文本查询(?)

只要有一项需求满足,就可以考虑使用MongoDB,匹配越多,选择MongoDB越合适。

接下来是如何快速开始使用MongoDB的步骤:

2.1 Linux安装MongoDB:

首先进行环境准备,选择Linux系统(如centos7),然后下载并安装MongoDB社区版。具体操作如下:

```markdown

# 下载MongoDB Community Server

wget https://fastdl.mongodb.org/osx/mongodb-macos-x86_64-4.4.28.tgz

tar -zxvf mongodb-macos-x86_64-4.4.28.tgz

vim ~/.zshrc # 内容BEGIN export PATH=/Users/zhinian/Desktop/tools/mongodb/bin:$PATH # 内容END source ~/.zshrc

```

/mongod --help

Options:

-h, --help Show this usage information

--version Show version information

-f, --config arg Configuration file specifying additional options

--configExpand arg Process expansion directives in config file (none, exec, rest)

--port arg Specify port number - 27017 by default

--ipv6 Enable IPv6 support (disabled by default)

--listenBacklog arg (=128) Set socket listen backlog size

--maxConns arg Max number of simultaneous connections

--pidfilepath arg Full path to pidfile (if not set, no pidfile is created)

--timeZoneInfo arg Full path to time zone info directory, e.g. /usr/share/zoneinfo

--nounixsocket Disable listening on unix sockets

--unixSocketPrefix arg Alternative directory for UNIX domain sockets (defaults to /tmp)

--filePermissions arg Permissions to set on UNIX domain socket file - 0700 by default

--fork Fork server process

-v, --verbose [=arg(=v)] Be more verbose (include multiple times for more verbosity e.g. -vvvv)

--quiet Quieter output

--logpath arg Log file to send write to instead of stdout - has to be a file, not directory

--syslog Log to system's syslog facility instead of file or stdout

--syslogFacility arg syslog facility used for mongodb syslog message

--logappend Append to logpath instead of over-writing

--logRotate arg Set the log rotation behavior (rename|reopen)

--timeStampFormat arg Desired format for timestamps in log messages. One of iso8601-utc or iso8601-local

--setParameter arg Set a configurable parameter

--bind_ip arg Comma separated list of ip addresses to listen on - localhost by default

--bind_ip_all Bind to all ip addresses

--noauth Run without security

--transitionToAuth For rolling access control upgrade. Attempt to authenticate over outgoing connections and proceed regardless of success. Accept incoming connections with or without authentication.

--slowms arg (=100) Value of slow for profile and console log

--slowOpSampleRate arg (=1) Fraction of slow ops to include in the profile and console log

--profileFilter arg Query predicate to control which operations are logged and profiled

--auth Run with security

--clusterIpSourceWhitelist arg Network CIDR specification of permitted origin for `__system` access

--profile arg 0=off, 1=slow, 2=all

--cpu Periodically show cpu and iowait utilization

--sysinfo Print some diagnostic system information

--noscripting Disable scripting engine

--notablescan Do not allow table scans

--keyFile arg Private key for cluster authentication

--clusterAuthMode arg Authentication mode used for cluster authentication. Alternatives are (keyFile|sendKeyFile|sendX509|x509) Replication options:

--oplogSize arg Size to use (in MB) for replication op log. default is 5% of disk space (i.e. large is good) Replica set options:

--replSet arg <setname>[/<optionalseedhostlist>]

--enableMajorityReadConcern [=arg(=1)] (=1) Enables majority readConcern Sharding options:

--configsvr Declare this is a config db of a cluster; default port 27019; default dir /data/configdb

--shardsvr Declare this is a shard db of a cluster; default port 27018 Storage options:

以下是您提供的内容,我已将其重构为一个段落结构:

启动 MongoDB 服务器

1. 创建 dbpath 和 logpath

```bash

mkdir -p ~/Desktop/tools/mongodb-macos-x86_64-4.4.28/data

mkdir -p ~/Desktop/tools/mongodb-macos-x86_64-4.4.28/log

```

2. 进入 mongodb 目录,启动 mongodb 服务

```bash

cd ~/Desktop/tools/mongodb-macos-x86_64-4.4.28

mongod --logpath="/Users/zhinian/Desktop/tools/mongodb/log/mongodb.log" --bind_ip="0.0.0.0" --dbpath="/Users/zhinian/Desktop/tools/mongodb/data" --port=27017 --fork

```

3. 输出信息说明服务器准备就绪并等待连接。

```arduino

about to fork child process, waiting until server is ready for connections.

forked process: 50386

child process started successfully, parent exiting.

```

```markdown欢迎使用MongoDB shell,版本v4.4.28。连接地址为:mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb。这是一个隐式会话,会话ID为:UUID(d68a44f7-6a0f-4b7e-9984-fe6954a37527)。

MongoDB服务器版本为4.4.28。如需交互式帮助,请输入“help”。要查看更全面的文档,请访问https://docs.mongodb.com/。有任何疑问,请尝试访问MongoDB开发者社区论坛:https://community.mongodb.com。

启动时,服务器生成了一些启动警告:

1. 数据库未启用访问控制。数据和配置的读取和写入访问不受限制。

2. 软限制过低。

3. 建议的最小值为64000,当前值为2560。

要查看数据库列表,请输入以下命令:

```

show dbs

```

关于–dbpath参数,它用于指定数据文件存放目录。

请根据提供的内容完成内容重构,并保持段落结构:

```

-logpath:指定日志文件,注意是指定文件不是目录

-logappend:使用追加的方式记录日志

-port:指定端口,默认为27017

-bind_ip:默认只监听localhost网卡

-fork:后台启动

-auth:开启认证模式

```

利用配置文件启动服务:

```bash

vim ~/Desktop/tools/mongodb/conf/mongo.conf

systemLog: destination: file path: /Users/zhinian/Desktop/tools/mongodb/log/mongod.10g # log path logAppend: true storage: dbPath: /Users/zhinian/Desktop/tools/mongodb/data # data directory engine: wiredTiger #存储引擎 journal: #是否启用journa1日志 enabled: true net: bindIp: 0.0.0.0 port: 27017 # port processManagement: fork: true # fork and run in background shutdown: true # Add this line to shutdown the database

vim bins/mongodb # 内容BEGIN mongod -f /Users/zhinian/Desktop/tools/mongodb/conf/mongo.conf # 内容END chmod +x bins/mongodb

# 仅需要输入mongodb即可启动

zhinian@192 ~ % mongodb about to fork child process, waiting until server is ready for connections. forked process: 51981 child process started successfully, parent exiting

```

注意:一定要yaml格式。

以下是重构后的内容:

MongoDB服务启动和关闭方法:

1. 启动MongoDB服务

```

mongod -f/mongodb/conf/mongo.conf

```

2. 关闭MongoDB服务

方式一:新版本已没有shutdown命令,可以使用以下命令:

```

mongod --port=27017 --dbpath=/Users/zhinian/Desktop/tools/mongodb/data --shutdown

```

如果出现错误提示,可以尝试使用以下命令:

```

mongod --help

```

方式二:进入mongo shell,执行以下命令:

```

use admin db.shutdownServer()

```

以下是重构后的代码:

```javascript

// 执行一个JavaScript脚本文件

exit();

// 退出当前shell

quit();

// 查看mongodb支持哪些命令

help();

// 查询当前数据库支持的方法

db.help();

// 显示集合的帮助信息

db.集合名.help();

// 查看数据库版本

db.version();

// 数据库操作

// 查看所有库

show dbs;

// 切换数据库,如果不存在创建数据库

use test;

// 删除当前数据库

db.dropDatabase();

// 集合操作

// 查看集合

show collections;

// 创建集合

db.createCollection("emp");

// 删除集合

db.emp.drop();

// 创建集合语法

db.createCollection(name, options);

// options参数

// 字段类型描述

// capped布尔(可选)如果为true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。

// size数值(可选)为固定集合指定一个最大值(以字节计)。如果capped 为true,也需要指定该字段。

// max数值(可选)指定固定集合中包含文档的最大数量。注: 当集合不存在时,向集合中插入文档也会创建集合

// 2.3 安全认证

// 创建管理员账号

use admin; // 设置管理员用户名/密码需要切换到admin库

db.createUser({user: "fox", pwd: "fox", roles: ["root"]}); // 创建管理员

show users; // 查看所有用户信息

db.dropUser("fox"); // 删除用户

// 常用权限

const permissions = [

"read", // 允许用户读取指定数据库

"readWrite", // 允许用户读写指定数据库

"dbAdmin", // 允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile

"dbOwner", // 允许用户在指定数据库中执行任意操作,增、删、改、查等

"userAdmin", // 允许用户向system.users集合写入,可以在指定数据库里创建、删除和管理用户

"clusterAdmin" // 只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限

];

``

只在admin数据库中可用,赋予用户所有数据库的读写权限:

```javascript

readWriteAnyDatabase

```

只在admin数据库中可用,赋予用户所有数据库的读写权限:

```javascript

userAdminAnyDatabase

```

只在admin数据库中可用,赋予用户所有数据库的userAdmin权限:

```javascript

dbAdminAnyDatabase

```

只在admin数据库中可用。超级账号,超级权限:

```javascript

root

```

用户认证,返回1表示认证成功:

```javascript

创建应用数据库用户:

```javascript

use appdb db.createUser({user: "appdb", pwd: "fox", roles: ["dbOwner"]})

```

默认情况下,MongoDB不会启用鉴权,以鉴权模式启动MongoDB:

```bash

mongod -f /Users/zhinian/Desktop/tools/mongodb/conf/mongo.conf --auth

```

启用鉴权之后,连接MongoDB的相关操作都需要提供身份认证:

```bash

mongo 192.168.1.3:27017 -uappdb -pfox --authenticationDatabase=appdb

```

3. MongoDB文档操作:

3.1 插入文档:

3.2 版本之后新增了 `db.collection.insertOne()` 和 `db.collection.insertMany()`。新增单个文档:

```javascript

insertOne: 支持writeConcern

db.collection.insertOne(, {writeConcern: })

```

writeConcern 决定一个写操作落到多少个节点上才算成功。writeConcern 的取值包括:

0:发起写操作,不关心是否成功;

1-集群最大数据节点数:写操作需要被复制到指定节点数才算成功;

majority:写操作需要被复制到大多数节点上才算成功。

insert:若插入的数据主键已经存在,则会抛 DuplicatekeyException 异常,提示主键重复,不保存当前数据。

save:如果_jd 主键存在则更新数据,如果不存在就插入数据。

向指定集合中插入多条文档数据的方法是使用`insertMany`函数。以下是一个示例:

```javascript

db.collection.insertMany([

{ <document1>, <document2>, ... },

{ <document3>, <document4>, ... },

...

], {

writeConcern: <document>,

ordered: <boolean>

});

```

其中,`writeConcern`表示写入策略,默认为1,即要求确认写操作,0是不要求。`ordered`表示是否按顺序写入,默认为true,按顺序写入。此外,还可以使用`insert`和`save`方法实现批量插入。

测试:批量插入50条随机数据

首先,创建一个名为`book.js`的脚本文件,然后在其中编写以下代码:

```javascript

let tags = ["nosql", "mongodb", "document", "developer", "popular"];

let types = ["technology", "sociality", "travel", "novel", "literature"];

let books = [];

for (let i = 0; i < 50; i++) {

let typeId = Math.floor(Math.random() * types.length);

let tagId = Math.floor(Math.random() * tags.length);

let favCount = Math.floor(Math.random() * 100);

let book = { title: "book-" + i, type: types[typeId], tag: tags[tagId], favCount: favCount, author: "xxx" + i };

books.push(book);

}

db.books.insertMany(books);

```

接下来,进入mongo shell,执行以下命令:

```javascript

load("book.js");

```

以下是重构后的内容:

```markdown

## 查询操作符

### find()

- 参数:

- query(可选):使用查询操作符指定查询条件。

- projection(可选):使用投影操作符指定返回的键。查询时返回文档中所有键值,只需省略该参数即可(默认省略)。投影时,id为1的时候,其他字段必须是1;id是0的时候,其他字段可以是0;如果没有_id字段约束,多个其他字段必须同为0或同为1。

- 返回值:查询结果。

### findOne()

- 参数:

- query(可选):使用查询操作符指定查询条件。

- projection(可选):使用投影操作符指定返回的键。

- 返回值:查询集合中的第一个文档。

### 条件查询

- 示例:

- 查询带有nosql标签的book文档:`db.books.find({tag: "nosql"})`

- 按照id查询单个book文档:`db.books.find({_id: ObjectId("65b4e219ebed3fc5e9d74276")})`

- 查询分类为travel、收藏数超过60的book文档:`db.books.find({type: "travel", favCount: {$gt: 60}})`

### 查询条件对照表

| SQL | MQL |

| --- | --- |

| a = 1 | ${a: 1} |

| a <> 1 | {a: {$ne: 1}} |

| a > 1 | {a: {$gt: 1}} |

| a >= 1 | {a: {$ge: 1}} |

| a < 1 | {a: {$lt: 1}} |

| a <= 1 | {a: {$lte: 1}} |

### 查询逻辑对照表

| SQL | MQL |

| --- | --- |

| a = 1 AND b = 1 | ${a: 1, b: 1}或{$and: [{a: 1}, {b: 1}]} |

| a = 1 OR b = 1 | {$or: [{a: 1}, {b: 1}]} |

| a IS NULL | {a: {$exists: false}} |

| a IN (1, 2, 3) | {a: {in: [1, 2, 3]}} |

### 查询逻辑运算符

- $lt:存在并小于。

- $lte:存在并小于等于。

- $gt:存在并大于。

- $gte:存在并大于等于。

- $ne:不存在或存在但不等于。

- $in:存在并在指定数组中。

- $nin:不存在或不在指定数组中。

在MongoDB中,我们可以使用不同的操作符来满足各种查询和更新需求。以下是一些常用的操作符及其格式和描述:

1. 排序(sort):

- `$or`:匹配两个或多个条件中的一个。

- `$and`:匹配全部条件。

在MongoDB中使用`sort()`方法对数据进行排序。例如,按收藏数(favCount)降序返回:

```javascript

db.books.find({type: "travel"}).sort({favCount: -1})

```

2. 分页查询:

- `skip`用于指定跳过记录数,`limit`则用于限定返回结果数量。可以在执行`find`命令的同时指定`skip`、`limit`参数,以此实现分页的功能。例如,查询第3页的book文档:

```javascript

db.books.find().skip(8).limit(4)

```

3. 正则表达式匹配查询:

MongoDB使用`$regex`操作符来设置匹配字符串的正则表达式。例如,查找type包含"so"字符串的book:

```javascript

db.books.find({type: {$regex: "so"}})

```

或者:

```javascript

db.books.find({type:/so/})

```

4. 更新文档:

我们可以使用`update`命令对指定的数据进行更新。命令的格式如下:

```javascript

db.collection.update(query, update, options)

```

其中:

- `query`描述更新的查询条件;

- `update`描述更新的动作及新的内容;

- `options`描述更新的选项,如是否插入新的记录、是否按条件查询出的多条记录全部更新等。

追加多个值到一个数组字段内:

```javascript

{$pushAll: {field: value_array}}

```

从数组中删除指定的元素:

```javascript

{$pull: {field: _value}}

```

添加元素到数组中,具有排重功能:

```javascript

{$addToSet: {field: value}}

```

删除数组的第一个或最后一个元素:

```javascript

{$pop: {field: 1}}

```

更新单个文档,将某个book文档的favCount字段自增:

```javascript

db.books.update({_id: ObjectId("65b4e219ebed3fc5e9d74287")}, {$inc: {favCount: 1}})

```

更新多个文档,将分类为“novel”的文档的增加发布时间(publishedDate):

```javascript

db.books.update({type: "nosql"}, {$set: {publishedDate: new Date()}}, {"multi": true})

```

其中,multi选项表示如果需要更新多个文档,则使用该选项。

使用upsert命令,实现replace语义。当目标文档不存在时,执行插入命令。例如,将标题为"my book"的书籍的标签设置为["nosql", "mongodb"],类型为"none",作者为"fox":

```javascript

db.books.update({title: "my book"}, {$set: {tags: ["nosql", "mongodb"], type: "none", author: "fox"}}, {upsert: true})

```

如果没有文档被匹配及更新,nUpserted=1提示执行了upsert操作。

MongoDB中的更新操作主要通过update命令实现,其更新描述(update)通常由操作符描述。如果更新描述中不包含任何操作符,那么MongoDB会实现文档的replace语义。例如:

```javascript

db.books.update({title: "my book"}, {justTitle: "my first book"})

```

findAndModify命令则兼容了查询和修改指定文档的功能。例如,将某个book文档的收藏数(favCount)加1:

```javascript

db.books.findAndModify({query: {_id: ObjectId("65b4e219ebed3fc5e9d74264")}}, {$inc: {favCount: 1}})

```

该操作会返回符合查询条件的文档数据,并完成对文档的修改。默认情况下,findAndModify会返回修改前的“旧”数据。如果希望返回修改后的数据,则可以指定new选项:

```javascript

db.books.findAndModify({query: {_id: ObjectId("65b4e219ebed3fc5e9d74264")}}, {$inc: {favCount: 1}}, new: true)

```

与findAndModify语义相近的命令有findOneAndUpdate、findOneAndReplace。例如,使用remove删除文档:

```javascript

db.user.remove({age: 28}) // 删除 age 等于 28 的记录

db.user.remove({age: {$lt: 25}}) // 删除 age 小于 25 的记录

db.user.remove({}) // 删除所有记录

db.user.remove() // 报错,因为没有提供查询条件

```

remove命令会删除匹配条件的全部文档,如果希望明确限定只删除一个文档,则需要指定justOne参数。命令格式如下:

以下是重构后的内容:

### MongoDB删除文档

#### 1. remove方法

`db.collection.remove(query, justone)`

例如:删除满足type:novel条件的首条记录(加true删首条)

```javascript

db.books.remove({type: "novel"}, true)

```

#### 2. delete方法

官方推荐使用 `deleteOne()` 和 `deleteMany()` 方法删除文档,语法格式如下:

- `db.books.deleteMany({})`:删除集合下全部文档

- `db.books.deleteMany({type: "novel"})`:删除 type 等于 novel 的全部文档

- `db.books.deleteOne({type: "novel"})`:删除 type 等于 novel 的一个文档

注意:`remove`、`deleteMany`等命令需要对查询范围内的文档逐个删除,如果希望删除整个集合,则使用 `drop` 命令会更加高效。

返回被删除文档:

- `remove`、`deleteOne`等命令在删除又档后只会返回确认性的信息,如果希望获得被删除的文档,则可以使用 `findOneAndDelete` 命令。

- `db.books.findOneAndDelete({type: "novel"})`:除了在结果中返回删除文档,`findOneAndDelete` 命令还允许定义 “删除的顺序”,即按照指定顺序删除找到的第一个文档。

- `db.books.findoneAndDelete({type: "nove1"}, {sort: {favcount:1}})`:利用这个特性,`findOneAndDelete` 可以实现队列的先进先出。

```xml

org.springframework.boot

spring-boot-starter-data-mongodb

```

2. 配置 yml:

```yaml

spring:

data:

mongodb:

uri: mongodb://fox:fox@192.168.1.3:27017/test?authSource=admin # uri等同于下面的配置

# database: test # host: 192.168.1.3 # port: 27017 # username: fox # password: fox # authentication-database: admin

# 连接配置参考文档: https://docs.mongodb.com/manual/reference/connection-string/

```

3. 使用时注入 `mongoTemplate`:

```java

@Autowired

MongoTemplate mongoTemplate;

```

4.2 集合操作:

```java

/** * 创建集合 */

@Test

public void testCreateCollection() {

final String collectionName = "emp";

if (mongoTemplate.collectionExists(collectionName)) {

mongoTemplate.dropCollection(collectionName);

}

mongoTemplate.createCollection(collectionName);

}

```

4.3 文档操作:

相关注解:

- `@Document`:修饰范围在类上,用来映射这个类的一个对象为 MongoDB 中一条文档数据。属性:`value`、`collection`用来指定操作的集合名称。

- `@Id`:修饰范围在成员变量上,用于表示 MongoDB 文档的主键。

- `@Field`:修饰范围在成员变量、方法上,用于指定 MongoDB 文档的字段名和类型。

以下是重构后的代码:

```java

import org.springframework.data.annotation.Id;

import org.springframework.data.mongodb.core.mapping.Document;

import org.springframework.data.mongodb.core.mapping.Field;

import org.springframework.data.mongodb.core.mapping.Transient;

import org.springframework.data.mongodb.core.mapping.annotation.Document;

import org.springframework.data.mongodb.core.mapping.annotation.Id;

import org.springframework.data.mongodb.core.mapping.annotation.Field;

import org.springframework.data.mongodb.core.mapping.annotation.Transient;

import org.springframework.data.mongodb.core.mapping.annotation.Document;

import org.springframework.data.mongodb.core.mapping.annotation.AllArgsConstructor;

import org.springframework.data.mongodb.core.mapping.annotation.NoArgsConstructor;

import lombok.Data;

import java.util.Date;

import java.math.BigDecimal;

@Document("emp") // 对应emp集合中的一个文档

@AllArgsConstructor

@NoArgsConstructor

@Data

public class Employee {

@Id //映射文档中的_id

private Long id;

@Field(value = "username")

private String name;

@Field

private BigDecimal salary;

@Field

private Date birthday;

}

```

```java

import org.junit.Test;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.mongodb.core.MongoTemplate;

import org.springframework.data.mongodb.core.query.Criteria;

import org.springframework.data.mongodb.core.query.Query;

import java.util.Arrays;

import java.util.Date;

import java.util.List;

public class EmployeeTest {

@Autowired

private MongoTemplate mongoTemplate;

@Test

public void testInsert() {

Employee emp = new Employee(1L, "小明", new BigDecimal("3010000.00"), new Date());

// 添加文档

// save: _id存在时更新数据

mongoTemplate.save(emp);

// insert: _id存在时抛出异常, 支持批量操作

mongoTemplate.insert(emp);

List list = Arrays.asList(

new Employee(2L, "张三", new BigDecimal("2150000.00"), new Date()),

new Employee(3L, "李四", new BigDecimal("2680000.00"), new Date()),

new Employee(4L, "王五", new BigDecimal("2280000.00"), new Date()),

new Employee(5L, "张龙", new BigDecimal("2860000.00"), new Date()),

new Employee(6L, "赵虎", new BigDecimal("2470000.00"), new Date()),

new Employee(7L, "赵六", new BigDecimal("2812000.00"), new Date())

);

// 插入多条数据

mongoTemplate.insert(list, Employee.class);

}

}

```

Criteria 是标准查询的接口,它提供了一些静态方法,如 Criteria.where,可以将多个条件组合在一起。通过使用这些方法,我们可以轻松地将多个方法和查询连接起来,从而方便地操作查询语句。

在 MongoDB 中,我们可以使用 Criteria 接口来构建查询条件。以下是一些常用的方法及其说明:

1. `and(String key)`:$and 操作符,表示两个或多个字段同时满足查询条件。

2. `andOperator(Criteria... criteria)`:$and 操作符,表示两个或多个字段同时满足查询条件。

3. `orOperator(Criteria... criteria)`:$or 操作符,表示至少有一个字段满足查询条件。

4. `gt(Object o)`:大于操作符,表示查询结果中某个字段的值大于给定值。

5. `gte(Object o)`:大于等于操作符,表示查询结果中某个字段的值大于等于给定值。

6. `in(Object... o)`:包含操作符,表示查询结果中某个字段的值在给定值集合中。

7. `is(Object o)`:等于操作符,表示查询结果中某个字段的值等于给定值。

8. `lt(Object o)`:小于操作符,表示查询结果中某个字段的值小于给定值。

9. `lte(Object o)`:小于等于操作符,表示查询结果中某个字段的值小于等于给定值。

10. `nin(Object... o)`:不包含操作符,表示查询结果中某个字段的值不在给定值集合中。

通过组合这些方法和操作符,我们可以根据实际需求构建复杂的查询条件。

以下是重构后的代码:

```java

@Test

public void testFind() {

System.out.println("===============查询所有文档===============");

// 查看所有文档

List list = mongoTemplate.findAll(Employee.class);

for (Employee emp : list) {

System.out.println(emp);

}

System.out.println("==============根据id查询==============");

// 根据id查询

Employee emp = mongoTemplate.findById(1L, Employee.class);

System.out.println(emp);

System.out.println("==========findOne返回第一个文档==========");

// 如果查询结果是多个,返回其中第一个文档对象

Employee one = mongoTemplate.findOne(new Query(), Employee.class);

System.out.println(one);

System.out.println("================条件查询================");

Criteria criteria = new Criteria();

Criteria ageCriteria = Criteria.where("age").gt(25);

Criteria salaryCriteria = Criteria.where("salary").gt(8000.00);

Criteria nameCriteria = Criteria.where("name").is("张三");

Criteria orCriteria = criteria.orOperator(nameCriteria, salaryCriteria);

Query query = new Query(orCriteria).with(Sort.by(Sort.Order.desc("salary")));

// skip limit分页参数设置,skip用于指定跳过记录数,limit则用于限定返回结果数量。

query = query.with(Sort.by(Sort.Order.desc("salary")))

.skip(0)//指定跳过记录数

.limit(4);//每页显示记录数

//查询结果列表输出

List employees = mongoTemplate.find(query, Employee.class);

for (Employee empl : employees){

System.out.println(empl);

}

}

```

在Mongodb中,无论是使用客户端API还是使用Spring Data,更新返回结果都受到行数的影响。如果更新后的结果和更新前的结果是相同的,就返回0。

`updateFirst()` 方法只更新满足条件的第一条记录;

`updateMulti()` 方法更新所有满足条件的记录;

`upsert()` 方法表示没有符合条件的记录则插入数据。

以下是重构后的代码,我将注释中的内容删除了,并对代码进行了格式化:

```java

@Test

public void testUpdate() {

// 设置查询条件

Query query = new Query(Criteria.where("salary").gte(10000));

System.out.println("==========更新前===========");

List employees = mongoTemplate.find(query, Employee.class);

employees.forEach(System.out::println);

Update update = new Update();

// 设置更新属性

update.set("salary", 14000);

// updateFirst() 只更新满足条件的第一条记录

// UpdateResult updateResult = mongoTemplate.updateFirst(query, update, Employee.class);

// updateMulti() 更新所有满足条件的记录

// UpdateResult updateResult = mongoTemplate.updateMulti(query, update, Employee.class);

// upsert() 没有符合条件的记录则插入数据

update.setOnInsert("id", 11);

// update.push("name", "John Doe");

// update.inc("age", incAmount);

// set操作符支持各种操作,如$inc,$mul,$rename等。如果需要更多功能,可以使用其他操作符。

// push操作符用于向数组字段中增加一个值。例如,如果有一个名为"addresses"的数组字段,那么可以使用update.push("addresses", "New York")来向这个字段中添加一个新的地址。注意:此操作只适用于DBObject中的arrayFieldName字段。

}

```

删除文档

```java

@Test

public void testDelete() {

// 删除所有文档

// mongoTemplate.remove(new Query(), Employee.class);

// 条件删除

Query query = new Query(Criteria.where("salary").gte(10000));

mongoTemplate.remove(query, Employee.class);

}

```

聚合操作

聚合操作处理数据记录并返回计算结果(诸如统计平均值,求和等)。聚合操作组值来自多个文档,可以对分组数据执行各种操作以返回单个结果。聚合操作包含三类:单一作用聚合、聚合管道、MapReduce。

5.1 单一作用聚合

MongoDB提供以下这类单一作用的聚合函数:

- db.collection.estimatedDocumentCount():忽略查询条件,返回集合或视图中所有文档的计数。

- db.collection.count():返回与find()集合或视图的查询匹配的文档计数。等同于db.collection.find(query).count()。

- db.collection.distinct():在单个集合或视图中查找指定字段的不同值,并在数组中返回结果。

MongoDB聚合框架是一个计算框架,它可以作用在一个或几个集合上,对集合中的数据进行一系列运算,并将这些数据转化为期望的形式。从效果而言,聚合框架相当于 SQL 查询中的 GROUP BY、LEFT OUTER JOIN、AS 等。

聚合框架由多个阶段组成,每个阶段接受一系列文档(原始数据),对这些文档进行一系列运算,结果文档输出给下一个阶段。整个聚合运算过程称为管道(Pipeline),它是由多个阶段(Stage)组成的。除 $out、$merge 和 $geoNear 阶段之外,每个阶段都可以在管道中出现多次。

聚合管道操作语法如下:

```javascript

pipeline = [$stage1, $stage2, ...$stageN];

db.collection.aggregate(pipeline, {options})

```

其中,`pipelines` 是一组数据聚合阶段。除了 $out、$out、Merge 和 $geoNear 阶段之外,每个阶段都可以在管道中出现多次。`options` 是可选的,包含聚合操作的其他参数,如查询计划、是否使用临时文件、游标、最大操作时间、读写策略、强制索引等。

在数据管道中,聚合阶段是非常重要的一个环节。常用的聚合管道包含了很多聚合阶段,下面是最常用的聚合阶段和它们的描述以及SQL等价运算符:

1. $match(筛选条件)

* 描述:用于筛选出符合条件的文档。

* SQL等价运算符:WHERE

2. $project(投影)

* 描述:用于选择需要展示的字段。

* SQL等价运算符:SELECT

3. $lookup(左外连接)

* 描述:类似于LEFT OUTER JOIN,用于将多个文档关联起来。

* SQL等价运算符:LEFT OUTER JOIN

4. $sort(排序)

* 描述:用于对文档进行排序。

* SQL等价运算符:ORDER BY

5. $group(分组)

* 描述:用于对文档进行分组操作。

* SQL等价运算符:GROUP BY

6. $skip/$limit(分页)

* 描述:用于实现分页功能。

* SQL等价运算符:LIMIT OFFSET

7. $unwind(展开数组)

* 描述:用于将嵌套数组展开成单个文档。

* SQL等价运算符:UNNEST

8. $graphLookup(图搜索)

* 描述:用于在图数据库中进行搜索。

* SQL等价运算符:GRAPH <图名称>.<节点名称> + WHERE <条件>

9. $facet/$bucket(分面搜索)

* 描述:用于实现多维度的搜索过滤功能。

* SQL等价运算符:FACET <字段名称>或BUCKET <字段名称>

以下是重构后的内容:

let tags = ["nosql", "mongodb", "document", "developer", "popular"];

let types = ["technology", "sociality", "travel", "novel", "literature"];

let books=[];

for(let i=0; i<50; i++){

let typeIdx = Math.floor(Math.random() * types.length);

let tagIdx = Math.floor(Math.random() * tags.length);

let tagIdx2 = Math.floor(Math.random() * tags.length);

let favCount = Math.floor(Math.random() * 100);

let username = "xx00" + Math.floor(Math.random() * 10);

let age = 20 + Math.floor(Math.random() * 15);

let book = {

title: "book-" + i,

type: types[typeIdx],

tag: [tags[tagIdx], tags[tagIdx2]],

favCount: favCount,

author: {name:username, age:age}

};

books.push(book)

}

db.books.insertMany(books);

$project

投影操作,将原始字段投影成指定名称,如将集合中的 title 投影成 name

db.books.aggregate([{$project:{name:"$title"}}])

以下是重构后的代码:

```javascript

db.books.aggregate([

// $project 可以灵活控制输出文档的格式,也可以剔除不需要的字段

{$project: {name: "$title", _id: 0, type: 1, author: 1}},

// 从嵌套文档中排除字段

{$project: {name: "$title", _id: 0, type: 1, "author.name": 1}},

// $match 用于对文档进行筛选,之后可以在得到的文档子集上做聚合

{$match: {type: "technology"}},

// 筛选管道操作和其他管道操作配合时候时,尽量放到开始阶段,这样可以减少后续管道操作符要操作的文档数,提升效率

{$project: {name: "$title", _id: 0, type: 1, author: {name: 1}}},

// $count 计数并返回与查询匹配的结果数

{$count: "type_count"},

]);

```

重构后的代码将 `$project`、`$match`、`$count` 三个聚合阶段分别提取出来,使代码结构更加清晰。同时,将 `$project` 阶段的内容合并到一起,避免了重复代码。

按指定的表达式对文档进行分组,并将每个不同分组的文档输出到下一个阶段。输出文档包含一个_id字段,该字段按键包含不同的组。输出文档还可以包含计算字段,该字段保存由$group的_id字段分组的一些accumulator表达式的值。 $group不会输出具体的文档而只是统计信息。

以下是使用MongoDB聚合框架对文档进行分组并生成输出文档的示例代码:

```javascript

db.books.aggregate([

{$group: {_id: null, count: {$sum: 1}, pop: {$sum: "$favCount"}, avg: {$avg: "$favCount"}}}

])

```

上述代码中,我们使用了MongoDB的聚合框架来执行分组操作。在`$group`阶段,我们设置了`_id`字段的值为`null`,表示对整个输入文档进行分组;然后定义了三个计算字段:`count`、`pop`和`avg`,分别计算每组文档的数量、收藏总数和平均值。

请注意,上述示例代码中的`db.books`表示数据库中的名为`books`的集合。您需要将其替换为您实际使用的数据库和集合名称。此外,根据您的具体需求,您可以根据提供的表达式自定义计算字段,例如使用其他聚合函数或嵌套`$group`等操作。

希望这可以帮助到您!如有进一步的问题,请随时提问。

根据您提供的内容,我为您生成了以下内容重构:

1. 统计每个作者的每本book的收藏数:

```javascript

db.books.aggregate([

{

$group: {

_id: "$author.name",

pop: { $sum: "$favCount" },

},

},

]);

```

2. 每个作者的book的type合集:

```javascript

db.books.aggregate([

{

$group: {

_id: { name: "$author.name", title: "$title" },

pop: { $sum: "$favCount" },

},

},

]);

```

3. 将姓名为xx006的作者的book的tag数组拆分为多个文档:

```javascript

db.books.aggregate([

{$match: {"author.name": "xx006"}},

{$unwind: "$tag"},

]);

```

以下是重构后的代码:

```javascript

db.books.aggregate([

{$unwind: "$tag"},

{$group: {_id: "$author.name", types: {$addToSet: "$tag"}}}

])

```

示例数据:

```javascript

db.books.insert([

{

title: "book-51",

type: "technology",

favCount: 11,

tag: [],

author: {

name: "fox",

age: 28

}

},

{

title: "book-52",

type: "technology",

favCount: 15,

author: {

name: "fox",

age: 28

}

},

{

title: "book-53",

type: "technology",

tag: [

"nosql",

"document"

],

favCount: 20,

author: {

name: "fox",

age: 28

}

}

])

```

以下是重构后的代码:

```javascript

# 使用includeArrayIndex选项来输出数组元素的数组索引

db.books.aggregate([

{$match:{"author.name":"fox"}},

{$unwind:{path:"$tag", includeArrayIndex: "arrayIndex"}}

])

# 使用preserveNullAndEmptyArrays选项在输出中包含缺少size字段,null或空数组的文档

db.books.aggregate([

{$match:{"author.name":"fox"}},

{$unwind:{path:"$tag", preserveNullAndEmptyArrays: true}}

])

# $limit限制传递到管道中下一阶段的文档数

db.books.aggregate([

{$limit: 5}

])

# $sort对所有输入文档进行排序,并按排序顺序将它们返回到管道。

# 要对字段进行排序,请将排序顺序设置为1或-1,以分别指定升序(1)或降序(-1)排序。

db.books.aggregate([

{$sort: {"favCount": -1, "title": 1}}

])

# $skip跳过进入stage的指定数量的文档,并将其余文档传递到管道中的下一个阶段。

db.books.aggregate([

{$skip: 5}

])

```

MongoDB 3.2版本新增了$lookup阶段,主要用途是实现多表关联查询,相当于关系型数据库中的多表关联查询。在每个输入待处理的文档中,经过$lookup阶段的处理,输出的新文档中会包含一个新生成的数组(可根据需要命名新key)。这个数组列存放的数据来源于被Join集合的适配文档。如果没有,则集合为空([])。

$lookup阶段的语法如下:

```bash

db.collection.aggregate([{ $lookup: { from: "", localField: "", foreignField: "", as: "" } })

```

以下是各个参数的作用:

* `from`:同一个数据库下等待被Join的集合。

* `localField`:源集合中的match值。如果输入的集合中,某文档没有`localField`这个Key(Field),在处理的过程中,会默认为此文档含有`localField:null`的键值对。

* `foreignField`:待Join的集合的match值。如果待Join的集合中,文档没有`foreignField`值,在处理的过程中,会默认为此文档含有`foreignField:null`的键值对。

* `as`:为输出文档的新增值命名。如果输入的集合中已存在该值,则会覆盖掉。注意:`null = null`,此为真。其语法功能类似于下面的伪SQL语句:

```sql

SELECT a.*, b. FROM a JOIN b ON a. = b.

```

重构如下:

```sql

SELECT *,

FROM collection

WHERE IN (

SELECT *

FROM

WHERE =

);

```

案例:

假设我们有两个集合,一个是学生集合,另一个是班级集合。我们想要查询所有选修了某个课程的学生信息。

数据准备:

1. 学生集合(students):包含学生的ID、姓名等信息。

2. 班级集合(classes):包含班级的ID、班级名称等信息。

3. 选课集合(course_selections):包含课程ID和学生ID。

4. 学生-课程关联表(student_course_associations):包含学生ID和课程ID。

根据这些数据,我们需要先找到选修了某个课程的学生ID,然后再根据学生ID从学生集合中查询对应的学生信息。可以使用以下SQL语句实现:

```sql

SELECT s.*, c.course_name -- 输出学生信息和课程名称字段

FROM students s

JOIN course_selection cs ON s.id = cs.student_id -- 根据学生ID关联选课集合

JOIN student_course_association sac ON cs.course_id = sac.course_id -- 根据课程ID关联学生-课程关联表

JOIN classes c ON sac.class_id = c.id; -- 根据班级ID关联班级集合

```

您可以使用$lookup操作符进行关联查询。$lookup操作符用于实现多表关联查询,将相关的文档连接在一起。例如,要查询订单和用户之间的关系,您可以使用以下代码:

```javascript

db.orders.aggregate([

{$lookup: {from: "customer", localField: "customerCode", foreignField: "customerCode", as: "customer_info"}}

]);

```

```javascript

db.customer.aggregate([

{

$lookup: {

from: "order",

localField: "customerCode",

foreignField: "customerCode",

as: "customerOrder"

}

}

]);

db.order.aggregate([

{

$lookup: {

from: "customer",

localField: "customerCode",

foreignField: "customerCode",

as: "curstomer"

}

},

{

$lookup: {

from: "orderItem",

localField: "orderId",

foreignField: "orderId",

as: "orderItem"

}

}

]);

```

以下是重构后的MongoDB聚合查询代码:

```javascript

db.books.aggregate([

{ $match: { favCount: { $gt: 0 } } },

{ $unwind: "$tag" },

{ $group: { _id: "$tag", total: { $sum: "$favCount" } } },

{ $sort: { total: -1 } }

])

```

这段代码的功能如下:

1. `$match`阶段:用于过滤收藏数(favCount)为0的文档。

2. `$unwind`阶段:用于将标签数组进行展开,这样原本包含3个标签的文档会被拆解为3个条目。

3. `$group`阶段:对拆解后的文档进行分组计算,使用`$sum`操作符按收藏数(favCount)字段进行累加。

4. `$sort`阶段:接收分组计算的输出,按总收藏数(total)得分进行降序排序。

. 返回人口超过1000万的州的代码:

```python

db.zips.aggregate([

{

$group: {

_id: "$state",

totalPop: { $sum: "$pop" }

}

},

{

$match: {

totalPop: { $gte: 10*1000*1000 }

}

}

])

```

这个聚合操作的等价SQL是:

```sql

SELECT state, SUM(pop) AS totalPop FROM zips GROUP BY state HAVING totalPop >= (10*1000*1000)

```

2. 返回各州平均城市人口的代码:

```python

db.zips.aggregate([

{

$group: {

_id: { state: "$state", city: "$city" },

cityPop: { $sum: "$pop" }

}

},

{

$group: {

_id: "$_id.state",

avgCityPop: { $avg: "$cityPop" }

}

}

])

```

根据提供的内容,以下是重构后的MongoDB聚合查询语句:

```

db.zips.aggregate([

{ $group: {

_id: { state: "$state", city: "$city" },

pop: { $sum: "$pop" }

}

},

{ $sort: { pop: -1 } },

{ $group: {

_id: "$_id.state",

biggestCity: { $last: "$_id.city" },

biggestPop: { $last: "$pop" },

smallestCity: { $first: "$_id.city" },

smallestPop: { $first: "$pop" }

}

},

{ $project: {

_id: 0,

state: "$_id",

biggestCity: {

name: "$biggestCity",

pop: "$biggestPop"

},

smallestCity: {

name: "$smallestCity",

pop: "$smallestPop"

}

}

}

])

```

db.collectionmapReduce(

{

map: function() { emit(key, value); },

reduce: function(key, values) {

return Array.sum(values);

},

out: ,

query: ,

sort: ,

limit: ,

finalize: ,

scope: ,

jsMode: ,

verbose: ,

bypassDocumentValidation:

}

)

其中,map函数将数据拆分成键值对并交给reduce函数进行统计运算;out参数表示输出结果的集合名称;query参数用于筛选需要处理的数据;sort参数用于排序数据;limit参数限制了输入到map函数中的文档数量;finalize参数在reduce函数处理完所有数据后执行;scope参数指定了全局变量的作用范围;jsMode参数表示是否启用JavaScript模式(默认为false);verbose参数表示是否在结果中显示时间;bypassDocumentValidation参数表示是否跳过数据校验。

.4 SpringBoot整合MongoDB聚合操作

在SpringBoot中,我们可以使用MongoTemplate提供的aggregate方法来实现对数据的聚合操作。基于聚合管道,MongoDB提供了以下可操作的内容:

1. 支持的操作

2. Java接口

3. 说明

- $project:修改输入文档的结构

- $match:用于过滤数据

- $limit:用于限制MongoDB聚合管道返回的文档数

- $skip:在聚合管道中跳过指定数量的文档

- $unwind:在文档中的某一个数组类型字段拆分成多条

- $group:在集合中的文档分组,可用于统计结果

- $sort:将输入文档排序后输出

- $geoNear:输出接近某一地理位置的有序文档

基于聚合操作,如Aggregation.group,MongoDB提供可选的表达式。聚合表达式包括以下几种:

1. 求和($sum)

2. 求平均($avg)

3. 获取集合中所有文档对应值的最小值($min)

4. 获取集合中所有文档对应值的最大值($max)

5. 在结果文档中插入值到一个数组中($push)

6. 在结果文档中插入值到一个数组中,但不创建副本($addToSet)

7. 在聚合管道中跳过指定数量的文档($skip)

8. 在聚合管道中限制返回的文档数($limit)

9. 对输入文档进行排序($sort)

10. 输出接近某一地理位置的有序文档($geoNear)

在Java接口中,这些聚合表达式分别对应如下方法:

- Aggregation.group().sum("field").as("sum") 求和

- Aggregation.group().avg("field").as("avg") 求平均

- Aggregation.group().min("field").as("min") 获取集合中所有文档对应值的最小值

- Aggregation.group().max("field").as("max") 获取集合中所有文档对应值的最大值

- Aggregation.group().push("field").as("push") 在结果文档中插入值到一个数组中

- Aggregation.group().addToSet("field").as("addToSet") 在结果文档中插入值到一个数组中,但不创建副本

- Aggregation.group().first("field").as("first") 在聚合管道中跳过指定数量的文档

根据提供的内容,我们可以重构为以下Java代码:

```java

import org.springframework.data.mongodb.core.aggregation.Aggregation;

import org.springframework.data.mongodb.core.aggregation.GroupOperation;

import org.springframework.data.mongodb.core.aggregation.MatchOperation;

import org.springframework.data.mongodb.core.aggregation.ProjectionOperation;

import org.springframework.data.mongodb.core.query.Criteria;

public class AggregationExample {

public static void main(String[] args) {

// 根据资源文档的排序获取第一个文档数据

GroupOperation groupFirst = Aggregation.group("field")

.first("field").as("first");

// 根据资源文档的排序获取最后一个文档数据

GroupOperation groupLast = Aggregation.group("field")

.last("field").as("last");

// 返回人口超过1000万的州

Criteria matchCriteria = Criteria.where("totalPop").gt(10 * 1000 * 1000);

MatchOperation matchOperation = Aggregation.match(matchCriteria);

Aggregation aggregation = Aggregation.newAggregation(groupFirst, groupLast, matchOperation);

}

}

```

这段Java代码首先定义了两个聚合操作:`groupFirst` 和 `groupLast`,分别用于获取第一个和最后一个文档数据。然后,我们创建了一个 `matchOperation`,用于筛选出人口超过1000万的州。最后,我们将这些聚合操作组合在一起,形成一个完整的聚合管道。

您可以使用以下代码来查询各州平均城市人口:`db.zips.aggregate([

{ $group: { _id: "$state", averagePopulation: { $avg: "$pop" } } }

])`。这将返回一个包含每个州和其平均城市人口的数组。

首先,我们需要解析这个MongoDB聚合查询。这个查询的目的是按州和城市对数据进行分组,并计算每个州的平均城市人口。然后按照平均城市人口降序排序。

接下来,我们将使用Java实现这个查询。我们将使用MongoDB Java驱动程序来执行这个查询。

1. 添加Maven依赖:

```xml

org.mongodb

mongodb-driver-sync

4.4.0

```

2. 实现Java代码:

```java

import com.mongodb.MongoClientSettings;

import com.mongodb.client.*;

import org.bson.Document;

import org.bson.conversions.Bson;

import java.util.Arrays;

import java.util.List;

import java.util.stream.Collectors;

public class MongoDBAggregationExample {

public static void main(String[] args) {

// 连接到MongoDB服务器

MongoClientSettings settings = MongoClientSettings.builder()

.applyConnectionString(new ConnectionString("mongodb://localhost:27017"))

.build();

MongoClient mongoClient = MongoClients.create(settings);

MongoDatabase database = mongoClient.getDatabase("test");

MongoCollection collection = database.getCollection("zips");

// 构建聚合管道

List pipeline = Arrays.asList(

new Document("$group", new Document("_id", new Document("state", "$state").append("city", "$city"))).append("cityPop", new Document("$sum", "$pop")),

new Document("$group", new Document("_id", new Document("_id.state"))).append("avgCityPop", new Document("$avg", "$cityPop")),

new Document("$sort", new Document("avgCityPop", -1))

);

// 执行聚合查询

AggregateIterable result = collection.aggregate(pipeline);

for (Document doc : result) {

System.out.println(doc.toJson());

}

}

}

```

这段Java代码首先连接到本地的MongoDB服务器,并选择一个名为"test"的数据库和一个名为"zips"的集合。然后,它构建了一个聚合管道,该管道包含三个阶段:按州和城市分组、计算每个州的平均城市人口以及按平均城市人口降序排序。最后,它执行聚合查询并打印结果。

```java

@Testpublic void test2() {

// $group

GroupOperation groupOperation = Aggregation.group("state", "city")

.sum("pop").as("cityPop");

// $group

GroupOperation groupOperation2 = Aggregation.group("_id.state")

.avg("cityPop").as("avgCityPop");

// $sort

SortOperation sortOperation = Aggregation.sort(Sort.Direction.DESC, "avgCityPop");

// 按顺序组合每一个聚合步骤

TypedAggregation typedAggregation = Aggregation.newAggregation(Zips.class,

groupOperation, groupOperation2, sortOperation);

// 执行聚合操作,如果不使用 Map,也可以使用自定义的实体类来接收数据

AggregationResults aggregationResults = mongoTemplate

.aggregate(typedAggregation, AvgCityPopDO.class);

// 取出最终结果

List mappedResults = aggregationResults.getMappedResults();

for (AvgCityPopDO avgCityPopDO : mappedResults) {

System.out.println(avgCityPopDO);

}

}

```

```java

import com.mongodb.BasicDBObject;

import com.mongodb.DB;

import com.mongodb.DBCollection;

import com.mongodb.DBCursor;

import com.mongodb.DBObject;

import com.mongodb.MongoClient;

import com.mongodb.client.AggregateIterable;

import com.mongodb.client.MongoCollection;

import com.mongodb.client.MongoDatabase;

import org.bson.Document;

import java.util.Arrays;

import java.util.List;

public class Main {

public static void main(String[] args) {

MongoClient mongoClient = new MongoClient("localhost", 27017);

MongoDatabase database = mongoClient.getDatabase("test");

DBCollection collection = database.getCollection("zips");

List pipeline = Arrays.asList(

new BasicDBObject("$group", new BasicDBObject("_id", new BasicDBObject("state", "$state").append("city", "$city"))),

new BasicDBObject("$sort", new BasicDBObject("pop", 1)),

new BasicDBObject("$group", new BasicDBObject("_id", "$_id.state")

.append("biggestCity", new BasicDBObject("$last", "$_id.city"))

.append("biggestPop", new BasicDBObject("$last", "$pop"))

.append("smallestCity", new BasicDBObject("$first", "$_id.city"))

.append("smallestPop", new BasicDBObject("$first", "$pop"))),

new BasicDBObject("$project", new BasicDBObject("_id", 0)

.append("state", "$_id")

.append("biggestCity", new BasicDBObject("name", "$biggestCity").append("pop", "$biggestPop"))

.append("smallestCity", new BasicDBObject("name", "$smallestCity").append("pop", "$smallestPop")))

);

AggregateIterable result = collection.aggregate(pipeline);

for (Document doc : result) {

System.out.println(doc.toJson());

}

}

}

```

以下是重构后的代码:

```java

@Test

public void test3() {

// 按顺序组合每一个聚合步骤

TypedAggregation typedAggregation = Aggregation.newAggregation(

Zips.class,

// $group

GroupOperation.group("state", "city").sum("pop").as("pop"),

// $sort

SortOperation.sort(Sort.Direction.ASC, "pop"),

// $group

GroupOperation.group("_id.state")

.last("_id.city").as("biggestCity")

.last("pop").as("biggestPop")

.first("_id.city").as("smallestCity")

.first("pop").as("smallestPop")

.and("_id").as("state")

);

// 执行聚合操作,如果不使用 Map,也可以使用自定义的实体类来接收数据

AggregationResults aggregationResults = mongoTemplate.aggregate(typedAggregation, SmallBigCityPopDO.class);

// 取出最终结果

List mappedResults = aggregationResults.getMappedResults();

for (SmallBigCityPopDO smallBigCityPopDO : mappedResults) {

System.out.println(smallBigCityPopDO);

}

}

```

MongoDB 视图是一个可查询的对象,它的内容由其他集合或视图上的聚合管道定义。MongoDB 不会将视图内容持久化到磁盘。当客户端查询视图时,视图的内容按需计算。MongoDB 可以要求客户端具有查询视图的权限。MongoDB 不支持对视图进行写操作。

作用:数据抽象、保护敏感数据的一种方法、将敏感数据投影到视图之外、只读、结合基于角色的授权,可按角色访问信息、数据准备。

以下是根据提供的内容重构后的代码:

```javascript

let orders = new Array();

let shipping = new Array();

let addresses = [

"广西省玉林市",

"湖南省岳阳市",

"湖北省荆州市",

"甘肃省兰州市",

"吉林省松原市",

"江西省景德镇",

"辽宁省沈阳市",

"福建省厦门市",

"广东省广州市",

"北京市朝阳区"

];

for (let i = 10000; i < 20000; i++) {

let orderNo = i + Math.random().toString().substr(2, 5);

orders[i] = {

orderNo: orderNo,

userId: i,

price: Math.round(Math.random() * 10000) / 100,

qty: Math.floor(Math.random() * 10) + 1,

orderTime: new Date(new Date().setSeconds(Math.floor(Math.random() * 10000)))

};

let address = addresses[Math.floor(Math.random() * addresses.length)];

shipping[i] = {

orderNo: orderNo,

address: address,

recipienter: "Wilson",

province: address.substr(0, 3),

city: address.substr(3, 3)

};

}

db.order.insert(orders);

db.shipping.insert(shipping);

```

请根据以下内容完成重构,并保持段落结构:

db.createView(

"",

"",

[

"",

,

...

],

{

"collation" : { <collation> }

}

)

: 必须,视图名称

: 必须,数据源,集合/视图

[]: 可选,一组管道

collation: optional,排序规则

例如现在查看当天最高的10笔订单视图,需要实时显示金额最高的订单。假设有一个订单集合和一个商品集合,我们需要创建一个视图,包含订单信息和商品信息。首先删除原有的orderInfo视图(如果有的话):

```javascript

db.orderInfo.drop()

```

然后创建新的视图:

```javascript

db.orderInfo.createView(

"orderInfo", // 视图名称

"$lookup", // 需要使用$lookup连接操作符来关联订单和商品集合

{

$lookup:

{

from: "products", // 要关联的集合名

localField: "productId", // 订单集合中的字段名,用于匹配商品集合中的_id字段

foreignField: "_id", // 商品集合中的字段名,用于匹配订单集合中的productId字段

as: "product" // 将匹配到的商品信息存储在名为product的字段中

},

pipeline: [

{ $match: { orderTime: { $gte: ISODate("2022-01-26T00:00:00.000Z") } } }, // 按金额倒序排序,限制10个文档,选择要显示的字段(排除_id字段)

{ $sort: { price: -1 } }, // 按价格降序排列

{ $limit: 10 } // 只显示前10个文档(包括_id字段)

]

},

{ collation: { locale: "en", strength: 2 } } // 按照指定的排序规则进行排序(en_US.UTF-8)

)

```

最后查询新创建的视图:

```javascript

db.orderInfo.find()

```

以下是重构后的内容:

MongoDB 视图

6.1 创建视图

db.orderDetail.drop() db.createView( "orderDetail", "order", [ { $lookup: { from: "shipping", localField: "orderNo", foreignField: "orderNo", as: "shipping" } }, { $project: { "orderNo": 1, "price": 1, "shipping.address": 1 } } ] )

db.orderDetail.find()

6.2 修改视图

db.runCommand({ collMod: "orderInfo", viewOn: "order", pipeline: [ { $match: { "orderTime": { $gte: ISODate("2020-04-13T16:00:00.000Z") } } }, { $limit: 10 }, { $sort: { "price": -1 } }, // 增加qty { $project: { _id: 0, orderNo: 1, price: 1, price2: 1, shippingName: "$shipping.name", shippingPrice: "$shipping.price", shippingAddress: "$shipping.address" } } ] })

db.orderInfo.find()

6.3 删除视图

db.orderInfo.drop();

7. MongoDB索引

7.1 索引介绍

索引是一种用来快速查询数据的数据结构。B+Tree就是一种常用的数据库索引数据结构,MongoDB 采用B+Tree做索引,索引创建在collections上。MongoDB不使用索引的查询,先扫描所有的文档,再匹配符合条件的文档。使用索引的查询,通过索引找到文档,使用索引能够极大的提升查询效率。

MongoDB索引数据结构

MongoDB 官方文档中提到,MongoDB indexes use a B-tree data structure。但是,有一些资料坚持使用 B+Tree 来描述 MongoDB 的索引结构 。这是因为 MongoDB 3.2 之后,使用了 B+ 树作为其数据结构。B+ 树对于范围查询方面更有优势,可以让其更加快速地找到数据,加快其查找速度 。

每个查询原则上都需要创建对应索引:在MongoDB中,为了提高查询性能,我们需要为经常用于查询的字段创建索引。单个索引设计应考虑满足尽量多的查询需求,因为索引可以提高查询速度,但是过多的索引会影响写入速度。

以下是一些关于索引设计的建议:

1. 索引字段选择及顺序需要考虑查询覆盖率及选择性。尽量让常用的字段成为索引的一部分,以提高查询速度。同时,选择性较高的字段更容易成为索引,因为它们可以在索引中快速筛选出符合条件的记录。

2. 对于更新及其频繁的字段上创建索引需慎重。因为更新操作会破坏已有的索引,导致性能下降。在这种情况下,可以考虑使用缓存或者定期更新数据的方式来减轻对索引的压力。

3. 对于数组索引需要慎重考虑未来元素个数。因为数组索引的大小是固定的,如果未来数组元素个数增加,需要重新创建索引,这会导致性能下降。在这种情况下,可以使用字符串类型的索引来替代数组索引。

4. 对于超长字符串类型字段上慎用索引。因为超长字符串类型的字段不适合作为普通文本索引,因为它们的全文搜索性能较差。在这种情况下,可以考虑使用专门的文本索引或者其他方式来提高查询性能。

5. 并发更新较高的单个集合上不宜创建过多索引。因为过多的索引会增加维护成本和查询复杂度,降低系统的整体性能。

接下来是7.2 索引操作部分的内容:

在MongoDB中,我们可以使用db.collection.createIndex()方法来创建索引。具体语法如下:

```javascript

db.collection.createIndex(keys, options)

```

其中,Key值为你要创建的索引字段,1表示按升序创建索引,-1表示按降序创建索引;options参数用于设置其他选项。可选参数列表如下:

| Parameter | Type | Description |

| --- | --- | --- |

| background | Boolean | 建立索引过程会阻塞其他数据库操作,指定background可指定以后台方式创建索引。默认值为false。 |

| unique | Boolean | 建立的索引是否唯一。指定为true创建唯一索引。默认值为false。 |

| name | string | 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。 |

| dropDups | Boolean | 3.0+版本已废弃。在建立唯一索引时是否删除重复记录,指定true创建唯一索引。默认值为false。 |

| sparse | Boolean | 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档。默认值为 false。 |

| expireAfterSeconds | integer | 指定一个以秒为单位的数值,完成TTL设定,设定集合的生存时间。 |

| v | index version | 索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。 |

| weights | document | 索引权重值,数值在1到99,999之间,表示该索引相对于其他索引字段的得分权重。 |

| default_language | string | 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。默认为英语。 |

| language_override | string | 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为language。

注意:在3.0.0版本之前,创建索引的方法是`db.collection.ensureIndex()`。

创建索引(后台执行):

```javascript

db.values.createIndex({open: 1, close: 1}, {background: true})

```

创建唯一索引:

```javascript

db.values.createIndex({title:1},{unique:true})

```

查看索引信息:

```javascript

# 查看索引信息 db.books.getIndexes() # 查看索引键 db.books.getIndexKeys()

```

查看索引占用空间:

```javascript

db.collection.totalIndexSize([is_detail])

is_detail:可选参数,传入除0或false外的任意数据,都会显示该集合中每个索引的大小及总大小。如果传入0或false则只显示该集合中所有索引的总大小。默认值为false。

```

删除索引:

```javascript

# 删除集合指定索引 db.books.dropIndex("索引名称") # 删除集合所有索引 db.books.dropIndexes()

```

7.3 索引类型

单键索引(Single Field Indexes)

在某一个特定的字段上建立索引。MongoDB在ID上建立了唯一的单键索引,所以经常会使用id来进行查询; 在索引字段上进行精确匹配、排序以及范围查找都会使用此索引。

```javascript

db.books.createIndex({title:1})

```

对内嵌文档字段创建索引:

```javascript

db.books.createIndex({"author.name":1})

```

复合索引(Compound Index)

复合索引是多个字段组合而成的索引,其性质和单字段索引类似。但不同的是,复合索引中字段的顺序、字段的升降序对查询性能有直接的影响,因此在设计复合索引时则需要考虑不同的查询场景。

```javascript

db.books.createIndex({type: 1, favCount: 1})

```

多键索引(Multikey Index)

在数组的属性上建立索引。针对这个数组的任意值的查询都会定位到这个文档,既多个索引入口或者键值引用同一个文档。

以下是重构后的内容:

首先,我们可以使用`insertMany()`方法将多个文档插入到`inventory`集合中。这些文档包含了不同的类型(如食物)、项目名称和评分列表。以下是插入的示例文档:

```javascript

db.inventory.insertMany([

{ _id: 5, type: "food", item: "aaa", ratings: [5, 8, 9] },

{ _id: 6, type: "food", item: "bbb", ratings: [5, 9] },

{ _id: 7, type: "food", item: "ccc", ratings: [9, 5, 8] },

{ _id: 8, type: "food", item: "ddd", ratings: [9, 5] },

{ _id: 9, type: "food", item: "eee", ratings: [5, 9, 5] }

])

```

接下来,我们可以创建一个多键索引,以便根据项目的名称和评分来查询文档。以下是创建多键索引的示例代码:

```javascript

db.inventory.createIndex({ item: 1, ratings: 1 })

```

需要注意的是,MongoDB并不支持在复合索引中同时出现多个数组字段。因此,如果需要在一个复合字段上创建多键索引,可以考虑使用嵌套文档的索引数组。例如:

```javascript

db.inventory.createIndex({ item: ObjectId("item_id"), ratings: ObjectId("ratings_id") })

```

以下是重构后的内容:

```javascript

db.inventory.insertMany([

{

_id: 1,

item: "abc",

stock: [

{ size: "S", color: "red", quantity: 25 },

{ size: "S", color: "blue", quantity: 10 },

{ size: "M", color: "blue", quantity: 50 }

]

},

{

_id: 2,

item: "def",

stock: [

{ size: "S", color: "blue", quantity: 20 },

{ size: "M", color: "blue", quantity: 5 },

{ size: "M", color: "black", quantity: 10 },

{ size: "L", color: "red", quantity: 2 }

]

},

{

_id: 3,

item: "ijk",

stock: [

{ size: "M", color: "blue", quantity: 15 },

{ size: "L", color: "blue", quantity: 100 },

{ size: "L", color: "red", quantity: 25 }

]

}

]).then(() => db.inventory.createIndex({ "stock.size": 1, "stock.quantity": 1 }));

```

MongoDB提供了两种特殊的索引类型,分别是地理空间索引(Geospatial Index)和全文索引(Text Indexes)。

地理空间索引(Geospatial Index)主要用于实现基于地理位置的检索。在移动互联网时代,基于地理位置的检索(LBS)功能几乎是所有应用系统的标配。MongoDB为地理空间检索提供了非常方便的功能。地理空间索引(2dsphereindex)就是专门用于实现位置检索的一种特殊索引。

案例:如何使用MongoDB实现“查询附近商家”?

假设商家的数据模型如下:

```javascript

db.restaurant.insert({ restaurantId: 0, restaurantName:"兰州牛肉面", location: { type: "Point", coordinates: [ -73.97, 40.77 ] } })

```

首先,我们需要创建一个2dsphere索引来存储商家的位置信息:

```javascript

db.restaurant.createIndex({location : "2dsphere"})

```

接下来,我们可以使用$near查询操作符来查询附近10000米范围内的商家信息:

```javascript

db.restaurant.find( { location:{ $near :{ $geometry :{ type: "Point", coordinates: [ -73.88, 40.78 ]}, $maxDistance: 10000 } } })

```

在这个例子中,$near查询操作符用于实现附近商家的检索,返回数据结果会按距离排序。$geometry操作符用于指定一个GeoJSON格式的地理空间对象,type=Point表示地理坐标点,coordinates则是用户当前所在的经纬度位置;$maxDistance限定了最大距离,单位是米。

text操作符可以在具有文本索引的集合上执行文本检索。$text会使用空格和标点符号作为分隔符对搜索字符串进行分词,并对检索字符串中所有分词结果进行逻辑上的OR操作。全文索引可以解决快速文本查找的需求,例如在一个博客文章集合中,需要根据博客内容快速查找时,可以针对博客内容建立文本索引。

以下是一个使用MongoDB全文索引的案例:

1. 数据准备:

```javascript

db.stores.insert([

{ _id: 1, name: "Java Hut", description: "Coffee and cakes" },

{ _id: 2, name: "Burger Buns", description: "Gourmet hamburgers" },

{ _id: 3, name: "Coffee Shop", description: "Just coffee" },

{ _id: 4, name: "Clothes Clothes Clothes", description: "Discount clothing" },

{ _id: 5, name: "Java Shopping", description: "Indonesian goods" }

])

```

2. 创建name和description的全文索引:

```javascript

db.stores.createIndex({name: "text", description: "text"})

```

3. 测试:通过$text操作符查找包含“coffee”、“shop”、“java”列表中任何词语的商店。注意,这里的查询字符串使用了空格将多个词语连接起来。

```javascript

db.stores.find({$text: {$search: "java coffee shop"}})

```

需要注意的是,MongoDB的文本索引功能存在诸多限制,官方并未提供中文分词的功能,这使得该功能的应用场景十分受限。此外,Hash索引(哈希索引)也是MongoDB中的一种索引类型,但它主要用于提高查询性能,而不是全文检索。

哈希索引与传统B-Tree索引的区别在于,哈希索引使用hash函数来创建索引,适用于精确匹配但不支持范围查询和多键hash。而通配符索引(Wildcard Indexes)则可以建立在一些不可预知的字段上,以实现查询加速。MongoDB 4.2 引入了通配符索引来支持对未知或任意字段的查询。

以下是一个使用哈希索引和通配符索引的示例:

1. 准备商品数据,不同商品属性不一样:

```javascript

db.products.insert([

{

"product_name": "Spy Coat",

"product_attributes": {

"material": [

"Tweed",

"Wool",

"Leather"

],

"size": {

"length": 72,

"units": "inches"

}

}

},

{

"product_name": "Spy Pen",

"product_attributes": {

"colors": [

"Blue",

"Black"

],

"secret_feature": {

"name": "laser",

"power": "1000",

"units": "watts"

}

}

},

{

"product_name": "Spy Book"

}

])

```

2. 创建哈希索引:

```javascript

db.products.createIndex({"product_attributes.$**": 1})

```

3. 创建通配符索引:

```javascript

db.products.createIndex({"product_attributes.$**": 1})

```

通配符索引可以支持任意单字段查询`product_attributes`或其嵌入字段。以下是三个示例:

1. 查询`product_attributes.size.length`大于60的产品:

```javascript

db.products.find({ "product_attributes.size.length": { $gt: 60 } })

```

2. 查询`product_attributes.material`为"Leather"的产品:

```javascript

db.products.find({ "product_attributes.material": "Leather" })

```

3. 查询`product_attributes.secret_feature.name`为"laser"的产品:

```javascript

db.products.find({ "product_attributes.secret_feature.name": "laser" })

```

注意事项:

- 通配符索引不兼容的索引类型或属性。例如,不能在已经创建了文本索引的情况下使用通配符索引。

- 不能直接在`product_attributes`上创建通配符索引,需要先创建其他非通配符的索引,然后再为其添加通配符索引。例如:

```javascript

db.products.createIndex({ "product_attributes.$**": 1, "product_name": 1 })

```

- 通配符索引不能支持以下查询:

- `db.products.find({ "product_attributes": { $exists: false } })`

- `db.products.aggregate([{ $match: { "product_attributes": { $exists: false } } }])`

- 通配符索引不能支持以下查询:

- `db.products.find({ "product_attributes.colors": ["Blue", "Black"] })`

- `db.products.aggregate([{ $match: { "product_attributes.colors": ["Blue", "Black"] } }])`

唯一性索引和部分索引是MongoDB中的两种索引类型。唯一性索引用于保证文档中某个字段的值是唯一的,不允许有重复的值。部分索引则只对满足指定过滤条件的文档进行索引,可以大大降低索引的存储需求和维护成本。

创建唯一性索引的语法如下:

```

db.values.createIndex({title:1},{unique:true})

```

或者

```

db.values.createIndex({title:1,type:1},{unique:true})

```

或者

```

db.inventory.createIndex( { ratings: 1 },{unique:true} )

```

其中,`title`、`type`和`ratings`分别为要创建唯一性索引的字段名。对于分片的集合,唯一性约束必须匹配分片规则。为了保证全局的唯一性,分片键必须作为唯一性索引的前缀字段。

部分索引的语法如下:

```

db.restaurants.createIndex( { cuisine: 1, name: 1 }, { partialFilterExpression: { rating: { $gt: 5 } } } )

```

其中,`cuisine`和`name`分别为要创建部分索引的字段名,`rating: {$gt: 5}`表示只对评分大于5的文档进行索引。partialFilterExpression选项接受指定过滤条件的文档,例如等式表达式、$exists、$gt、$gte、$lt、$lte、$type等。

您好,您的问题是关于MongoDB中的唯一约束失效的问题。我理解您的问题是:在创建索引时,使用了部分索引和唯一约束,但是唯一约束失效了。

这个问题可能是由于您在使用部分索引时,没有正确地使用唯一约束导致的。在MongoDB中,如果您想要保证某个字段的唯一性,那么您需要使用唯一索引。而如果在创建索引时使用了部分索引,那么这个部分索引就不会包含唯一约束。因此,如果您想要保证某个字段的唯一性,那么您需要使用完整索引或者将部分索引转换为完整索引。

注意:如果同时指定了partialFilterExpression和唯一约束,那么唯一约束只适用于满足筛选器表达式的文档。如果文档不满足筛选条件,那么带有惟一约束的部分索引不会阻止插入不满足惟一约束的文档。

案例2:用户集合数据准备

```javascript

db.users.insertMany([

{ username: "david", age: 29 },

{ username: "amanda", age: 35 },

{ username: "rajiv", age: 57 }

]);

```

创建索引,指定username字段和部分过滤器表达式age: {$gte: 21}的唯一约束。

```javascript

db.users.createIndex(

{ username: 1 },

{ unique: true, partialFilterExpression: { age: { $gte: 21 } } }

);

```

测试:索引防止了以下文档的插入,因为文档已经存在,且指定的用户名和年龄字段大于21:

```javascript

db.users.insertMany([

{ username: "david", age: 27 },

{ username: "amanda", age: 25 },

{ username: "rajiv", age: 32 }

]);

```

但是,以下具有重复用户名的文档是允许的,因为唯一约束只适用于年龄大于或等于21岁的文档。

```javascript

db.users.insertMany([

{ username: "david", age: 20 },

{ username: "amanda" },

{ username: "rajiv", age: null }

]);

```

不索引不包含xmpp_id字段的文档

```javascript

db.addresses.createIndex( { "xmpp_id": 1 }, { sparse: true } )

```

如果稀疏索引会导致查询和排序操作的结果集不完整,MongoDB将不会使用该索引,除非hint()明确指定索引。

## 案例1

### 数据准备

```javascript

db.scores.insertMany([

{ "userid" : "newbie" },

{ "userid" : "abby", "score" : 82 },

{ "userid" : "nina", "score" : 90 }

])

```

### 创建稀疏索引

```javascript

db.scores.createIndex( { score: 1 } , { sparse: true } )

```

### 测试

```javascript

-- 使用稀疏索引 db.scores.find( { score: { $lt: 90 } } )

-- 即使排序是通过索引字段,MongoDB也不会选择稀疏索引来完成查询,以返回完整的结果 db.scores.find().sort( { score: -1 } )

-- 要使用稀疏索引,使用hint()显式指定索引 db.scores.find().sort( { score: -1 } ).hint( { score: 1 } )

```

同时具有稀疏性和唯一性的索引可以防止集合中存在字段值重复的文档,但允许不包含此索引字段的文档插入。

## 案例2

### 创建具有唯一约束的稀疏索引

```javascript

db.scores.createIndex( { score: 1 } , { sparse: true, unique: true } )

```

### 测试

这个索引将允许插入具有唯一的分数字段值或不包含分数字段的文档。因此,给定scores集合中的现有文档,索引允许以下插入操作:

以下是重构后的内容:

```javascript

// 插入多个文档到scores集合

db.scores.insertMany([

{ "userid": "AAAAAAA", "score": 43 },

{ "userid": "BBBBBBB", "score": 34 },

{ "userid": "CCCCCCC" },

{ "userid": "CCCCCCC" }

]);

// 尝试插入评分为82和90的文档,由于已经存在评分为82和90的文档,因此无法插入

db.scores.insertMany([

{ "userid": "AAAAAAA", "score": 82 },

{ "userid": "BBBBBBB", "score": 90 }

]);

// TTL索引(Time To Live Indexes)简介

在一般的应用系统中,并非所有的数据都需要永久存储。例如一些系统事件、用户消息等,这些数据随着时间的推移,其重要程度逐渐降低。更重要的是,存储这些大量的历史数据需要花费较高的成本,因此项目中通常会对过期且不再使用的数据进行老化处理。

通常的做法如下:

方案一: 为每个数据记录一个时间戳,应用侧开启一个定时器,按时间戳定期删除过期的数据。

方案二: 数据按日期进行分表,同一天的数据归档到同一张表,同样使用定时器删除过期的表。

对于数据老化,MongoDB提供了一种更加便捷的做法:TTL(Time To Live)索引。TTL索引需要声明在一个日期类型的字段中,TTL 索引是特殊的单字段索引,MongoDB 可以使用它在一定时间或特定时钟时间后自动从集合中删除文档。

// 创建 TTL 索引,TTL 值为3600秒

db.eventlog.createIndex({ "lastModifiedDate": 1 }, { expireAfterSeconds: 3600 });

// 对集合创建TTL索引之后,MongoDB会在周期性运行的后台线程中对该集合进行检查及数据清理工作。 除了数据老化功能,TTL索引具有普通索引的功能,同样可以用于加速数据的查询。

```

TL 索引不保证过期数据会在过期后立即被删除。文档过期和 MongoDB 从数据库中删除文档的时间之间可能存在延迟。删除过期文档的后台任务每 60 秒运行一次。因此,在文档到期和后台任务运行之间的时间段内,文档可能会保留在集合中。

TTL 索引可以减少开发的工作量,而且通过数据库自动清理的方式会更加高效、可靠。但是在使用 TTL 索引时需要注意以下限制:

- TTL 索引只能支持单个字段,并且必须是非 _id 字段。

- TTL 索引不能用于固定集合。

- TTL 索引无法保证及时的数据老化,MongoDB 会通过后台的 TTLMonitor 定时器来清理老化数据,默认的间隔时间是 1 分钟。当然如果在数据库负载过高的情况下,TTL 的行为则会进一步受到影响。

- TTL 索引对于数据的清理仅仅使用了 remove 命令,这种方式并不是很高效。因此 TTL Monitor 在运行期间对系统 CPU、磁盘都会造成一定的压力。相比之下,按日期分表的方式操作会更加高效。

隐藏索引对查询规划器不可见,不能用于支持查询。通过对规划器隐藏索引,用户可以在不实际删除索引的情况下评估删除索引的潜在影响。如果影响是负面的,用户可以取消隐藏索引,而不必重新创建已删除的索引。

在MongoDB中,我们可以通过创建隐藏索引来优化查询性能。以下是一些操作示例:

1. 创建隐藏索引:

```javascript

db.restaurants.createIndex({ borough: 1 }, { hidden: true });

```

2. 查看现有索引信息:

```javascript

db.restaurants.getIndexes();

```

3. 隐藏现有索引:

```javascript

db.restaurants.hideIndex({ borough: 1 });

```

4. 取消隐藏索引:

```javascript

db.restaurants.unhideIndex({ borough: 1 });

```

案例:

假设我们有一个名为"scores"的集合,其中包含用户的分数信息。我们可以通过以下步骤创建一个隐藏索引:

1. 插入数据:

```javascript

db.scores.insertMany([

{ "userid": "newbie" },

{ "userid": "abby", "score": 82 },

{ "userid": "nina", "score": 90 }

]);

```

2. 创建隐藏索引:

```javascript

db.scores.createIndex({ userid: 1 }, { hidden: true });

```

3. 查看索引信息:

```javascript

db.scores.getIndexes();

```

此时,如果我们不使用这个隐藏索引进行查询,例如:

```javascript

db.scores.find({ userid: "abby" }).explain();

```

结果不会显示这个隐藏索引。要取消隐藏索引,可以执行以下命令:

```javascript

db.scores.unhideIndex({ userid: 1 });

```

然后再次尝试查询,可以看到这个隐藏索引已经被取消隐藏,可以在查询中使用了。

如果你的查询会使用到多个字段,MongoDB有两个索引技术可以使用:交叉索引和复合索引。交叉索引就是针对每个字段单独建立一个单字段索引,然后在查询执行时候使用相应的单字段索引进行索引交 叉而得到查询结果。交叉索引目前触发率较低,所以如果你有一个多字段查询的时候,建议使用复合索 引能够保证索引正常的使用。

以下是一些关于MongoDB复合索引的建议:

- 查找所有年龄小于30岁的深圳市马拉松运动员:`db.athelets.find({sport: "marathon", location: "sz", age: {$lt: 30}}})`

- 创建复合索引:`db.athelets.createIndex({sport:1, location:1, age:1})`

- 复合索引字段顺序:匹配条件在前,范围条件在后(Equality First, Range After)。例如,在创建复合索引时如果条件有匹配和范围之分,那么匹配条件(sport: “marathon”) 应该在复合索引的前面。范围条件(age: <30)字段应该放在复合索引的后面。

- 尽可能使用覆盖索引(Covered Index),即查询语句中的条件可以直接用索引中的字段来满足。这样可以避免回表操作,提高查询效率。

- 在对一个集合创建索引时,建议使用后台运行选项 `{background: true}`,以避免影响其他读写操作。

此外,你还可以使用`explain()`方法来评估指定查询模型的执行计划。通过观察查询是否使用了索引、索引是否减少了扫描的记录数量以及是否存在低效的内存排序等信息,可以根据实际情况进行调整,从而提高查询效率。`explain()`方法的形式如下:

```javascript

db.collection.find().explain("")

```

其中,`verbose` 可选参数表示执行计划的输出模式,默认为`queryPlanner`。根据需要选择不同的模式进行查看。

QueryPlanner

`queryPlanner` 是 MongoDB 中用于解释查询执行计划的工具。通过 `explain("queryPlanner")`,您可以查看查询的执行计划和相关信息。以下是一个示例:

```javascript

db.books.find({title: "book-1"}).explain("queryPlanner")

```

## 字段名称

- plannerVersion:执行计划的版本

- namespace:查询的集合

- indexFilterSet:是否使用索引

- parsedQuery:查询条件

- winningPlan:最佳执行计划

- stage:查询方式

- filter:过滤条件

- direction:查询顺序

- rejectedPlans:拒绝的执行计划

- serverInfo:mongodb服务器信息

- executionStats:executionStats 模式的返回信息中包含了 queryPlanner 模式的所有字段,并且还包含了最佳执行计划的执行情况

## 创建索引

```javascript

db.books.createIndex({title:1})

db.books.find({title: "book-1"}).explain("executionStats")

```

## 字段名称

- winningPlan.inputStage:用来描述子stage,并且为其父stage提供文档和索引关键字

- winningPlan.inputStage.stage:子查询方式

- winningPlan.inputStage.keyPattern:所扫描的index内容

- winningPlan.inputStage.indexName:索引名

- winningPlan.inputStage.isMultiKey:是否是Multikey。如果索引建立在array上,将是true

- executionStats.executionSuccess:是否执行成功

- executionStats.nReturned:返回的个数

- executionStats.executionTimeMillis:这条语句执行时间

- executionStats.executionStages.executionTimeMillisEstimate:检索文档获取数据的时间

- executionStats.executionStages.inputStage.executionTimeMillisEstimate

扫描获取数据的时间

```javascript

executionStats.totalKeysExamined

```

索引扫描次数

```javascript

executionStats.totalDocsExamined

```

文档扫描次数

```javascript

executionStats.executionStages.isEOF

```

是否到达 steam 结尾,1 或者 true 代表已到达结尾

```javascript

executionStats.executionStages.works

```

工作单元数,一个查询会分解成小的工作单元

```javascript

executionStats.executionStages.advanced

```

优先返回的结果数

```javascript

executionStats.executionStages.docsExamined

```

allPlansExecution

```json

allPlansExecution返回的信息包含 executionStats 模式的内容,且包含allPlansExecution:[]块

{

"allPlansExecution": [

{

"nReturned": ,

"executionTimeMillisEstimate": ,

"totalKeysExamined": ,

"totalDocsExamined": ,

"executionStages": {

"stage": ,

"nReturned": ,

"executionTimeMillisEstimate": ,

...

}

},

...

]

}

```

COUNT_SCAN和SUBPLA是执行计划的返回结果中常见的stage,它们分别表示count使用了Index进行count时的stage返回和未使用到索引的$or查询的stage返回。在使用全文索引进行查询时,会返回TEXT类型的stage。此外,在限定返回字段时,也会返回PROJECTION类型的stage。

然而,在执行计划的返回结果中,应尽量避免出现以下stage:

- COLLSSCAN(全表扫描):由于全表扫描会遍历整个表,因此会影响查询性能。如果可能的话,应该尽量使用更高效的索引或优化查询条件。

- SORT(使用sort但是无index):当需要对结果进行排序时,如果存在合适的索引可以使用,应该优先选择使用索引进行排序,而不是进行全表排序。

- 不合理的SKIP:在执行查询时,如果使用了SKIP操作来跳过一些结果,但是这些结果并不会对最终的结果产生影响或者跳过的原因是基于错误的假设,那么这种做法就是不合理的。

- SUBPLA(未用到index的$or):当查询条件中包含$or操作符且没有使用合适的索引进行优化时,会导致未使用到索引的部分仍然需要进行扫描和计算,从而增加了查询时间和资源消耗。

- COUNTSCAN(不使用index进行count):与COUNT_SCAN类似,当不使用索引进行count操作时,会导致额外的计算开销和性能下降。

综上所述,为了提高查询性能和效率,应该尽可能地避免以上提到的不合理的stage的出现。