1. MongoDB体系结构

1.1 NoSQL 和 MongoDB

NoSQL=Not Only SQL,支持类似SQL的功能, 与Relational Database相辅相成。其性能较高, 不使用SQL意味着没有结构化的存储要求(SQL为结构化的查询语句),没有约束之后架构更加灵 活。
NoSQL数据库四大家族列存储 Hbase,键值(Key-Value)存储 Redis,图像存储 Neo4j,文档存储 MongoDB
MongoDB 是一个基于分布式文件存储的数据库,由 C++ 编写,可以为 WEB 应用提供可扩展、 高性能、易部署的数据存储解决方案。
MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库中功能最丰富、 最像关系数据库的。在高负载的情况下,通过添加更多的节点,可以保证服务器性能。

1.2 MongoDB 体系结构

image-20220104135858438

1.3 MongoDB 和RDBMS(关系型数据库)对比

image-20220104135919522

1.4 什么是BSON

BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON,它和JSON一样,支持内嵌的文 档对象和数组对象,但是BSON有JSON没有的一些数据类型,如Date和Binary Data类型。BSON可以 做为网络数据交换的一种存储形式,是一种schema-less的存储形式,它的优点是灵活性高,但它的缺点是空间利用率不是很理想。

{key:value,key2:value2} 这是一个BSON的例子,其中key是字符串类型,后面的value值,它的类型一般是字符串,double,Array,ISODate等类型。

BSON有三个特点:轻量性、可遍历性、高效性

1.5 BSON在MongoDB中的使用

MongoDB使用了BSON这种结构来存储数据和网络数据交换。把这种格式转化成一文档这个概念 (Document),这里的一个Document也可以理解成关系数据库中的一条记录(Record),只是这里的 Document的变化更丰富一些,如Document可以嵌套。
MongoDB中Document 中 可以出现的数据类型 。

image-20220104135947467

1.6 MongoDB在Linux的安装

1.下载社区版 MongoDB 4.1.3
去官网下载对应的MongoDB 然后上传到Linux虚拟机
2.将压缩包解压即可
tar -zxvf MongoDB-linux-x86_64-4.1.3.tgz
3.启动
./bin/mongod
4.指定配置文件方式的启动
./bin/mongod -f mongo.conf
配置文件样例:

dbpath=/data/mongo/ 
port=27017 
bind_ip=0.0.0.0 
fork=true 
logpath = /data/mongo/MongoDB.log 
logappend = true 
auth=false 

1.7 MongoDB启动和参数说明

参数 说明

  • dbpath 数据库目录,默认/data/db
  • port 监听的端口,默认27017
  • bind_ip 监听IP地址,默认全部可以访问
  • fork 是否已后台启动的方式登陆
  • logpath 日志路径
  • logappend 是否追加日志
  • auth 是开启用户密码登陆
  • config 指定配置文件

1.8 mongo shell 的启动

启动mongo shell
./bin/mongo
指定主机和端口的方式启动
./bin/mongo --host=主机IP --port=端口

1.9 Mongodb GUI工具

1.9.1 MongoDB Compass Community

MongoDB Compass Community由MongoDB开发人员开发,这意味着更高的可靠性和兼容性。它为MongoDB提供GUI mongodb工具,以探索数据库交互,具有完整的CRUD功能并提供可视方式。借助内置模式可视化,用户可以分析文档并显示丰富的结构。为了监控服务器的负载,它提供了数据库操作的实时统计信息。就像MongoDB一样,Compass也有两个版本,一个是Enterprise(付费),社区可以免费使用。适用于Linux,Mac或Windows。

1.9.2 NoSQLBooster(mongobooster)

NoSQLBooster是MongoDB CLI界面中非常流行的GUI工具。它正式名称为MongoBooster。NoSQLBooster是一个跨平台,它带有一堆mongodb工具来管理数据库和监控服务器。这个Mongodb 工具包括服务器监控工具,Visual Explain Plan,查询构建器,SQL查询,ES2017语法支持等等……它有免费,个人和商业版本,当然,免费版本有一些功能限制。NoSQLBooster也可用于Windows,MacOS和Linux。

2. MongoDB命令

2.1 MongoDB的基本操作

