MongoDB简单学习

news2024/11/23 8:37:36

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 admin

db.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.109
      database: articledb
      port: 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“,则该注解可以省略,否则必须写
    @Id
    private 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;

    //上级ID
    private String parentId;

    //文章ID
    private 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 {
    @Autowired
    private 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 {
    @Autowired
    private CommentService commentService;


    @Test
    public void findCommentList(){
        List<Comment> commentList = commentService.findCommentList();
        for (Comment comment : commentList) {
            System.out.println(comment);
        }
    }

    @Test
    public 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);
    }

    @Test
    public 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
    @Test
    public 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
    @Autowired
    private 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,步长为1
        update.inc("likeNum",1);
        //指定query、update、指定表<comment.class>
        mongoTemplate.updateFirst(query,update,Comment.class);
    }
  1. 测试
    @Test
    public 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 admin

db.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.109
      database: articledb
      port: 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“,则该注解可以省略,否则必须写
    @Id
    private 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;

    //上级ID
    private String parentId;

    //文章ID
    private 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 {
    @Autowired
    private 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 {
    @Autowired
    private CommentService commentService;


    @Test
    public void findCommentList(){
        List<Comment> commentList = commentService.findCommentList();
        for (Comment comment : commentList) {
            System.out.println(comment);
        }
    }

    @Test
    public 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);
    }

    @Test
    public 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
    @Test
    public 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
    @Autowired
    private 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,步长为1
        update.inc("likeNum",1);
        //指定query、update、指定表<comment.class>
        mongoTemplate.updateFirst(query,update,Comment.class);
    }
  1. 测试
    @Test
    public void updateCommentLikeNum(){
        commentService.updateCommentLikeNum("6315bfcb234e2c2d4bb864c3");
    }

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32--基于STM32F103C8T6的OV7670摄像头显示

本文介绍基于STM32F103C8T6实现的OV7670摄像头显示设计&#xff08;完整资源及代码见文末链接&#xff09; 一、简介 本文实现的功能&#xff1a;基于STM32F103C8T6实现的OV7670摄像头模组实时在2.2寸TFT彩屏上显示出来 所需硬件&#xff1a; STM32F103C8T6最小系统板、OV76…

[C++ 11] 列表初始化:轻量级对象initializer_list

C发展历史 C11是C语言的第二个主要版本&#xff0c;也是自C98以来最重要的一次更新。它引入了大量的新特性&#xff0c;标准化了已有的实践&#xff0c;并极大地改进了C程序员可用的抽象能力。在2011年8月12日被ISO正式采纳之前&#xff0c;人们一直使用“C0x”这个名称&#…

10-1.idea中的项目结构,辅助快捷键,模块的操作

idea中的项目结构和辅助快捷键 IDEA中项目结构 首先是创建项目&#xff0c;新建的项目中有子项目&#xff0c;我们可以创建模块 然后在模块中我们可以创建包&#xff0c;在包中的SRC中写我们的源代码&#xff0c;也就是类。 VScode写Java项目 如何你电脑比较卡的话&#…

基于GPT的智能客服落地实践

&#x1f4cd;前言 在日常生活中&#xff0c;「客服」这个角色几乎贯穿着我们生活的方方面面。比如&#xff0c;淘宝买东西时&#xff0c;需要客服帮你解答疑惑。快递丢失时&#xff0c;需要客服帮忙找回。报名参加培训课程时&#xff0c;需要客服帮忙解答更适合的课程…… 基…

RTE 2024 隐藏攻略

大家好&#xff01;想必今年 RTE 大会议程大家都了解得差不多了&#xff0c;这将是一场实时互动和多模态 AI builder 的年度大聚会。 大会开始前&#xff0c;我们邀请了参与大会策划的 RTE 开发者社区和超音速计划的成员们&#xff0c;分享了不同活动的亮点和隐藏攻略。 请收…

InnoDB 存储引擎<一>InnoDB简介与MySQL存储架构及相关数据结构

目录 回顾MySQL架构 InnoDB简介 ​MySQL存储结构 回顾MySQL架构 对MySQL架构图的总结: MySQL服务器是以网络服务的方式对外提供数据库服务的&#xff0c;我们使用的应用程序以及客户端统称为外部程序。 外部程序通过发送网络请求的方式来连接MySQL服务器&#xff0c;这时首先每…

SpringBoot启动报错java.nio.charset.MalformedInputException: Input length =1

启动springboot项目时&#xff0c;出现了以下报错&#xff1a; defaultPattern_IS_UNDEFINEDdefaultPattern_IS_UNDEFINEDdefaultPattern_IS_UNDEFINEDjava.lang.IllegalStateException: Failed to load property source from location classpath:/application-local.yamlat o…

