MongoDB简单学习

MongoDB

一、基本使用

1.1业务应用场景

传统的关系型数据库(如Mysql),在数据库操作的“三高”需求以及对应web2.0的网站需求面前,显得力不从心

三高:

  • High performance - 对数据库高并发读写的要求
  • Huge Storage - 对海量数据的高效率存储和访问的需求
  • High Scalability && High Avaliablity 对数据库的高可扩展性和高可用性的需求

具体的应用场景如:

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

这些应用场景中,数据操作方面的共同特点是:

  1. 数据量大
  2. 写入操作频繁(读写都很频繁)
  3. 价值较低的数据,对事务性要求不高

对于这样的数据,我们更适合使用MongoDB来实现数据的存储。什么时候选择MongoDB ?
在架构选型上,除了上述的三个特点外,如果你还犹豫是否要选择它?可以考虑以下的一些问题:应用不需要事务复杂join支持
新应用,需求会变,数据模型无法确定,想快速迭代开发应用需要2000-3000以上的读写QPS(更高也可以)
应用需要TB甚至PB级别数据存储

那么我们什么时候选择 MongoDB 呢?

除了架构选型上, 除了上述三个特点之外, 还要考虑下面这些问题:

  • 应用不需要事务及复杂 JOIN 支持
  • 新应用, 需求会变, 数据模型无法确定, 想快速迭代开发
  • 应用需要 2000 - 3000 以上的读写QPS(更高也可以)
  • 应用需要 TB 甚至 PB 级别数据存储
  • 应用发展迅速, 需要能快速水平扩展
  • 应用要求存储的数据不丢失
  • 应用需要 99.999% 高可用
  • 应用需要大量的地理位置查询, 文本查询

如果上述有1个符合, 可以考虑 MongoDB, 2个及以上的符合, 选择 MongoDB 绝不会后悔.

如果用MySQL呢?

相对MySQL, 可以以更低的成本解决问题(包括学习, 开发, 运维等成本)

1.2 MongoDB 简介

MongoDB是一个开源, 高性能, 无模式的文档型数据库, 当初的设计就是用于简化开发和方便扩展, 是NoSQL数据库产品中的一种.是最 像关系型数据库(MySQL)的非关系型数据库. 它支持的数据结构非常松散, 是一种类似于 JSON 的 格式叫BSON, 所以它既可以存储比较复杂的数据类型, 又相当的灵活. MongoDB中的记录是一个文档, 它是一个由字段和值对(field:value)组成的数据结构.MongoDB文档类似于JSON对象, 即一个文档认 为就是一个对象.字段的数据类型是字符型, 它的值除了使用基本的一些类型外, 还可以包括其他文档, 普通数组和文档数组.

“最像关系型数据库的 NoSQL 数据库”. MongoDB 中的记录是一个文档, 是一个 key-value pair. 字段的数据类型是字符型, 值除了使用基本的一些类型以外, 还包括其它文档, 普通数组以及文档数组

img

img

MongoDB 数据模型是面向文档的, 所谓文档就是一种类似于 JSON 的结构, 简单理解 MongoDB 这个数据库中存在的是各种各样的 JSON(BSON)

  • 数据库 (database)
    • 数据库是一个仓库, 存储集合 (collection)
  • 集合 (collection)
    • 类似于数组, 在集合中存放文档
  • 文档 (document)
    • 文档型数据库的最小单位, 通常情况, 我们存储和操作的内容都是文档

在 MongoDB 中, 数据库和集合都不需要手动创建, 当我们创建文档时, 如果文档所在的集合或者数据库不存在, 则会自动创建数据库或者集合

1.3Windows下载

https://www.mongodb.com/try/download/community

1.4Linux下载

https://www.mongodb.com/try/download/community

1.5图形化界面

https://www.mongodb.com/try/download/compass

二、单机部署

三、基本使用

3.1案例需求

3.2数据库操作

  • 设置MongoDB账号密码,防止被删库勒索
  1. 进入到mongodb服务器下
use admindb.createUser({user: '用户名',    // 用户名(自定义)pwd: '密码',  // 密码(自定义)roles:[{role: 'root',   // 使用超级用户角色db: 'admin'     // 指定数据库}]
})
  1. 找到配置的mongodb.conf文件,添加以下信息
security:authorization: enabled
  1. 重启mongodb服务
  2. 使用mongodb连接数据库,并使用超级管理员账号

在没有认证之前,也可以使用mongo链接数据库,但是不能执行其他命令

以下是两种使用超级管理员账号登录数据库的方式

// 方式一
mongo
use admin
db.auth('用户名', '密码')// 方式二
mongo admin -u 用户名 -p 密码
3.2.1权限说明(基于角色的权限控制)
3.2.1.1 内置角色
数据库用户角色
  • read: 只读数据权限
  • readWrite:写数据权限
数据库管理角色
  • dbAdmin: 在当前db中执行管理操作的权限
  • dbOwner: 在当前db中执行任意操作
  • userADmin: 在当前db中管理user的权限
备份和还原角色
  • backup
  • restore
夸库角色
  • readAnyDatabase: 在所有数据库上都有读取数据的权限
  • readWriteAnyDatabase: 在所有数据库上都有读写数据的权限
  • userAdminAnyDatabase: 在所有数据库上都有管理user的权限
  • dbAdminAnyDatabase: 管理所有数据库的权限
集群管理
  • clusterAdmin: 管理机器的最高权限
  • clusterManager: 管理和监控集群的权限
  • clusterMonitor: 监控集群的权限
  • hostManager: 管理Server
超级权限
  • root: 超级用户

  • 显示数据库

show dbs
3.2.1选择和创建数据库

选择和创建数据库

use 数据库名称

如果数据库不存在将会自动创建,比如将创建spitdb数据库

use spitdb

查看当前数据库

db

默认的三个数据库

  • admin:从权限的角度看,这是root数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库权限。一些特定的服务器端命令也只能通过这个数据库运行,比如列出所有的数据库或者关闭服务器
  • local:这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合
  • config:当Mongo分片设置时,config数据库在内部使用,用于保存分片的相关信息
3.2.2数据库的删除
db.dropDatabase()

3.3集合操作

3.2.1集合的显示创建
db.createCollection(name) #name必须要由 "" 包围

查看当前库中的表

show tables
3.2.2集合的隐式创建

当向有一个集合中插入一个文档的时候,如果集合不存在,将会自动创建集合

3.2.3集合的删除
db.collection.drop()
或
db.集合name.drop()

返回值

如果成功删除选定的集合,则drop()方法返回true,否则返回false

例如:要删除mycollection集合

db.mycollection.drop()

3.4文档基本CRUD

文档(document)的数据结构和JSON基本一样

所有存储在集合中的数据都是JSON格式

3.4.1文档的插入
  1. 单个文档插入

使用insert() 或 save()方法向集合中插入文档,语法:

db.collection.insert(<document or array of documents>,{writeConcern: <document>,ordered: <boolean>}
)

参数

ParamterType描述
documentdocument or array要插入到集合内的文档或文档数组(JSON格式)
writeConcerndocument
orderdboolean可选。true代表按顺序插入数组中的文档,如果其中一个文档出现错误,MongoDB将返回而不处理数组中的其他文档;如果为false,则执行顺序插入,如果其中一个文档出现错误,则继续处理数组中的主文档。在2.6+版本,默认为true

示例