查看数据库 
show dbs; 
切换数据库 如果没有对应的数据库则创建 
use 数据库名;

创建集合 
db.createCollection("集合名") 

查看集合
show tables;
show collections; 

删除集合
db.集合名.drop();

删除当前数据库 
db.dropDatabase();

2.2 MongoDB集合数据操作(CURD)

2.2.1 数据添加

  1. 插入单条数据 db.集合名.insert(文档)
    文档的数据结构和JSON基本一样。
    所有存储在集合中的数据都是BSON格式。
    BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON。
  1. 例如:
    db.lg_resume_preview.insert({name:”张晓峰”,birthday:new ISODate(“2000-07-01”),expectSalary:15000,gender:0,city:”bj”})
    没有指定 _id 这个字段 系统会自动生成当然我们也可以指定 _id ( _id 类型是ObjectId 类型是一个12字节 BSON 类型数据,有以下格式:
    前4个字节表示时间戳 ObjectId(“对象Id字符串”).getTimestamp() 来获取
    接下来的3个字节是机器标识码
    紧接的两个字节由进程id组成(PID)
    最后三个字节是随机数。)
  1. 插入多条数据
    db.集合名.insert([文档,文档])

2.2.2 数据查询

比较条件查询
db.集合名.fifind(条件)

image-20220104140206368

逻辑条件查询

and 条件
MongoDB 的 find() 方法可以传入多个键(key),每个键(key)以逗号隔开,即常规 SQL 的 AND 条件 
   db.集合名.find({key1:value1, key2:value2}).pretty() 
or 条件
  db.集合名.find({$or:[{key1:value1}, {key2:value2}]}).pretty() 
not 条件
   db.集合名.find({key:{$not:{$操作符:value}}).pretty()

分页查询
db.集合名.fifind({条件}).sort({排序字段:排序方式})).skip(跳过的行数).limit(一页显示多少数据)

2.2.3 数据更新

调用update

$set :设置字段值
$unset :删除指定字段
$inc:对修改的值进行自增
db.集合名.update( 
  <query>,
  <update>, 
   { 
    upsert: <boolean>,
    multi: <boolean>,
    writeConcern: <document> 
  } 
)

参数说明:

  • query : update的查询条件,类似sql update查询内where后面的。
  • update : update的对象和一些更新的操作符(如$set,$inc…)等,也可以理解为sql update中 set后面的
  • upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认 是false,不插入。
  • multi : 可选,MongoDB 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查 出来多条记录全部更新。
  • writeConcern :可选,用来指定mongod对写操作的回执行为比如写的行为是否需要确认。

举例:
db.集合名.update({条件},{$set:{字段名:值}},{multi:true})

writeConcern 包括以下字段:
{ w: <value>, j: <boolean>, wtimeout: <number> }
w:指定写操作传播到的成员数量 。

比如:
w=1(默认):则要求得到写操作已经传播到独立的Mongod实例或副本集的primary成员的确认
w=0:则不要求确认写操作,可能会返回socket exceptions和 networking errors
w=”majority”:要求得到写操作已经传播到大多数具有存储数据具有投票的(data-bearing voting )成员(也就是 members[n].votes 值大于0的成员)的确认 。
j:要求得到Mongodb的写操作已经写到硬盘日志的确认

比如:
j=true:要求得到Mongodb(w指定的实例个数)的写操作已经写到硬盘日志的确认。j=true本身并不保证 因为副本集故障而不会回滚。
wtimeout:指定write concern的时间限制,只适用于w>1的情况 wtimeout在超过指定时间后写操作会返回error,即使写操作最后执行成功,当这些写操作返回时, MongoDB不会撤消在wtimeout时间限制之前执行成功的数据修改。
如果未指定wtimeout选项且未指定write concern级别,则写入操作将无限期阻止。 指定wtimeout值 为0等同于没有wtimeout选项。

2.2.4 数据删除

db.collection.remove( 
  <query>, 
  { justOne: <boolean>, writeConcern: <document> } 
)

参数说明:
query :(可选)删除的文档的条件。
justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。 
writeConcern :(可选)用来指定mongod对写操作的回执行为。

2.3 MongoDB 聚合操作