使用pytest单元测试框架执行单元测试

Pytest 是一个功能强大且灵活的 Python 单元测试框架&#xff0c;它使编写、组织和运行测试变得更加简单。以下是 Pytest 的一些主要特点和优点&#xff1a; 简单易用&#xff1a;Pytest 提供了简洁而直观的语法&#xff0c;使编写测试用例变得非常容易。它支持使用 assert 语…

《皮革制作与环保科技》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答 问&#xff1a;《皮革制作与环保科技》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的正规学术期刊。 问&#xff1a;《皮革制作与环保科技》级别&#xff1f; 答&#xff1a;国家级。主管单位&#xff1a;中国轻工业联合会 …

软考:CORBA架构

CORBA过时了吗 CORBA指南 个人小结&#xff1a; IPC&#xff0c;进程间通信&#xff0c;Socket应用在不同机器之间的通信 RPC是一种技术思想而非一种规范 但站在八九十年代的当口&#xff0c;简单来说&#xff0c;就是我在本地调用了一个函数&#xff0c;或者对象的方法&…

uvm_info、uvm_warning,uvm_error、uvm_fatal

1、warning/error/fatal调试语句 调试语句除了uvm_info&#xff0c;UVM内部根据问题的严重性&#xff08;severity&#xff09;由低到高&#xff0c;还引入了uvm_warning/uvm_error/uvm_fatal。 它们也是UVM预定义的宏&#xff0c;格式跟umv_info很像&#xff0c;只是不再需要…

int main(int argc,char* argv[])详解

#include <stdio.h> //argc 是指命令行输入参数的个数; //argv[]存储了所有的命令行参数, //arg[0]通常指向程序中的可执行文件的文件名。在有些版本的编译器中还包括程序文件所在的路径。 //如:"d:\Production\Software\VC_2005_Test\Win32控制台应用程序\Vc_T…

Kafka-Windows搭建全流程(环境,安装包,编译,消费案例,远程连接,服务自启,可视化工具)

目录 一. Kafka安装包获取 1. 官网地址 2. 百度网盘链接 二. 环境要求 1. Java 运行环境 (1) 对 java 环境变量进行配置 (2) 下载完毕之后进行解压 三. 启动Zookeeper 四. 启动Kafka (1) 修改Conf下的server.properties文件&#xff0c;修改kafka的日志文件路径 (2)…

软件分享丨Marktext 编辑器

Marktext是一款开源免费的Markdown编辑器&#xff0c;它具有简洁优雅的界面设计和强大的功能&#xff0c;支持多种Markdown语法&#xff0c;包括表格、流程图、甘特图、数学公式、代码高亮等。Marktext还支持导出HTML和PDF格式的文档&#xff0c;非常适合需要编写Markdown文档的…

sersync实时同步部署案例

目录 sersync介绍 案例信息 操作步骤 服务端部署 客户端部署 创建存储目录 安装sersync 修改配置文件 启动服务 停止服务 测试 sersync介绍 sersync是一个基于inotifyrsync的实时文件同步工具&#xff0c;通过监控目录的变动达到实时同步的目的。 案例信息 拓扑…

【微软商店平台】如何将exe打包上传微软商店

打开微软合作者中心&#xff1a;https://partner.microsoft.com/en-us/dashboard/home点击App and Games板块可以创建项目。 3. 重新生成包含私钥的自签名证书 运行以下命令&#xff0c;确保生成的证书包含私钥&#xff1a; New-SelfSignedCertificate -Type CodeSigning -Su…

Git的初次使用

一、下载git 找淘宝的镜像去下载比较快 点击这里 二、配置git 1.打开git命令框 2.设置配置 git config --global user.name "你的用名"git config --global user.email "你的邮箱qq.com" 3.制作本地仓库 新建一个文件夹即可&#xff0c;然后在文件夹…

从零开始:构建一个高效的开源管理系统——使用 React 和 Ruoyi-Vue-Plus 的实战指南

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

QT界面开发:图形化设计、资源文件添加

设计界面介绍 此时我们创建项目时就可以选择添加UI选项了。 添加完之后&#xff0c;我们可以看到&#xff0c;文件中多出了一个存放界面文件的目录&#xff0c;下面有个.ui的界面文件。甚至pro的项目文件中也会添加一项内容。 我们点击界面文件中的.ui文件&#xff0c;我们可以…

mono源码交叉编译 linux arm arm64全过程

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…