db.comment.insert({"articleid":"100000","content":"今天天气真好","userId":"1001","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null}
)

提示

  • comment如果不存在,将会被隐式创建
  • mongodb中的数字,默认情况下时double类型,如果要存入整形,必须使用函数Number(整型数字),否则取出来会有问题
  • 插入当前日期使用new Date()
  • 插入的数据没有指定_id,会自动生成主键值
  • 如果某字段没值,可以赋值为null,或不写

插入成功后的返回值

WriteResult({ "nInserted" : 1 })
  1. 多个文档插入
db.collection.insert([<document or array of documents>,{writeConcern: <document>,ordered: <boolean>},{writeConcern: <document>,ordered: <boolean>},{writeConcern: <document>,ordered: <boolean>},....])
db.comment.insert([{"articleid":"100001","content":"今天天气真好","userId":"1001","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null},{"articleid":"100002","content":"今天天气真好","userId":"1002","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null},{"articleid":"100003","content":"今天天气真好","userId":"1003","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null},{"articleid":"100004","content":"今天天气真好","userId":"1004","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null}
]);

如果担心多个数据插入查询失败,可以加上try-catch

try{db.comment.insert([{"articleid":"100001","content":"今天天气真好","userId":"1001","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null},{"articleid":"100002","content":"今天天气真好","userId":"1002","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null},{"articleid":"100003","content":"今天天气真好","userId":"1003","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null},{"articleid":"100004","content":"今天天气真好","userId":"1004","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null}]);
}catch(e){print(e);
}
3.4.2文档的查询
db.collection.find(<query>,[projection])

参数:

ParameterTypeDescription
querydocument可选,使用查询运算符指定选择筛选器。若要返回集合中的所有文档,请省略此参数或传递空文档
projectiondocument可选。指定要在于查询结果筛选器匹配的文档中返回的字段(投影)。若要返回匹配文档中的所有字段,请省略此参数

实例

db.comment.find()
或
db.comment.find({})#查询所有
db.comment.find(){ "_id" : ObjectId("6312fdfc778ba7b2fb5d7bd6"), "articleid" : "100000", "content" : "今天天气真好", "userId" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2024-09-03T07:10:52.544Z"), "likenum" : 10, "state" : null }#查询指定内容
db.comment.find({userId:"1001"}){ "_id" : ObjectId("6312fdfc778ba7b2fb5d7bd6"), "articleid" : "100000", "content" : "今天天气真好", "userId" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2024-09-03T07:10:52.544Z"), "likenum" : 10, "state" : null }#格式
db.comment.findOne({userId:"1001"})
{"_id" : ObjectId("6312fdfc778ba7b2fb5d7bd6"),"articleid" : "100000","content" : "今天天气真好","userId" : "1001","nickname" : "Rose","createdatetime" : ISODate("2024-09-03T07:10:52.544Z"),"likenum" : 10,"state" : null
}#指定查询字段
db.comment.findOne({userId:"1001"},{articleid:1,content:1})
{"_id" : ObjectId("6312fdfc778ba7b2fb5d7bd6"),"articleid" : "100000","content" : "今天天气真好"
}#排除指定字段
db.comment.findOne({userId:"1001"},{articleid:1,content:1,_id:0})
{ "articleid" : "100000", "content" : "今天天气真好" }
3.4.3文档的更新
db.collection.update(query,update,options)
或
db.collection.uodate(<query>,<update>,{upsert: <boolean>,multi:<boolean>,writeConcern:<document>collation:  [<filterdocument1>,...],hint:<document|string> }
)

示例

  1. 覆盖的修改

如果我们想修改id为1的记录,点赞量为1001,输入一下语句

db.comment.update({id:"1",{likenum:NumberInt(1001)})

执行后我们会发现,这条字段除了likenum,其他字段都不见了

  1. 局部修改
db.comment.update({id:"1",{{$set:likenum:NumberInt(1001)}})
  1. 批量修改
db.comment.update({id:"1",{{$set:likenum:NumberInt(1001)}},{multi:true})

如果不加参数,只修改第一条

  1. 列值增长的修改

如果我们想实现对某列值在原有值基础上进行增加或减少,可以使用$inc运算符来实现

需求:对3号数据的点赞数,每次递增1

db.comment.update({id:"3",{{$inc:likenum:NumberInt(1)}})
3.4.4文档的删除
db.集合名称.remove(条件)

以下为将数据全部删除

db.comment.remove({})

如果删除_id=1的记录,输入以下:

db.comment.remove({_id:"1"})

3.5文档的分页查询

3.5.1统计查询

统计查询使用count()方法

db.collection.count(query,options)

示例:

db.comment.count(Userid:"1003")
3.5.2列表查询

使用limit()可以限制查询的条数,使用skip()方法来跳过指定数量的数据

db.collection.find(x)

skip()

db.comment.find().skip(x)

例子

  1. 查找前四条数据
db.comment.find().limit(4)
  1. 查找第2条到最后一条数据
db.comment.find.skip(2)
  1. 查找第2条到第4条数据
#跳过前两条记录,查询两条记录
db.comment.find().skip(2).limit(2)
3.5.5排序查询

sort()方法对数据进行排序,sort()方法可以通过参数指定排序的字段,并使用1和-1来指定排序的方式,其中1为升序排序,-1位降序排序

#升序
db.collection.find().sort({key:1})#降序
db.collection.find().sort({key:-1})

例如

对userId进行降序排序

db.comment.find({},{userid:1}),sort({key:-1})

多个条件时

db.comment.find({},{userId:1,likenum:1}).sort(userId:-1,likenum:1)

3.6文档的更多查询

3.6.1正则的复杂条件查询

MongoDB的模糊查询是通过正则表达式的方式实现的。格式为:

db.collection.find({field:/正则表达式/})
或
db.集合.find({字段:/正则表达式/})

提示:正则表达式是js的语法,直接量的写法

例如,我要查询评论内容包含“开水”的所有文档,代码如下

db.comment.find({content:/开水/})

如果要查询评论的内容中以“专家”开头的,代码如下

db.comment.find({content:/^专家/})
3.6.2比较查询

<,<=,>,>=这些操作符也是很常用的,格式如下

db.集合名称.find({"field":{$gt:value}}) //大于: field > value
db.集合名称.find({"field":{$lt:value}}) //小于: field < value
db.集合名称.find({"field":{$gte:value}}) //大于等于: field >= value
db.集合名称.find({"field":{$lte:value}}) //小于等于: field <= value
db.集合名称.find({"field":{$ne:value}}) //不等于: field != value

例子:查询评论点赞数量大于700的记录

db.comment.find({likenum:{$gt:NumberInt(7000)}})
3.6.3包含查询

使用$in

**示例:**查询评论的集合中userId字段包含1003或1004的文档

db.comment.find({userId:{$in["1003","1004"]}})

不包含$nIn

示例:

db.comment.find({userId:{$nin:["1003","1004"]}})
3.6.5条件连接查询

我们如果需要查询同时满足两个以上条件,需要使用$and操作符

$and:[{},{}]

**示例:**查询评论集合中likenum大于等于700并且小于2000的文档:

db.comment.find({$and:[{likenum:{$gte:NumberInt(700)},{likenum:{$it:NumberInt(2000)}}}]})

如果两个以上的条件时或者的关系,我们使用$or操作符

$or:[{},{}]

**示例:**查询评论集合中userId为1003,或者点赞数小于1000的文档

db.comment.find({$or:[{userId:"1003"},{likenum:{$lt:1000}}]})

四、索引

4.1概述

​ 索引支持在MongoDB中高效地执行查询。如果没有索引,MongoDB必须执行全集合扫描,即扫描集合中的每个文档以选择与查询语句匹配的文档。这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
​ 如果查询存在适当的索引,MongoDB可以使用该索引限制必须检查的文档数。
索引是特殊的数据结构,它以易于遍历的形式存储集合数据集的一小部分。索引存储特定字段或一组字段的值,按字段值排序。索引项的排序支持有效的相等匹配和基于范围的查询操作。此外,MongoDB丕可以使用索引中的排序返回排序结果。
​ 官网文档: https://docs.mongodb.com/manual/indexes/了解:
​ MogoDB索引使用B树数据结构(确切的说是B-Tree,MySQL是B+Tree)

4.2索引的类型

4.2.1单字段索引

​ MongoDB支持在文档的单个字段上创建用户定义的升序/降序索引,称为单字段索引(Single Field lndex)。对于单个字段索引和排序操作,索引键的排序顺序(即升序或降序)并不重要,因为MongoDB可以在任何方向上遍历索引。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.2.2复合索引

​ MongoDB还支持多个字段的用户定义索引,即复合索引(Compound Index)。
​ 复合索引中列出的字段顺序具有重要意义。例如,如果复合索引由{ userid: 1, score: -1}组成,则索引首先按userid正序排序,然后在每个userid的值内,再在按score倒序排序。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.2.3其他索引

地理空间索引(Geospatial lndex)、文本索引(Text Indexes)、哈希索引 (Hashed lndexes)。

  • 地理空间索引 (Geospatial lndex):为了支持对地理空间坐标数据的有效查询,MongoDB提供了两种特殊的索引:返回结果时使用平面几何的二维索引和返回结果时使用球面几何的二维球面索引。
  • 文本索引(Text Indexes):MongoDB提供了一种文本索引类型,支持在集合中搜索字符串内容。这些文本索引不存储特定于语言的停止词(例如如"the"、“a””、“or”),而将集合中的词作为词干,只存储根词。
  • 哈希索引 (Hashed lndexes):为了支持基于散列的分片,MongoDB提供了散列索引类型,他对字段的散列进行索引,这些索引在其范围内的值分布更加随机,但只支持相等匹配,不支持基于范围的查询

4.3索引的管理操作

4.3.1索引的查看

返回一个集合中的所有索引的数组

db.collection.getIndex()

仅MongoDB3.0+支持

**例子:**查看comment集合中所有的索引情况

db.comment.getIndexes()

结果中显示的是默认_id索引

4.3.2索引的创建
  1. 单个索引
db.collection.createIndex(keys,options)
  1. 复合索引
# 根据userId升序,nickname降序创建
db.comment.createIndex({userId:1,nickname:-1})
4.3.3索引的移除
  1. 指定索引的移除
db.collection.dropIndex(index)

示例:

db.comment.dropIndex({userId:1})
  1. 移除所有索引
db.collection.dropIndexes()

_id是不能被删除的,可以删除其他字段的索引

4.4索引的使用

4.4.1执行计划

类似mysql的执行计划,用来分析性能,通常使用执行计划来查看情况

db.collection.find(query,options).explain(options)

**示例:**根据userId查询数据的情况

db.comment.find({userId:"1003"}).explain()
> db.comment.find({userId:"1003"}).explain()
{"explainVersion" : "1","queryPlanner" : {"namespace" : "my.comment","indexFilterSet" : false,"parsedQuery" : {"userId" : {"$eq" : "1003"}},"queryHash" : "B1777DBA","planCacheKey" : "A6C2ADF9","maxIndexedOrSolutionsReached" : false,"maxIndexedAndSolutionsReached" : false,"maxScansToExplodeReached" : false,"winningPlan" : {"stage" : "FETCH","inputStage" : {"stage" : "IXSCAN",   #因为建立了索引,此处就不是全表扫描了"keyPattern" : {"userId" : 1},"indexName" : "userId_1","isMultiKey" : false,"multiKeyPaths" : {"userId" : [ ]},"isUnique" : false,"isSparse" : false,"isPartial" : false,"indexVersion" : 2,"direction" : "forward","indexBounds" : {"userId" : ["[\"1003\", \"1003\"]"]}}},"rejectedPlans" : [ ]},"command" : {"find" : "comment","filter" : {"userId" : "1003"},"$db" : "my"},"serverInfo" : {"host" : "VM-24-17-centos","port" : 27017,"version" : "5.0.11","gitVersion" : "d08c3c41c105cde798ca934e3ac3426ac11b57c3"},"serverParameters" : {"internalQueryFacetBufferSizeBytes" : 104857600,"internalQueryFacetMaxOutputDocSizeBytes" : 104857600,"internalLookupStageIntermediateDocumentMaxSizeBytes" : 104857600,"internalDocumentSourceGroupMaxMemoryBytes" : 104857600,"internalQueryMaxBlockingSortMemoryUsageBytes" : 104857600,"internalQueryProhibitBlockingMergeOnMongoS" : 0,"internalQueryMaxAddToSetBytes" : 104857600,"internalDocumentSourceSetWindowFieldsMaxMemoryBytes" : 104857600},"ok" : 1
}

MongoDB Compass查看

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.4.2涵盖的查询

当查询掉件和查询的投影仅包含索引字段时,MongoDB直接从索引返回结果,而不扫描任何文档或将文档带入内存,这些覆盖的查询可以非常有效

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

db.comment.find({userId:"1003"},{userId,_id:0})

五、案例:文章评论

5.1需求分析

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5.2表结构分析

数据库:articledb

字段名称作用字段类型备注
_idIDObjectId或StringMongo的主键字段
articleId文章idString
content评论内容String
userId评论人IDString
nickname评论人昵称String
createdatetime评论的日期和时间Date
likenum点赞数Int32
replynum回复数Int32
state状态String0:不可见,1:可见
parentId子评论上级IDString如果为0表示文章的置顶评论

5.3技术选型

以下为SpringBoot项目搭建

5.3.1mongodb-driver

java链接的,类似于JDBC

5.3.2SpringDataMongoDB

SpringData家族成员之一,底部封装了mongodb-driver

5.4搭建Java环境

  • maven
        <!--        Mongodb--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency><!--        Mongodb-->
  • yaml
spring:data:mongodb:host: 101.43.254.109database: articledbport: 27017#有账号密码就写,没有就注掉username: "用户名"password: "密码"#因为配置过对所有数据库通用的账号,所以这里加上authentication-database: admin#也可以使用url连接#uri:

5.5编写实体类

  • pojo
package com.example.pojo;import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;/*** @author 我见青山多妩媚* @date Create on 2024/9/5 15:27*/@Data
@ToString
//类名是大写,这里是为了和数据库对应
@Document(collection = "comment")
//新增复合索引,最好还是在命令行的方式建
//@CompoundIndex(def = "{'userId':1,'nickname':-1}")
public class Comment implements Serializable {//主键 该属性的值会自动对应mongodb的主键字段“_id",如果该属性名就交”id“,则该注解可以省略,否则必须写@Idprivate String id;//该属性对应mongodb的字段的名称,如果一致就不用写@Field("content")//内容private String content;//发布日期private Date publishTime;//发布人Id@Indexed    //添加一个单字段的索引private String userId;//昵称private String nickname;//评论人日期private LocalDateTime createDatetime;//点赞数private Integer likeNum;//回复数private Integer replyNum;//状态private String state;//上级IDprivate String parentId;//文章IDprivate String articleId;}

5.6CRUD

  • dao
package com.example.demo.dao;import com.example.pojo.Comment;
import org.springframework.data.mongodb.repository.MongoRepository;/*** @author 我见青山多妩媚* @date Create on 2024/9/5 15:58*/
public interface CommentRepository extends MongoRepository<Comment,String> {
}
  • service
package com.example.demo.service;import com.example.demo.dao.CommentRepository;
import com.example.pojo.Comment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;/*** @author 我见青山多妩媚* @date Create on 2024/9/5 15:59*/
@Service
public class CommentService {@Autowiredprivate CommentRepository commentRepository ;/*** 保存* @param comment c* @return comment*/public Comment saveComment(Comment comment){return commentRepository.save(comment);}/*** 更新* @param comment c* @return comment*/public Comment updateComment(Comment comment){return commentRepository.save(comment);}/*** 删除* @param id id*/public void deleteCommentById(String id){commentRepository.deleteById(id);}/*** 查询所有* @return list*/public List<Comment> findCommentList(){return commentRepository.findAll();}/*** 根据id查询频率* @param id id* @return comment*/public Comment findCommentByID(String id){return commentRepository.findById(id).get();}
}
  • Test
package com.example.demo.service;import com.example.pojo.Comment;import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import java.time.LocalDateTime;
import java.util.List;/*** @author 我见青山多妩媚* @date Create on 2024/9/5 16:09*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class CommentServiceTest {@Autowiredprivate CommentService commentService;@Testpublic void findCommentList(){List<Comment> commentList = commentService.findCommentList();for (Comment comment : commentList) {System.out.println(comment);}}@Testpublic void save(){Comment comment = new Comment();comment.setArticleId("100002");comment.setContent("我不爱喝水");comment.setUserId("1005");comment.setNickname("海绵宝宝");comment.setCreateDatetime(LocalDateTime.now());comment.setLikeNum(6000);comment.setState("1");commentService.saveComment(comment);}@Testpublic void findById(){//Comment(id=6315bb3efabdb55ab0d8ab2f, content=我爱喝水, publishTime=null, userId=1004, nickname=杰克船长, createDatetime=2024-09-05T17:02:54.301, likeNum=2000, replyNum=null, state=1, parentId=null, articleId=100001)System.out.println(commentService.findCommentByID("6315bb3efabdb55ab0d8ab2f"));}
}

5.7根据上级ID查询文章评论的分页列表

  1. CommentRepository新增
Page<Comment> findByParentId(String parentId, Pageable pageable);
  1. CommentService新增
    /*** 分页查询* @param parentId 父级id* @param page 页码* @param size 每页size* @return comment*/public Page<Comment> findCommentListByParentId(String parentId,int page,int size){return commentRepository.findByParentId(parentId, PageRequest.of(page-1, size));}
  1. Test
    @Testpublic void findByParentId(){Page<Comment> page = commentService.findCommentListByParentId("10004", 1, 4);System.out.println("total:"+page.getTotalElements());List<Comment> content = page.getContent();System.out.println("all:"+page.getContent());for (Comment comment : content) {System.out.println(comment);}}

5.8MongoTemplate实现评论点赞

我们看以下节点的临时示例代码:CommentService新增updateThumbup方法

    /*** 评论点赞* 效率低*/public void updateCommentThumbupToIncrementingOld(String id){Comment comment = commentRepository.findById(id).get();comment.setLikeNum(comment.getLikeNum()+1);commentRepository.save(comment);}

以上方法虽然实现起来比较简单,但是执行效率比较高,因为我们只需要将点赞数+1就可以了,没必要查询出所有字段修改后再更新所有字段

优化

我们可以使用MongoTemplate类来实现对某列的操作

  1. 修改CommentService
    @Autowiredprivate MongoTemplate mongoTemplate;/*** 点赞优化* @param id id*/public void updateCommentLikeNum(String id){//查询条件  数据库里的id 等于传进来的id时Query query = Query.query(Criteria.where("_id").is(id));//更新条件Update update = new Update();//指定字段为likenum,步长为1update.inc("likeNum",1);//指定query、update、指定表<comment.class>mongoTemplate.updateFirst(query,update,Comment.class);}
  1. 测试
    @Testpublic void updateCommentLikeNum(){commentService.updateCommentLikeNum("6315bfcb234e2c2d4bb864c3");}

MongoDB

一、基本使用

1.1业务应用场景

传统的关系型数据库(如Mysql),在数据库操作的“三高”需求以及对应web2.0的网站需求面前,显得力不从心

三高:

  • High performance - 对数据库高并发读写的要求
  • Huge Storage - 对海量数据的高效率存储和访问的需求
  • High Scalability && High Avaliablity 对数据库的高可扩展性和高可用性的需求

具体的应用场景如:

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

这些应用场景中,数据操作方面的共同特点是:

  1. 数据量大
  2. 写入操作频繁(读写都很频繁)
  3. 价值较低的数据,对事务性要求不高

对于这样的数据,我们更适合使用MongoDB来实现数据的存储。什么时候选择MongoDB ?
在架构选型上,除了上述的三个特点外,如果你还犹豫是否要选择它?可以考虑以下的一些问题:应用不需要事务复杂join支持
新应用,需求会变,数据模型无法确定,想快速迭代开发应用需要2000-3000以上的读写QPS(更高也可以)
应用需要TB甚至PB级别数据存储

那么我们什么时候选择 MongoDB 呢?

除了架构选型上, 除了上述三个特点之外, 还要考虑下面这些问题:

  • 应用不需要事务及复杂 JOIN 支持
  • 新应用, 需求会变, 数据模型无法确定, 想快速迭代开发
  • 应用需要 2000 - 3000 以上的读写QPS(更高也可以)
  • 应用需要 TB 甚至 PB 级别数据存储
  • 应用发展迅速, 需要能快速水平扩展
  • 应用要求存储的数据不丢失
  • 应用需要 99.999% 高可用
  • 应用需要大量的地理位置查询, 文本查询

如果上述有1个符合, 可以考虑 MongoDB, 2个及以上的符合, 选择 MongoDB 绝不会后悔.

如果用MySQL呢?

相对MySQL, 可以以更低的成本解决问题(包括学习, 开发, 运维等成本)

1.2 MongoDB 简介

MongoDB是一个开源, 高性能, 无模式的文档型数据库, 当初的设计就是用于简化开发和方便扩展, 是NoSQL数据库产品中的一种.是最 像关系型数据库(MySQL)的非关系型数据库. 它支持的数据结构非常松散, 是一种类似于 JSON 的 格式叫BSON, 所以它既可以存储比较复杂的数据类型, 又相当的灵活. MongoDB中的记录是一个文档, 它是一个由字段和值对(field:value)组成的数据结构.MongoDB文档类似于JSON对象, 即一个文档认 为就是一个对象.字段的数据类型是字符型, 它的值除了使用基本的一些类型外, 还可以包括其他文档, 普通数组和文档数组.

“最像关系型数据库的 NoSQL 数据库”. MongoDB 中的记录是一个文档, 是一个 key-value pair. 字段的数据类型是字符型, 值除了使用基本的一些类型以外, 还包括其它文档, 普通数组以及文档数组

img

img

MongoDB 数据模型是面向文档的, 所谓文档就是一种类似于 JSON 的结构, 简单理解 MongoDB 这个数据库中存在的是各种各样的 JSON(BSON)

  • 数据库 (database)
    • 数据库是一个仓库, 存储集合 (collection)
  • 集合 (collection)
    • 类似于数组, 在集合中存放文档
  • 文档 (document)
    • 文档型数据库的最小单位, 通常情况, 我们存储和操作的内容都是文档

在 MongoDB 中, 数据库和集合都不需要手动创建, 当我们创建文档时, 如果文档所在的集合或者数据库不存在, 则会自动创建数据库或者集合

1.3Windows下载

https://www.mongodb.com/try/download/community

1.4Linux下载

https://www.mongodb.com/try/download/community

1.5图形化界面

https://www.mongodb.com/try/download/compass

二、单机部署

三、基本使用

3.1案例需求

3.2数据库操作

  • 设置MongoDB账号密码,防止被删库勒索
  1. 进入到mongodb服务器下
use admindb.createUser({user: '用户名',    // 用户名(自定义)pwd: '密码',  // 密码(自定义)roles:[{role: 'root',   // 使用超级用户角色db: 'admin'     // 指定数据库}]
})
  1. 找到配置的mongodb.conf文件,添加以下信息
security:authorization: enabled
  1. 重启mongodb服务
  2. 使用mongodb连接数据库,并使用超级管理员账号

在没有认证之前,也可以使用mongo链接数据库,但是不能执行其他命令

以下是两种使用超级管理员账号登录数据库的方式

// 方式一
mongo
use admin
db.auth('用户名', '密码')// 方式二
mongo admin -u 用户名 -p 密码
3.2.1权限说明(基于角色的权限控制)
3.2.1.1 内置角色
数据库用户角色
  • read: 只读数据权限
  • readWrite:写数据权限
数据库管理角色
  • dbAdmin: 在当前db中执行管理操作的权限
  • dbOwner: 在当前db中执行任意操作
  • userADmin: 在当前db中管理user的权限
备份和还原角色
  • backup
  • restore
夸库角色
  • readAnyDatabase: 在所有数据库上都有读取数据的权限
  • readWriteAnyDatabase: 在所有数据库上都有读写数据的权限
  • userAdminAnyDatabase: 在所有数据库上都有管理user的权限
  • dbAdminAnyDatabase: 管理所有数据库的权限
集群管理
  • clusterAdmin: 管理机器的最高权限
  • clusterManager: 管理和监控集群的权限
  • clusterMonitor: 监控集群的权限
  • hostManager: 管理Server
超级权限
  • root: 超级用户

  • 显示数据库

show dbs
3.2.1选择和创建数据库

选择和创建数据库

use 数据库名称

如果数据库不存在将会自动创建,比如将创建spitdb数据库

use spitdb

查看当前数据库

db

默认的三个数据库

  • admin:从权限的角度看,这是root数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库权限。一些特定的服务器端命令也只能通过这个数据库运行,比如列出所有的数据库或者关闭服务器
  • local:这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合
  • config:当Mongo分片设置时,config数据库在内部使用,用于保存分片的相关信息
3.2.2数据库的删除
db.dropDatabase()

3.3集合操作

3.2.1集合的显示创建
db.createCollection(name) #name必须要由 "" 包围

查看当前库中的表

show tables
3.2.2集合的隐式创建

当向有一个集合中插入一个文档的时候,如果集合不存在,将会自动创建集合

3.2.3集合的删除
db.collection.drop()
或
db.集合name.drop()

返回值

如果成功删除选定的集合,则drop()方法返回true,否则返回false

例如:要删除mycollection集合

db.mycollection.drop()

3.4文档基本CRUD

文档(document)的数据结构和JSON基本一样

所有存储在集合中的数据都是JSON格式

3.4.1文档的插入
  1. 单个文档插入

使用insert() 或 save()方法向集合中插入文档,语法:

db.collection.insert(<document or array of documents>,{writeConcern: <document>,ordered: <boolean>}
)

参数

ParamterType描述
documentdocument or array要插入到集合内的文档或文档数组(JSON格式)
writeConcerndocument
orderdboolean可选。true代表按顺序插入数组中的文档,如果其中一个文档出现错误,MongoDB将返回而不处理数组中的其他文档;如果为false,则执行顺序插入,如果其中一个文档出现错误,则继续处理数组中的主文档。在2.6+版本,默认为true

示例

db.comment.insert({"articleid":"100000","content":"今天天气真好","userId":"1001","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null}
)

提示

  • comment如果不存在,将会被隐式创建
  • mongodb中的数字,默认情况下时double类型,如果要存入整形,必须使用函数Number(整型数字),否则取出来会有问题
  • 插入当前日期使用new Date()
  • 插入的数据没有指定_id,会自动生成主键值
  • 如果某字段没值,可以赋值为null,或不写

插入成功后的返回值

WriteResult({ "nInserted" : 1 })
  1. 多个文档插入
db.collection.insert([<document or array of documents>,{writeConcern: <document>,ordered: <boolean>},{writeConcern: <document>,ordered: <boolean>},{writeConcern: <document>,ordered: <boolean>},....])
db.comment.insert([{"articleid":"100001","content":"今天天气真好","userId":"1001","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null},{"articleid":"100002","content":"今天天气真好","userId":"1002","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null},{"articleid":"100003","content":"今天天气真好","userId":"1003","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null},{"articleid":"100004","content":"今天天气真好","userId":"1004","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null}
]);

如果担心多个数据插入查询失败,可以加上try-catch

try{db.comment.insert([{"articleid":"100001","content":"今天天气真好","userId":"1001","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null},{"articleid":"100002","content":"今天天气真好","userId":"1002","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null},{"articleid":"100003","content":"今天天气真好","userId":"1003","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null},{"articleid":"100004","content":"今天天气真好","userId":"1004","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null}]);
}catch(e){print(e);
}
3.4.2文档的查询
db.collection.find(<query>,[projection])

参数:

ParameterTypeDescription
querydocument可选,使用查询运算符指定选择筛选器。若要返回集合中的所有文档,请省略此参数或传递空文档
projectiondocument可选。指定要在于查询结果筛选器匹配的文档中返回的字段(投影)。若要返回匹配文档中的所有字段,请省略此参数

实例

db.comment.find()
或
db.comment.find({})#查询所有
db.comment.find(){ "_id" : ObjectId("6312fdfc778ba7b2fb5d7bd6"), "articleid" : "100000", "content" : "今天天气真好", "userId" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2024-09-03T07:10:52.544Z"), "likenum" : 10, "state" : null }#查询指定内容
db.comment.find({userId:"1001"}){ "_id" : ObjectId("6312fdfc778ba7b2fb5d7bd6"), "articleid" : "100000", "content" : "今天天气真好", "userId" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2024-09-03T07:10:52.544Z"), "likenum" : 10, "state" : null }#格式
db.comment.findOne({userId:"1001"})
{"_id" : ObjectId("6312fdfc778ba7b2fb5d7bd6"),"articleid" : "100000","content" : "今天天气真好","userId" : "1001","nickname" : "Rose","createdatetime" : ISODate("2024-09-03T07:10:52.544Z"),"likenum" : 10,"state" : null
}#指定查询字段
db.comment.findOne({userId:"1001"},{articleid:1,content:1})
{"_id" : ObjectId("6312fdfc778ba7b2fb5d7bd6"),"articleid" : "100000","content" : "今天天气真好"
}#排除指定字段
db.comment.findOne({userId:"1001"},{articleid:1,content:1,_id:0})
{ "articleid" : "100000", "content" : "今天天气真好" }
3.4.3文档的更新
db.collection.update(query,update,options)
或
db.collection.uodate(<query>,<update>,{upsert: <boolean>,multi:<boolean>,writeConcern:<document>collation:  [<filterdocument1>,...],hint:<document|string> }
)

示例

  1. 覆盖的修改

如果我们想修改id为1的记录,点赞量为1001,输入一下语句

db.comment.update({id:"1",{likenum:NumberInt(1001)})

执行后我们会发现,这条字段除了likenum,其他字段都不见了

  1. 局部修改
db.comment.update({id:"1",{{$set:likenum:NumberInt(1001)}})
  1. 批量修改
db.comment.update({id:"1",{{$set:likenum:NumberInt(1001)}},{multi:true})

如果不加参数,只修改第一条

  1. 列值增长的修改

如果我们想实现对某列值在原有值基础上进行增加或减少,可以使用$inc运算符来实现

需求:对3号数据的点赞数,每次递增1

db.comment.update({id:"3",{{$inc:likenum:NumberInt(1)}})
3.4.4文档的删除
db.集合名称.remove(条件)

以下为将数据全部删除

db.comment.remove({})

如果删除_id=1的记录,输入以下:

db.comment.remove({_id:"1"})

3.5文档的分页查询

3.5.1统计查询

统计查询使用count()方法

db.collection.count(query,options)

示例:

db.comment.count(Userid:"1003")
3.5.2列表查询

使用limit()可以限制查询的条数,使用skip()方法来跳过指定数量的数据

db.collection.find(x)

skip()

db.comment.find().skip(x)

例子

  1. 查找前四条数据
db.comment.find().limit(4)
  1. 查找第2条到最后一条数据
db.comment.find.skip(2)
  1. 查找第2条到第4条数据
#跳过前两条记录,查询两条记录
db.comment.find().skip(2).limit(2)
3.5.5排序查询

sort()方法对数据进行排序,sort()方法可以通过参数指定排序的字段,并使用1和-1来指定排序的方式,其中1为升序排序,-1位降序排序

#升序
db.collection.find().sort({key:1})#降序
db.collection.find().sort({key:-1})

例如

对userId进行降序排序

db.comment.find({},{userid:1}),sort({key:-1})

多个条件时

db.comment.find({},{userId:1,likenum:1}).sort(userId:-1,likenum:1)

3.6文档的更多查询

3.6.1正则的复杂条件查询

MongoDB的模糊查询是通过正则表达式的方式实现的。格式为:

db.collection.find({field:/正则表达式/})
或
db.集合.find({字段:/正则表达式/})

提示:正则表达式是js的语法,直接量的写法

例如,我要查询评论内容包含“开水”的所有文档,代码如下

db.comment.find({content:/开水/})

如果要查询评论的内容中以“专家”开头的,代码如下

db.comment.find({content:/^专家/})
3.6.2比较查询

<,<=,>,>=这些操作符也是很常用的,格式如下

db.集合名称.find({"field":{$gt:value}}) //大于: field > value
db.集合名称.find({"field":{$lt:value}}) //小于: field < value
db.集合名称.find({"field":{$gte:value}}) //大于等于: field >= value
db.集合名称.find({"field":{$lte:value}}) //小于等于: field <= value
db.集合名称.find({"field":{$ne:value}}) //不等于: field != value

例子:查询评论点赞数量大于700的记录

db.comment.find({likenum:{$gt:NumberInt(7000)}})
3.6.3包含查询

使用$in

**示例:**查询评论的集合中userId字段包含1003或1004的文档

db.comment.find({userId:{$in["1003","1004"]}})

不包含$nIn

示例:

db.comment.find({userId:{$nin:["1003","1004"]}})
3.6.5条件连接查询

我们如果需要查询同时满足两个以上条件,需要使用$and操作符

$and:[{},{}]

**示例:**查询评论集合中likenum大于等于700并且小于2000的文档:

db.comment.find({$and:[{likenum:{$gte:NumberInt(700)},{likenum:{$it:NumberInt(2000)}}}]})

如果两个以上的条件时或者的关系,我们使用$or操作符

$or:[{},{}]

**示例:**查询评论集合中userId为1003,或者点赞数小于1000的文档

db.comment.find({$or:[{userId:"1003"},{likenum:{$lt:1000}}]})

四、索引

4.1概述

​ 索引支持在MongoDB中高效地执行查询。如果没有索引,MongoDB必须执行全集合扫描,即扫描集合中的每个文档以选择与查询语句匹配的文档。这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
​ 如果查询存在适当的索引,MongoDB可以使用该索引限制必须检查的文档数。
索引是特殊的数据结构,它以易于遍历的形式存储集合数据集的一小部分。索引存储特定字段或一组字段的值,按字段值排序。索引项的排序支持有效的相等匹配和基于范围的查询操作。此外,MongoDB丕可以使用索引中的排序返回排序结果。
​ 官网文档: https://docs.mongodb.com/manual/indexes/了解:
​ MogoDB索引使用B树数据结构(确切的说是B-Tree,MySQL是B+Tree)

4.2索引的类型

4.2.1单字段索引

​ MongoDB支持在文档的单个字段上创建用户定义的升序/降序索引,称为单字段索引(Single Field lndex)。对于单个字段索引和排序操作,索引键的排序顺序(即升序或降序)并不重要,因为MongoDB可以在任何方向上遍历索引。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.2.2复合索引

​ MongoDB还支持多个字段的用户定义索引,即复合索引(Compound Index)。
​ 复合索引中列出的字段顺序具有重要意义。例如,如果复合索引由{ userid: 1, score: -1}组成,则索引首先按userid正序排序,然后在每个userid的值内,再在按score倒序排序。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.2.3其他索引

地理空间索引(Geospatial lndex)、文本索引(Text Indexes)、哈希索引 (Hashed lndexes)。

  • 地理空间索引 (Geospatial lndex):为了支持对地理空间坐标数据的有效查询,MongoDB提供了两种特殊的索引:返回结果时使用平面几何的二维索引和返回结果时使用球面几何的二维球面索引。
  • 文本索引(Text Indexes):MongoDB提供了一种文本索引类型,支持在集合中搜索字符串内容。这些文本索引不存储特定于语言的停止词(例如如"the"、“a””、“or”),而将集合中的词作为词干,只存储根词。
  • 哈希索引 (Hashed lndexes):为了支持基于散列的分片,MongoDB提供了散列索引类型,他对字段的散列进行索引,这些索引在其范围内的值分布更加随机,但只支持相等匹配,不支持基于范围的查询

4.3索引的管理操作

4.3.1索引的查看

返回一个集合中的所有索引的数组

db.collection.getIndex()

仅MongoDB3.0+支持

**例子:**查看comment集合中所有的索引情况

db.comment.getIndexes()

结果中显示的是默认_id索引

4.3.2索引的创建
  1. 单个索引
db.collection.createIndex(keys,options)
  1. 复合索引
# 根据userId升序,nickname降序创建
db.comment.createIndex({userId:1,nickname:-1})
4.3.3索引的移除
  1. 指定索引的移除
db.collection.dropIndex(index)

示例:

db.comment.dropIndex({userId:1})
  1. 移除所有索引
db.collection.dropIndexes()

_id是不能被删除的,可以删除其他字段的索引

4.4索引的使用

4.4.1执行计划

类似mysql的执行计划,用来分析性能,通常使用执行计划来查看情况

db.collection.find(query,options).explain(options)

**示例:**根据userId查询数据的情况

db.comment.find({userId:"1003"}).explain()
> db.comment.find({userId:"1003"}).explain()
{"explainVersion" : "1","queryPlanner" : {"namespace" : "my.comment","indexFilterSet" : false,"parsedQuery" : {"userId" : {"$eq" : "1003"}},"queryHash" : "B1777DBA","planCacheKey" : "A6C2ADF9","maxIndexedOrSolutionsReached" : false,"maxIndexedAndSolutionsReached" : false,"maxScansToExplodeReached" : false,"winningPlan" : {"stage" : "FETCH","inputStage" : {"stage" : "IXSCAN",   #因为建立了索引,此处就不是全表扫描了"keyPattern" : {"userId" : 1},"indexName" : "userId_1","isMultiKey" : false,"multiKeyPaths" : {"userId" : [ ]},"isUnique" : false,"isSparse" : false,"isPartial" : false,"indexVersion" : 2,"direction" : "forward","indexBounds" : {"userId" : ["[\"1003\", \"1003\"]"]}}},"rejectedPlans" : [ ]},"command" : {"find" : "comment","filter" : {"userId" : "1003"},"$db" : "my"},"serverInfo" : {"host" : "VM-24-17-centos","port" : 27017,"version" : "5.0.11","gitVersion" : "d08c3c41c105cde798ca934e3ac3426ac11b57c3"},"serverParameters" : {"internalQueryFacetBufferSizeBytes" : 104857600,"internalQueryFacetMaxOutputDocSizeBytes" : 104857600,"internalLookupStageIntermediateDocumentMaxSizeBytes" : 104857600,"internalDocumentSourceGroupMaxMemoryBytes" : 104857600,"internalQueryMaxBlockingSortMemoryUsageBytes" : 104857600,"internalQueryProhibitBlockingMergeOnMongoS" : 0,"internalQueryMaxAddToSetBytes" : 104857600,"internalDocumentSourceSetWindowFieldsMaxMemoryBytes" : 104857600},"ok" : 1
}

MongoDB Compass查看

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.4.2涵盖的查询

当查询掉件和查询的投影仅包含索引字段时,MongoDB直接从索引返回结果,而不扫描任何文档或将文档带入内存,这些覆盖的查询可以非常有效

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

db.comment.find({userId:"1003"},{userId,_id:0})

五、案例:文章评论

5.1需求分析

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5.2表结构分析

数据库:articledb

字段名称作用字段类型备注
_idIDObjectId或StringMongo的主键字段
articleId文章idString
content评论内容String
userId评论人IDString
nickname评论人昵称String
createdatetime评论的日期和时间Date
likenum点赞数Int32
replynum回复数Int32
state状态String0:不可见,1:可见
parentId子评论上级IDString如果为0表示文章的置顶评论

5.3技术选型

以下为SpringBoot项目搭建

5.3.1mongodb-driver

java链接的,类似于JDBC

5.3.2SpringDataMongoDB

SpringData家族成员之一,底部封装了mongodb-driver

5.4搭建Java环境

  • maven
        <!--        Mongodb--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency><!--        Mongodb-->
  • yaml
spring:data:mongodb:host: 101.43.254.109database: articledbport: 27017#有账号密码就写,没有就注掉username: "用户名"password: "密码"#因为配置过对所有数据库通用的账号,所以这里加上authentication-database: admin#也可以使用url连接#uri:

5.5编写实体类

  • pojo
package com.example.pojo;import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;/*** @author 我见青山多妩媚* @date Create on 2024/9/5 15:27*/@Data
@ToString
//类名是大写,这里是为了和数据库对应
@Document(collection = "comment")
//新增复合索引,最好还是在命令行的方式建
//@CompoundIndex(def = "{'userId':1,'nickname':-1}")
public class Comment implements Serializable {//主键 该属性的值会自动对应mongodb的主键字段“_id",如果该属性名就交”id“,则该注解可以省略,否则必须写@Idprivate String id;//该属性对应mongodb的字段的名称,如果一致就不用写@Field("content")//内容private String content;//发布日期private Date publishTime;//发布人Id@Indexed    //添加一个单字段的索引private String userId;//昵称private String nickname;//评论人日期private LocalDateTime createDatetime;//点赞数private Integer likeNum;//回复数private Integer replyNum;//状态private String state;//上级IDprivate String parentId;//文章IDprivate String articleId;}

5.6CRUD

  • dao
package com.example.demo.dao;import com.example.pojo.Comment;
import org.springframework.data.mongodb.repository.MongoRepository;/*** @author 我见青山多妩媚* @date Create on 2024/9/5 15:58*/
public interface CommentRepository extends MongoRepository<Comment,String> {
}
  • service
package com.example.demo.service;import com.example.demo.dao.CommentRepository;
import com.example.pojo.Comment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;/*** @author 我见青山多妩媚* @date Create on 2024/9/5 15:59*/
@Service
public class CommentService {@Autowiredprivate CommentRepository commentRepository ;/*** 保存* @param comment c* @return comment*/public Comment saveComment(Comment comment){return commentRepository.save(comment);}/*** 更新* @param comment c* @return comment*/public Comment updateComment(Comment comment){return commentRepository.save(comment);}/*** 删除* @param id id*/public void deleteCommentById(String id){commentRepository.deleteById(id);}/*** 查询所有* @return list*/public List<Comment> findCommentList(){return commentRepository.findAll();}/*** 根据id查询频率* @param id id* @return comment*/public Comment findCommentByID(String id){return commentRepository.findById(id).get();}
}
  • Test
package com.example.demo.service;import com.example.pojo.Comment;import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import java.time.LocalDateTime;
import java.util.List;/*** @author 我见青山多妩媚* @date Create on 2024/9/5 16:09*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class CommentServiceTest {@Autowiredprivate CommentService commentService;@Testpublic void findCommentList(){List<Comment> commentList = commentService.findCommentList();for (Comment comment : commentList) {System.out.println(comment);}}@Testpublic void save(){Comment comment = new Comment();comment.setArticleId("100002");comment.setContent("我不爱喝水");comment.setUserId("1005");comment.setNickname("海绵宝宝");comment.setCreateDatetime(LocalDateTime.now());comment.setLikeNum(6000);comment.setState("1");commentService.saveComment(comment);}@Testpublic void findById(){//Comment(id=6315bb3efabdb55ab0d8ab2f, content=我爱喝水, publishTime=null, userId=1004, nickname=杰克船长, createDatetime=2024-09-05T17:02:54.301, likeNum=2000, replyNum=null, state=1, parentId=null, articleId=100001)System.out.println(commentService.findCommentByID("6315bb3efabdb55ab0d8ab2f"));}
}

5.7根据上级ID查询文章评论的分页列表

  1. CommentRepository新增
Page<Comment> findByParentId(String parentId, Pageable pageable);
  1. CommentService新增
    /*** 分页查询* @param parentId 父级id* @param page 页码* @param size 每页size* @return comment*/public Page<Comment> findCommentListByParentId(String parentId,int page,int size){return commentRepository.findByParentId(parentId, PageRequest.of(page-1, size));}
  1. Test
    @Testpublic void findByParentId(){Page<Comment> page = commentService.findCommentListByParentId("10004", 1, 4);System.out.println("total:"+page.getTotalElements());List<Comment> content = page.getContent();System.out.println("all:"+page.getContent());for (Comment comment : content) {System.out.println(comment);}}

5.8MongoTemplate实现评论点赞

我们看以下节点的临时示例代码:CommentService新增updateThumbup方法

    /*** 评论点赞* 效率低*/public void updateCommentThumbupToIncrementingOld(String id){Comment comment = commentRepository.findById(id).get();comment.setLikeNum(comment.getLikeNum()+1);commentRepository.save(comment);}

以上方法虽然实现起来比较简单,但是执行效率比较高,因为我们只需要将点赞数+1就可以了,没必要查询出所有字段修改后再更新所有字段

优化

我们可以使用MongoTemplate类来实现对某列的操作

  1. 修改CommentService
    @Autowiredprivate MongoTemplate mongoTemplate;/*** 点赞优化* @param id id*/public void updateCommentLikeNum(String id){//查询条件  数据库里的id 等于传进来的id时Query query = Query.query(Criteria.where("_id").is(id));//更新条件Update update = new Update();//指定字段为likenum,步长为1update.inc("likeNum",1);//指定query、update、指定表<comment.class>mongoTemplate.updateFirst(query,update,Comment.class);}
  1. 测试
    @Testpublic void updateCommentLikeNum(){commentService.updateCommentLikeNum("6315bfcb234e2c2d4bb864c3");}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/457315.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1U服务器和Hyper-V虚拟机使用记录

记录最近接触服务器和虚拟机的一些使用操作知识 背景&#xff1a;1U服务器上架使用&#xff0c;备份其他服务器vm虚拟机&#xff0c;Hyper-V管理虚拟机使用测试 设备&#xff1a;IBM3550服务器交换机&#xff0c; 移动硬盘&#xff1a;附加存储盘&#xff0c; u盘1&#xff1…

解决JAVA使用@JsonProperty序列化出现字段重复问题(大写开头的字段重复序列化)

文章目录 引言I 解决方案方案1:使用JsonAutoDetect注解方案2:手动编写get方法,JsonProperty注解加到方法上。方案3:首字母改成小写的II 知识扩展:对象默认是怎样被序列化?引言 需求: JSON序列化时,使用@JsonProperty注解,将字段名序列化为首字母大写,兼容前端和第三方…

【C++】进阶:类相关特性的深入探讨

⭐在对C 中类的6个默认成员函数有了初步了解之后&#xff0c;现在我们进行对类相关特性的深入探讨&#xff01; &#x1f525;&#x1f525;&#x1f525;【C】类的默认成员函数&#xff1a;深入剖析与应用&#xff08;上&#xff09; 【C】类的默认成员函数&#xff1a;深入剖…

【从零开始的LeetCode-算法】910. 最小差值 II

给你一个整数数组 nums&#xff0c;和一个整数 k 。 对于每个下标 i&#xff08;0 < i < nums.length&#xff09;&#xff0c;将 nums[i] 变成 nums[i] k 或 nums[i] - k 。 nums 的 分数 是 nums 中最大元素和最小元素的差值。 在更改每个下标对应的值之后&#xf…

论文略读:Not all Layers of LLMs are Necessary during Inference

202404 LLMs的推理阶段非常昂贵 目前实现LLM高效推理的流行方法包括模型剪枝和稀疏模型 但这些方法可能会改变LLM参数&#xff0c;从而冒险损害其泛化能力。这篇论文动态减少激活神经元的数量以加速LLM推理 根据输入实例动态决定推理终止时刻

openjdk17在java方法中创建对象 类加载在C++源码实现步骤

java的testYYM方法中OtherClass类是如何被加载的 ##有请志愿者java实验类 ByteCodeTest、OtherClass import java.lang.reflect.Method;public class ByteCodeTest {private int insert678;public static void main(String[] args) throws Exception {ByteCodeTest byteCodeT…

leetcode动态规划(十三)-目标和

题目 494.目标和 给你一个非负整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 或 - &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 &#xff1a; 例如&#xff0c;nums [2, 1] &#xff0c;可以在 2 之前添加 &#xff0c;在 1 之前添…

Unity(四十八):Unity与Web双向交互

效果 游戏对象绑定脚本 游戏脚本源码 using System.Collections; using System.Collections.Generic; using UnityEngine;public class Tent : MonoBehaviour {public Camera camera;// Start is called before the first frame updatevoid Start(){}// Update is called once…

鸿蒙网络编程系列36-固定包头可变包体解决TCP粘包问题

1. TCP数据传输粘包简介 在本系列的第6篇文章《鸿蒙网络编程系列6-TCP数据粘包表现及原因分析》中&#xff0c;我们演示了TCP数据粘包的表现&#xff0c;如图所示&#xff1a; 随后解释了粘包背后的可能原因&#xff0c;并给出了解决TCP传输粘包问题的两种思路&#xff0c;第一…

网络通信与并发编程(六)线程、进程池与线程池

线程、进程池与线程池 文章目录 线程、进程池与线程池一、线程二、线程的相关操作2.1创建线程的两种方式2.2线程的其他操作2.3死锁现象和递归锁2.4条件2.5定时器2.6 队列与堆栈 三、进程池与线程池 一、线程 线程是指cpu上实际执行计算的单位&#xff0c;而进程是将计算所需资…

潮畔汽车文化营地开营啦!全民测试场启动典礼圆满成功

在这个充满活力与创新的时代&#xff0c;汽车已不仅仅是代步工具&#xff0c;它承载着人们对速度、自由、科技与梦想的追求&#xff0c;成为了一种生活方式的象征。随着汽车文化的日益丰富和多元化&#xff0c;如何更好地连接汽车制造商、消费者以及广大汽车爱好者&#xff0c;…

群控系统服务端开发模式-应用开发-业务架构逻辑开发准备工作

安装与仓库已经调整完毕&#xff0c;现在开发业务架构逻辑&#xff0c;其次再开发功能逻辑。业务架构逻辑开发与功能逻辑开发不是一回事&#xff0c;一定要明白。业务架构指的是做某一件事或是某一种类型的事的逻辑&#xff0c;在互联网web应用中通常指一套系统的外在逻辑&…

Vue学习笔记(四)

事件处理 我们可以使用 v-on 指令 (通常缩写为 符号) 来监听 DOM 事件&#xff0c;并在触发事件时执行一些 JavaScript。用法为 v-on:click"methodName" 或使用快捷方式 click"methodName" 事件处理器的值可以是&#xff1a; 内联事件处理器&#xff1…

Jetpack架构组件_LiveData组件

1.LiveData初识 LiveData:ViewModel管理要展示的数据&#xff08;VM层类似于原MVP中的P层&#xff09;&#xff0c;处理业务逻辑&#xff0c;比如调用服务器的登陆接口业务。通过LiveData观察者模式&#xff0c;只要数据的值发生了改变&#xff0c;就会自动通知VIEW层&#xf…

基于Python大数据的王者荣耀战队数据分析及可视化系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

IDEA开发工具使用技巧积累

一、IDEA 工具设置默认使用maven的settings.xml文件 第一步&#xff1a;打开idea工具&#xff0c;选中 File ——> New Projects Setup ——> Settings for New Projects 第二步&#xff1a;先设置下自动构建项目这个选项 第三步&#xff1a;选中 Build Tools ——>…

windows下pycharm社区版2024下载与安装(包含新建第一个工程)

windows下pycharm社区版2024下载与安装 下载pycharm pycharm官网 安装pycharm 1.进入官网 pycharm官网 下载 点击Download–>右侧Other versions 下载对应的社区版&#xff08;如下图&#xff09;&#xff1a;下载网址 2.点击运行下载好的安装包 点击下一步 3.更改pychar…

2020款Macbook Pro A2251无法充电无法开机定位及修复

问题背景 up主有一台2020年的Macbook Pro&#xff0c;带Touch Bar&#xff0c;16G512G&#xff0c;四核I5&#xff0c;型号A2251 应该是一周没充电了&#xff0c;之前还用的好好的&#xff0c;后来有一天出差想带上 打开没电&#xff0c;手头上有个小米的66W快充头&#xff0c…

C#的自定义Tip窗体 - 开源研究系列文章

上次编写了自定义的提示和对话框窗体&#xff0c;这次记录的是自定义的Tip窗体&#xff0c;用于显示提示操作。有时间没编程了&#xff0c;这次就当进行了记录。 1、 项目目录&#xff1b; 2、 源码介绍&#xff1b; 1) 实现&#xff1b; 2) 应用&#xff1b; 3、 运行界面&…

Leetcode刷题笔记12

HJ1 字符串最后一个单词的长度 字符串最后一个单词的长度_牛客题霸_牛客网 这里可以使用rfind()&#xff0c;rfind()函数从字符串的末尾向前查找第一个空格的位置。这个空格将是最后一个单词和前面的单词的分隔符。首先使用getline读取字符串&#xff0c;然后用rfind找到最后一…