2.3.1 聚合操作简介

聚合是MongoDB的高级查询语言,它允许我们通过转化合并由多个文档的数据来生成新的在单个文档 里不存在的文档信息。一般都是将记录按条件分组之后进行一系列求最大值,最小值,平均值的简单操作,也可以对记录进行复杂数据统计,数据挖掘的操作。聚合操作的输入是集中的文档,输出可以是一个文档也可以是多个文档。

2.3.2 MongoDB 聚合操作分类

  • 单目的聚合操作(Single Purpose Aggregation Operation)
  • 聚合管道(Aggregation Pipeline)
  • MapReduce 编程模型

2.3.3 单目的聚合操作

单目的聚合命令常用的有:count() 和 distinct()

db.lg_resume_preview.find({}).count()

2.3.4 聚合管道(Aggregation Pipeline)

db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION) 
如: 
db.lg_resume_preview.aggregate([{$group:{_id:"$city",city_count:{$sum:1}}}])

MongoDB中聚合(aggregate)主要用于统计数据(诸如统计平均值,求和等),并返回计算后的数据结果。
表达式:处理输入文档并输出。表达式只能用于计算当前聚合管道的文档,不能处理其它的文档。

image-20220104140419937

MongoDB 中使用 db.COLLECTION_NAME.aggregate([{},…]) 方法来构建和使用聚合管道,每个文档通过一个由一个或者多个阶段(stage)组成的管道,经过一系列的处理,输出相应的结果。
MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。
这里我们介绍一下聚合框架中常用的几个操作:

  • $group:将集合中的文档分组,可用于统计结果。
  • $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及 嵌套文档。
  • $match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。
  • $limit:用来限制MongoDB聚合管道返回的文档数。
  • $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
  • $sort:将输入文档排序后输出。
  • $geoNear:输出接近某一地理位置的有序文档。
db.lg_resume_preview.aggregate([{$group : {_id: "$city", avgSal:{$avg:"$expectSalary"}}}, {$project : {city: "$city", salary : "$avgSal"}}])
db.lg_resume_preview.aggregate([{$group:{_id: "$city",count:{$sum : 1}}}, {$match:{count:{$gt:1}}}])

2.3.5 MapReduce 编程模型

Pipeline查询速度快于MapReduce,但是MapReduce的强大之处在于能够在多台Server上并行执行复杂的聚合逻辑。MongoDB不允许Pipeline的单个聚合操作占用过多的系统内存,如果一个聚合操作消耗20%以上的内存,那么MongoDB直接停止操作,并向客户端输出错误消息。
MapReduce是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执行,然后再将结果合并成最终结果(REDUCE)。

>db.collection.mapReduce(
  function() {emit(key,value);}, //map 函数
  function(key,values) {return reduceFunction}, //reduce 函数
  { 
    out: collection, 
    query: document,
    sort: document, 
    limit: number,
    finalize: <function>, verbose: <boolean> 
  } 
)

使用 MapReduce 要实现两个函数 Map 函数和 Reduce 函数,Map 函数调用 emit(key, value), 遍历collection 中所有的记录, 将 key 与 value 传递给 Reduce 函数进行处理。
参数说明:

  • map:是JavaScript 函数,负责将每一个输入文档转换为零或多个文档,生成键值对序列,作为 reduce 函数参数
  • reduce:是JavaScript 函数,对map操作的输出做合并的化简的操作(将key-value变成key-values,也就是把values数组变成一个单一的值value)
  • out:统计结果存放集合
  • query: 一个筛选条件,只有满足条件的文档才会调用map函数。
  • sort: 和limit结合的sort排序参数(也是在发往map函数前给文档排序),可以优化分组机制
  • limit: 发往map函数的文档数量的上限(要是没有limit,单独使用sort的用处不大)
  • finalize:可以对reduce输出结果再一次修改
  • verbose:是否包括结果信息中的时间信息,默认为fasle
    db.lg_resume_preview.mapReduce( 
      function() { emit(this.city,this.expectSalary); }, 
      function(key, value) {return Array.avg(value)},
      { 
        query:{expectSalary:{$gt: 15000}}, 
        out:"cityAvgSal" 
      } 
    )

    3. MongoDB索引Index

    3.1 什么是索引

    索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。索引目标是提高数据库的查询效率,没有索引的话,查询会进行全表扫描(scan every document in a collection),数据量大时严重降低了查询效率。默认情况下Mongo在一个集合(collection)创建时,自动地对集合的_id创建了唯一索引。

3.2 索引类型

3.2.1 单键索引 (Single Field)

MongoDB支持所有数据类型中的单个字段索引,并且可以在文档的任何字段上定义。
对于单个字段索引,索引键的排序顺序无关紧要,因为MongoDB可以在任一方向读取索引。
单个例上创建索引:
db.集合名.createIndex({“字段名”:排序方式})
特殊的单键索引 过期索引 TTL ( Time To Live)
TTL索引是MongoDB中一种特殊的索引, 可以支持文档在一定时间之后自动过期删除,目前TTL索引只能在单字段上建立,并且字段类型必须是日期类型。

db.集合名.createIndex({"日期字段":排序方式}, {expireAfterSeconds: 秒数})

3.2.2 复合索引(Compound Index)

通常我们需要在多个字段的基础上搜索表/集合,这是非常频繁的。 如果是这种情况,我们可能会考虑在MongoDB中制作复合索引。 复合索引支持基于多个字段的索引,这扩展了索引的概念并将它们扩展到索引中的更大域。
制作复合索引时要注意的重要事项包括:字段顺序与索引方向。

db.集合名.createIndex( { "字段名1" : 排序方式, "字段名2" : 排序方式 } )

3.2.3 多键索引(Multikey indexes)

针对属性包含数组数据的情况,MongoDB支持针对数组中每一个element创建索引,Multikey indexes支持strings,numbers和nested documents

3.2.4 地理空间索引(Geospatial Index)

针对地理空间坐标数据创建索引。
2dsphere索引,用于存储和查找球面上的点
2d索引,用于存储和查找平面上的点

db.company.insert( 
  { 
    loc : { type: "Point", coordinates: [ 116.482451, 39.914176 ] },
    name: "大望路地铁", category : "Parks" 
  } 
)

db.company.ensureIndex( { loc : "2dsphere" } ) 参数不是1-1,为2dsphere 或者 2d。还可以建立组合索引。
db.company.find({ 
  "loc" : {
    "$geoWithin" : {
      "$center":[[116.482451,39.914176],0.05]
    } 
  }
})

/** 计算中心点最近的三个点 */
db.company.aggregate([
   {
     $geoNear: {
        near: { type: "Point", coordinates: [116.482451,39.914176 ] },
        key: "loc",
        distanceField: "dist.calculated"
     }
   },
   { $limit: 3 }
])




/** 2d 测试  */
db.places.drop()
db.places.insert({"name": "Temple1","tile": [32, 22]})
db.places.insert({"name": "Temple2","tile": [30, 22]})
db.places.insert({"name": "Temple3","tile": [28, 21]})
db.places.insert({"name": "Temple4","tile": [34, 27]})
db.places.insert({"name": "Temple5","tile": [34, 26]})
db.places.insert({"name": "Temple6","tile": [39, 28]})

db.places.find({})
 
db.places.ensureIndex({"tile" : "2d"}, {"min" : -90, "max" : 90, "bits" : 20}) 
 
db.places.find({"tile": {"$within": {"$box": [[0, 0], [30, 30]]}}})

3.2.5 全文索引

MongoDB提供了针对string内容的文本查询,Text Index支持任意属性值为string或string数组元素的索引查询。注意:一个集合仅支持最多一个Text Index,中文分词不理想 推荐ES。

  • db.集合.createIndex({“字段”: “text”})
  • db.集合.find({“$text”: {“$search”: “coffee”}})

3.2.6 哈希索引 Hashed Index

针对属性的哈希值进行索引查询,当要使用Hashed index时,MongoDB能够自动的计算hash值,无需程序计算hash值。注:hash index仅支持等于查询,不支持范围查询。

db.集合.createIndex({"字段": "hashed"})

3.3 索引和explain 分析

3.3.1 索引管理

创建索引并在后台运行

db.COLLECTION_NAME.createIndex({"字段":排序方式}, {background: true});

获取针对某个集合的索引
db.COLLECTION_NAME.getIndexes()

索引的大小
db.COLLECTION_NAME.totalIndexSize()

索引的重建
db.COLLECTION_NAME.reIndex()

索引的删除
db.COLLECTION_NAME.dropIndex("INDEX-NAME") 
db.COLLECTION_NAME.dropIndexes() 
注意: _id 对应的索引是删除不了的

3.3.2 explain 分析

使用js循环 插入100万条数据 不使用索引字段 查询查看执行计划 ,然后给某个字段建立索引,使用索引字段作为查询条件再查看执行计划进行分析

explain()也接收不同的参数,通过设置不同参数我们可以查看更详细的查询计划。

  • queryPlanner:queryPlanner是默认参数,具体执行计划信息参考下面的表格。
  • executionStats:executionStats会返回执行计划的一些统计信息(有些版本中和 allPlansExecution等同)。
  • allPlansExecution:allPlansExecution用来获取所有执行计划,结果参数基本与上文相同。

1、queryPlanner 默认参数

image-20220104140534135

2、executionStats参数

image-20220104140600038

3、executionStats返回逐层分析
第一层,executionTimeMillis最为直观explain返回值是executionTimeMillis值,指的是这条语句的执行时间,这个值当然是希望越少越好。
其中有3个executionTimeMillis,分别是:

  • executionStats.executionTimeMillis 该query的整体查询时间。
  • executionStats.executionStages.executionTimeMillisEstimate 该查询检索document获得数据的时间。
  • executionStats.executionStages.inputStage.executionTimeMillisEstimate 该查询扫描文档 index所用时间。

第二层,index与document扫描数与查询返回条目数 这个主要讨论3个返回项 nReturned、 totalKeysExamined、totalDocsExamined,分别代表该条查询返回的条目、索引扫描条目、文档扫描 条目。 这些都是直观地影响到executionTimeMillis,我们需要扫描的越少速度越快。 对于一个查询,

我们最理想的状态是:nReturned=totalKeysExamined=totalDocsExamined

第三层,stage状态分析 那么又是什么影响到了totalKeysExamined和totalDocsExamined?是stage的类型。
类型列举如下:

  • COLLSCAN:全表扫描
  • IXSCAN:索引扫描
  • FETCH:根据索引去检索指定document
  • SHARD_MERGE:将各个分片返回数据进行merge
  • SORT:表明在内存中进行了排序
  • LIMIT:使用limit限制返回数
  • SKIP:使用skip进行跳过
  • IDHACK:针对_id进行查询
  • SHARDING_FILTER:通过mongos对分片数据进行查询
  • COUNT:利用db.coll.explain().count()之类进行count运算
  • TEXT:使用全文索引进行查询时候的stage返回
  • PROJECTION:限定返回字段时候stage的返回

对于普通查询,我希望看到stage的组合(查询的时候尽可能用上索引):

  • Fetch+IDHACK
  • Fetch+IXSCAN
  • Limit+(Fetch+IXSCAN)
  • PROJECTION+IXSCAN
  • SHARDING_FITER+IXSCAN
  • 不希望看到包含如下的stage:
  • COLLSCAN(全表扫描)
  • SORT(使用sort但是无index)
  • COUNT 不使用index进行count)

全表扫描:db.lg_resume.find({name:"test11011"}).explain("executionStats"):

image.png

索引检索:
db.lg_resume.createIndex({id:1})
db.lg_resume.find({id:{$gt:678909}}).explain("executionStats")

image.png

4、allPlansExecution参数

queryPlanner 参数和executionStats的拼接

3.4 慢查询分析

1.开启内置的查询分析器,记录读写操作效率
db.setProfifilingLevel(n,m),n的取值可选0,1,2
0表示不记录
1表示记录慢速操作,如果值为1,m必须赋值单位为ms,用于定义慢速查询时间的阈值
2表示记录所有的读写操作
2.查询监控结果
db.system.profifile.fifind().sort({millis:-1}).limit(3)
3.分析慢速查询
应用程序设计不合理、不正确的数据模型、硬件配置问题,缺少索引等
4.解读explain结果 确定是否缺少索引
image.png

3.5 MongoDB 索引底层实现原理分析

MongoDB 是文档型的数据库,它使用BSON 格式保存数据,比关系型数据库存储更方便。比如之前关系型数据库中处理用户、订单等数据要建立对应的表,还要建立它们之间的关联关系。但是BSON就不一样了,我们可以把一条数据和这条数据对应的数据都存入一个BSON对象中,这种形式更简单,通俗易懂。MySql是关系型数据库,数据的关联性是非常强的,区间访问是常见的一种情况,底层索引组织数据使用B+树,B+树由于数据全部存储在叶子节点,并且通过指针串在一起,这样就很容易的进行区间遍历甚至全部遍历。MongoDB使用B-树,所有节点都有Data域,只要找到指定索引就可以进行访问,单次查询从结构上来看要快于MySql。
B-树是一种自平衡的搜索树,形式很简单:

image-20220104141132846

B-树的特点:
(1) 多路 非二叉树 。
(2) 每个节点 既保存数据 又保存索引 。
(3) 搜索时 相当于二分查找 。
B+树是B-树的变种 。

image-20220104141158914

B+ 树的特点:
(1) 多路非二叉 。
(2) 只有叶子节点保存数据 。
(3) 搜索时 也相当于二分查找 。
(4) 增加了 相邻节点指针。
从上面我们可以看出最核心的区别主要有俩,一个是数据的保存位置,一个是相邻节点的指向。就是这俩造成了MongoDB和MySql的差别。
(1)B+树相邻接点的指针可以大大增加区间访问性,可使用在范围查询等,而B-树每个节点 key 和 data 在一起 适合随机读写 ,而区间查找效率很差。
(2)B+树更适合外部存储,也就是磁盘存储,使用B-结构的话,每次磁盘预读中的很多数据是用不上的数据。因此,它没能利用好磁盘预读的提供的数据。由于节点内无 data 域,每个节点能索引的范围更大更精确。
(3)注意这个区别相当重要,是基于(1)(2)的,B-树每个节点即保存数据又保存索引 树的深度小,所以磁盘IO的次数很少,B+树只有叶子节点保存,较B树而言深度大磁盘IO多,但是区间访问比较好。

4. MongoDB 应用实战

4.1 MongoDB的适用场景

  • 网站数据:Mongo 非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。
  • 缓存:由于性能很高,Mongo 也适合作为信息基础设施的缓存层。在系统重启之后,由Mongo搭建的持久化缓存层可以避免下层的数据源过载。
  • 大尺寸、低价值的数据:使用传统的关系型数据库存储一些大尺寸低价值数据时会比较浪费,在此之前,很多时候程序员往往会选择传统的文件进行存储。
  • 高伸缩性的场景:Mongo 非常适合由数十或数百台服务器组成的数据库,Mongo 的路线图中已经包含对MapReduce 引擎的内置支持以及集群高可用的解决方案。
  • 用于对象及JSON 数据的存储:Mongo 的BSON 数据格式非常适合文档化格式的存储及查询。

4.2 MongoDB的行业具体应用场景

  • 游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、更新。
  • 物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来。
  • 社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能。
  • 物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析。
  • 直播,使用 MongoDB 存储用户信息、礼物信息等。

4.3 如何抉择是否使用MongoDB

image.png

4.4 Java 访问MongoDB

maven 依赖

 <dependency>
      <groupId>org.mongodb</groupId>
      <artifactId>mongo-java-driver</artifactId>
      <version>3.10.1</version>
</dependency>

文档添加
package org.example;


import com.mongodb.MongoClient;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import org.bson.Document;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        MongoClient mongoClient = new MongoClient("106.75.226.220", 28017);
        final MongoDatabase mongoDatabase = mongoClient.getDatabase("lg_resume");
        final MongoCollection<Document> collection = mongoDatabase.getCollection("lg_resume");
        final Document document = Document.parse("{id:9999999,name:'text1',salary:99999}");
        //collection.insertOne(document);

        Document sdoc=new Document();
        //按expectSalary倒序
        sdoc.append("salary", -1);
        final FindIterable<Document> documents = collection.find(Document.parse("{id:{$gt:999000}}")).sort(sdoc);
        FindIterable<Document> findIterable = collection.find(Filters.gt("salary",21000)).sort(sdoc);
        for (Document doc : documents){
            System.out.println(doc);
        }

        mongoClient.close();
        System.out.println( "Hello World!" );
    }
}

4.5 Spring 访问MongoDB

第1步:基于maven新建工程导入依赖的包

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>2.0.9.RELEASE</version> 
</dependency>

第2步:在配置文件中配置 MongoTemplate
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mongo="http://www.springframework.org/schema/data/mongo"
       xsi:schemaLocation=" http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/data/mongo
        http://www.springframework.org/schema/data/mongo/spring-mongo.xsd">

    <mongo:db-factory id="mongoDbFactory" client-uri="mongodb://106.75.226.220:28017/lg_resume"/>

    <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
        <constructor-arg index="0" ref="mongoDbFactory"/>
    </bean>
    <context:component-scan base-package="org.example.mongodb"/>
</beans>

第3步:DAO 实现类注入 MongoTemplate 完成增删改查
package org.example.mongodb.bean;

import java.util.Date;

/**
 * @author :xjlonly
 * @date :Created in 2021/5/13 15:20
 * @description:
 * @modified By:
 * @version: $
 */
public class Resume {
    private int id;
    private String name;
    private double salary;
    private Date birthday;

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Resume{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", salary=" + salary +
                ", birthday=" + birthday +
                '}';
    }
}

package org.example.mongodb.dao.impl;

import org.example.mongodb.bean.Resume;
import org.example.mongodb.dao.ResumeDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Repository;

/**
 * @author :xjlonly
 * @date :Created in 2021/5/13 15:23
 * @description:
 * @modified By:
 * @version: $
 */
@Repository(value = "resumeDao")
public class ResumeDaoImpl implements ResumeDao {

    @Autowired
    private MongoTemplate  mongoTemplate;

    @Override
    public void insertResume(Resume resume) {
        //不指定collection时  默认为对象名首字母小写
        //mongoTemplate.insert(resume);
        //指定collection
        mongoTemplate.insert(resume,"lg_resume_datas");
    }
    
    @Override
    public List<Resume> findByResume(String name) {
        Query query = new Query();
        query.addCriteria(Criteria.where("name").is(name));
        return mongoTemplate.find(query,Resume.class, "lg_resume_datas");
    }
    
    @Override
    public List<Resume> findByNameAndSalary(String name, double salary) {
        Query query  =new Query();
        query.addCriteria(Criteria.where("name").is(name).andOperator(Criteria.where("salary").is(salary)));
        return mongoTemplate.find(query, Resume.class,"lg_resume_datas");

    }
}

第4步: 从Spring容器中获取DAO对象 进行测试 (注意:要开启组件扫描)

package org.example.mongodb;

import org.example.mongodb.bean.Resume;
import org.example.mongodb.dao.ResumeDao;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.format.datetime.DateFormatter;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        ClassPathXmlApplicationContext pathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        final ResumeDao resumeDao = pathXmlApplicationContext.getBean("resumeDao", ResumeDao.class);
        Resume resume = new Resume();
        resume.setName("zhangsan");
        resume.setSalary(895555);
        //日期格式
        Date date = null;
        SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        try {
            date =  dateFormatter.parse("2001-2-12 13:12:23");
        } catch (ParseException e) {
            e.printStackTrace();
        }
        resume.setBirthday(date);
        resumeDao.insertResume(resume);
        System.out.println( "Hello World!" );
    }
}

4.6 Spring Boot 访问 MongoDB

4.6.1 MongoTemplate 的方式

第1步:基于maven新建springboot工程

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mongodb-example</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mongo-spring-boot-demo</artifactId>

    <name>mongo-spring-boot-demo</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency> <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.1.0</version>
                </plugin>
                <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.22.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
                <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
                <plugin>
                    <artifactId>maven-site-plugin</artifactId>
                    <version>3.7.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-project-info-reports-plugin</artifactId>
                    <version>3.0.0</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

第2步: 配置文件application.properties
spring.data.mongodb.host=106.75.226.220
spring.data.mongodb.port=28017
spring.data.mongodb.database=lg_resume

第3步: DAO 实现类 注入 MongoTemplate 完成增删改查
package org.example.mongodb.dao.impl;

import org.example.mongodb.bean.Resume;
import org.example.mongodb.dao.ResumeDao;
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 org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author :xjlonly
 * @date :Created in 2021/5/13 15:23
 * @description:
 * @modified By:
 * @version: $
 */
@Repository(value = "resumeDao")
public class ResumeDaoImpl implements ResumeDao {

    @Autowired
    private MongoTemplate  mongoTemplate;

    @Override
    public void insertResume(Resume resume) {
        //不指定collection时  默认为对象名首字母小写
        //mongoTemplate.insert(resume);
        //指定collection
        mongoTemplate.insert(resume,"lg_resume_datas");
    }

    @Override
    public List<Resume> findByName(String name) {
        Query query = new Query();
        query.addCriteria(Criteria.where("name").is(name));
        return mongoTemplate.find(query,Resume.class, "lg_resume_datas");
    }

    @Override
    public List<Resume> findByNameAndSalary(String name, double salary) {
        Query query  =new Query();
        query.addCriteria(Criteria.where("name").is(name).andOperator(Criteria.where("salary").is(salary)));
        return mongoTemplate.find(query, Resume.class,"lg_resume_datas");

    }
}

第4步: 从Spring容器中获取DAO对象 进行测试
package org.example.mongodb;

import org.example.mongodb.bean.Resume;
import org.example.mongodb.dao.ResumeDao;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Hello world!
 *
 */
@SpringBootApplication
public class App 
{
    public static void main( String[] args )
    {
        ApplicationContext applicationContext = SpringApplication.run(App.class,args);
        final ResumeDao resumeDao = applicationContext.getBean("resumeDao", ResumeDao.class);
        Resume resume = new Resume();
        resume.setId(199233);
        //多长插入数据 不想重复 可以设置字段索引唯一性约束
        resume.setName("zhangsan");
        resume.setSalary(895555);
        //日期格式
        Date date = null;
        SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        try {
            date =  dateFormatter.parse("2001-2-12 13:12:23");
        } catch (ParseException e) {
            e.printStackTrace();
        }
        resume.setBirthday(date);
        resumeDao.insertResume(resume);
        System.out.println( "Hello World!" );
    }
}

4.6.2 MongoRepository 的方式
第1步:基于maven新建springboot工程

第2步: 配置文件application.properties 以上两步同上小节

第3步:编写实体类并在实体类上打@Document(“集合名”)

package org.example.mongodb.bean;

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

import java.util.Date;

/**
 * @author :xjlonly
 * @date :Created in 2021/5/13 15:20
 * @description:
 * @modified By:
 * @version: $
 */
@Document("lg_resume_datas")
public class Resume {
    private int id;
    private String name;
    private double salary;
    private Date birthday;

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Resume{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", salary=" + salary +
                ", birthday=" + birthday +
                '}';
    }
}

第4步:编写 Repository 接口 继承 MongoRepository
方法具体参考:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation
如果内置方法不够用 就自己定义 如:定义find|read|get 等开头的方法进行查询

package org.example.mongodb.dao;

import org.example.mongodb.bean.Resume;
import org.springframework.data.mongodb.repository.MongoRepository;

import java.util.List;

/**
 * @author :xjlonly
 * @date :Created in 2021/5/13 15:23
 * @description:
 * @modified By:
 * @version: $
 */
public interface ResumeRepository extends MongoRepository<Resume, Integer> {
}

第5步: 从Spring容器中获取Repository对象 进行测试
package org.example.mongodb;

import org.example.mongodb.bean.Resume;
import org.example.mongodb.dao.ResumeRepository;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

/**
 * Hello world!
 *
 */
@SpringBootApplication
public class App
{
    public static void main( String[] args )
    {
        ApplicationContext applicationContext = SpringApplication.run(App.class,args);
        final ResumeRepository resumeRepository = applicationContext.getBean(ResumeRepository.class);
        final List<Resume> all = resumeRepository.findAll();
        all.forEach(x->{
            System.out.println(x.toString());
        });
    }
}