vlambda博客
学习文章列表

面试官问你:“你用过MongoDB吗?”

这次给大家总结下一个NoSQL数据库——MongoDB的基本使用,作为自己的一个记录,也希望能帮助到大家~

1 前言

1.1 NoSQL是什么

NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库(RDBMS)的数据库管理系统的统称。

NoSQL用于超大规模数据的存储。(例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。

1.2 为什么会有NoSQL

以关系型数据库的方式做存储的架构演进

面试官问你:“你用过MongoDB吗?”

阶段一:企业刚发展的阶段,最简单,一个应用服务器配一个关系型数据库,每次读写数据库。

阶段二:无论是使用MySQL还是Oracle还是别的关系型数据库,数据库通常不会先成为性能瓶颈,通常随着企业规模的扩大,一台应用服务器扛不住上游过来的流量且一台应用服务器会产生单点故障的问题,因此加应用服务器并且在流量入口使用Nginx做一层负载均衡,保证把流量均匀打到应用服务器上。

阶段三:随着企业规模的继续扩大,此时由于读写都在同一个数据库上,数据库性能出现一定的瓶颈,此时简单地做一层读写分离,每次写主库,读备库,主备库之间通过binlog同步数据,就能很大程度上解决这个阶段的数据库性能问题

阶段四:企业发展越来越好了,业务越来越大了,做了读写分离数据库压力还是越来越大,这时候怎么办呢,一台数据库扛不住,那我们就分几台吧,做分库分表,对表做垂直拆分,对库做水平拆分。以扩数据库为例,扩出两台数据库,以一定的单号(例如交易单号),以一定的规则(例如取模),交易单号对2取模为0的丢到数据库1去,交易单号对2取模为1的丢到数据库2去,通过这样的方式将写数据库的流量均分到两台数据库上。一般分库分表会使用Shard的方式,通过一个中间件,便于连接管理、数据监控且客户端无需感知数据库ip

关系型数据库的优点

上面的方式,看似可以解决问题(实际上确实也能解决很多问题),正常对关系型数据库做一下读写分离 + 分库分表,支撑个1W+的读写QPS还是问题不大的。但是受限于关系型数据库本身,这套架构方案依然有着明显的不足,下面对利用关系型数据库方式做存储的方案的优点先进行一下分析,后一部分再分析一下缺点,对某个技术的优缺点的充分理解是技术选型的前提。

  • 易理解

  因为行 + 列的二维表逻辑是非常贴近逻辑世界的一个概念,关系模型相对网状、层次等其他模型更加容易被理解

  • 操作方便

  通用的SQL语言使得操作关系型数据库非常方便,支持join等复杂查询,Sql + 二维关系是关系型数据库最无可比拟的优点,这种易用性非常贴近开发者

  • 数据一致性

  支持ACID特性,可以维护数据之间的一致性,这是使用数据库非常重要的一个理由之一,例如同银行转账,张三转给李四100元钱,张三扣100元,李四加100元,而且必须同时成功或者同时失败,否则就会造成用户的资损

  • 数据稳定

  数据持久化到磁盘,没有丢失数据风险,支持海量数据存储

  • 服务稳定

  最常用的关系型数据库产品MySql、Oracle服务器性能卓越,服务稳定,通常很少出现宕机异常

关系型数据库的缺点

紧接着的,我们看一下关系型数据库的缺点,也是比较明显的。

  • 高并发下IO压力大

  数据按行存储,即使只针对其中某一列进行运算,也会将整行数据从存储设备中读入内存,导致IO较高

  • 为维护索引付出的代价大

  为了提供丰富的查询能力,通常热点表都会有多个二级索引,一旦有了二级索引,数据的新增必然伴随着所有二级索引的新增,数据的更新也必然伴随着所有二级索引的更新,这不可避免地降低了关系型数据库的读写能力,且索引越多读写能力越差。有机会的话可以看一下自己公司的数据库,除了数据文件不可避免地占空间外,索引占的空间其实也并不少

  • 为维护数据一致性付出的代价大

  数据一致性是关系型数据库的核心,但是同样为了维护数据一致性的代价也是非常大的。我们都知道SQL标准为事务定义了不同的隔离级别,从低到高依次是读未提交、读已提交、可重复度、串行化,事务隔离级别越低,可能出现的并发异常越多,但是通常而言能提供的并发能力越强。那么为了保证事务一致性,数据库就需要提供并发控制与故障恢复两种技术,前者用于减少并发异常,后者可以在系统异常的时候保证事务与数据库状态不会被破坏。对于并发控制,其核心思想就是加锁,无论是乐观锁还是悲观锁,只要提供的隔离级别越高,那么读写性能必然越差

  • 水平扩展后带来的种种问题难处理

  前文提过,随着企业规模扩大,一种方式是对数据库做分库,做了分库之后,数据迁移(1个库的数据按照一定规则打到2个库中)、跨库join(订单数据里有用户数据,两条数据不在同一个库中)、分布式事务处理都是需要考虑的问题,尤其是分布式事务处理,业界当前都没有特别好的解决方案

  • 表结构扩展不方便

  由于数据库存储的是结构化数据,因此表结构schema是固定的,扩展不方便,如果需要修改表结构,需要执行DDL(data definition language)语句修改,修改期间会导致锁表,部分服务不可用

  • 全文搜索功能弱

  例如like "%中国真伟大%",只能搜索到"2019年中国真伟大,爱祖国",无法搜索到"中国真是太伟大了"这样的文本,即不具备分词能力,且like查询在"%中国真伟大"这样的搜索条件下,无法命中索引,将会导致查询效率大大降低

写了这么多,我的理解核心还是前三点,它反映出的一个问题是关系型数据库在高并发下的能力是有瓶颈的,尤其是写入/更新频繁的情况下,出现瓶颈的结果就是数据库CPU高、Sql执行慢、客户端报数据库连接池不够等错误,因此例如万人秒杀这种场景,我们绝对不可能通过数据库直接去扣减库存。

可能有朋友说,数据库在高并发下的能力有瓶颈,我公司有钱,加CPU、换固态硬盘、继续买服务器加数据库做分库不就好了,问题是这是一种性价比非常低的方式,花1000万达到的效果,换其他方式可能100万就达到了,不考虑人员、服务器投入产出比的Leader就是个不合格的Leader,且关系型数据库的方式,受限于它本身的特点,可能花了钱都未必能达到想要的效果。至于什么是花100万就能达到花1000万效果的方式呢?可以继续往下看,这就是我们要说的NoSql。

正是由于关系型数据库有这样的缺点,NoSQL出现了,是对关系型数据库的一种补充,NoSQL能更好的处理大数据场景。

1.3 NoSQL 数据库分类

类型 部分代表 特点
列存储 Hbase、Cassandra、Hypertable 顾名思义,是按列存储数据的。最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列或者某几列的查询有非常大的IO优势。
文档存储 MongoDB、CouchDB 文档存储一般用类似json的格式存储,存储的内容是文档型的。这样也就有机会对某些字段建立索引,实现关系数据库的某些功能。
搜索型NoSql ElasticSearch 解决关系型数据库全文搜索能力较弱的问题
key-value存储 Tokyo Cabinet / TyrantBerkeley DBMemcacheDBRedis 可以通过key快速查询到其value。一般来说,存储不管value的格式,照单全收。(Redis包含了其他功能)
图存储 Neo4JFlockDB 图形关系的最佳存储。使用传统关系数据库来解决的话性能低下,而且设计使用不方便。
对象存储 db4oVersant 通过类似面向对象语言的语法操作数据库,通过对象的方式存取数据。
xml数据库 Berkeley DB XMLBaseX 高效的存储XML数据,并支持XML的内部查询语法,比如XQuery,Xpath。

1.4 NoSQL的优点/缺点

优点:

  • - 高可扩展性

  • - 分布式计算

  • - 低成本

  • - 架构的灵活性,半结构化数据

  • - 没有复杂的关系

缺点:

  • - 没有标准化

  • - 有限的查询功能(到目前为止)

  • - 最终一致是不直观的程序

1.5 BASE

BASE:Basically Available, Soft-state, Eventually Consistent。由 Eric Brewer 定义。

CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。

BASE是NoSQL数据库通常对可用性及一致性的弱要求原则:

  • Basically Availble --基本可用

  • Soft-state --软状态/柔性事务。"Soft state" 可以理解为"无连接"的, 而 "Hard state" 是"面向连接"的

  • Eventual Consistency -- 最终一致性, 也是 ACID 的最终目的。

1.6 ACID vs BASE

ACID BASE
原子性(Atomicity) 基本可用(Basically Available)
一致性(Consistency) 软状态/柔性事务(Soft state)
隔离性(Isolation) 最终一致性 (Eventual consistency)
持久性 (Durable)

2 什么是MongoDB

MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。

在高负载的情况下,添加更多的节点,可以保证服务器性能。

MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。快速开发互联网Web应用

MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

3 MongoDB安装启动

3.1 安装包下载

MongoDB的版本偶数版本为稳定版本,奇数版本为开发版(中间数字,例如4.4.1为偶数版本,4.1.1为奇数版本)

注意:在 MongoDB 2.2 版本后已经不再支持 Windows XP 系统。最新版本也已经没有了 32 位系统的安装文件。

下载合适自己服务器系统的安装包,如果是window的下载安装包,一直下一步即可(可自行换安装位置)。

3.2 配置环境变量

找到安装目录:D:\install\MongoDB\Server\4.4\bin

计算机->属性->高级系统配置->环境变量->系统变量,找到Path添加如上路径。

面试官问你:“你用过MongoDB吗?”

验证是否配置成功

cmd,输入mongod

面试官问你:“你用过MongoDB吗?”

出现上图内容,则表示配置成功。

3.3 创建数据库目录

D:\data\db

3.4 启动MongoDB数据库

dbpath默认是C:\data\db,port默认是27017

mongod --dbpath D:\data\db --port=27017

3.5 连接mongodb

新开一个cmd窗口,输入mongo 或者mongo --host=127.0.0.1

面试官问你:“你用过MongoDB吗?”

3.6 MongoDB后台启动

net start MongoDB

注意:4.x版本可直接这样后台启动,3.x版本的还需要配置为系统服务才行,可查看官方文档:https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/

3.7 MongoDB客户端

nosqlbooster:https://nosqlbooster.com/downloads

nosql for mongodb:https://www.mongodbmanager.com/download

4 MongoDB基本操作

4.1 三个概念

  • 数据库(database)

    数据库是一个仓库,在仓库中可以存放集合

  • 集合(collection)

    集合类似于数组,在集合中可以存放文档

  • 文档(document)

    数据库中最小单位,我们存储和操作的内容都是文档

注意:在MongoDB中,数据库和集合都不需要手动创建,当我们创建文档时,如果文档或数据库不存在会自动创建数据库和集合。

4.2 基本指令

  1. 显示当前所有数据库

    show dbs

    show databases

  2. 进入指定数据库

    use 数据库名

  3. 显示当前数据库

    db

  4. 显示数据库中所有的集合

    show collections

4.3 数据库的CRUD(增删查改)的操作

文档:https://docs.mongodb.com/manual/crud/

4.3.1 插入文档

  • db.<conllection>.insert(doc); 向集合插入一个文档或多个文档

例子:向test数据库中的stus集合中插入一个新的学生对象{“name”:"孙悟空","age":18}

db.stus.insert({{“name”:"孙悟空",age:18})

  • db.collection.insertOne(); 插入一个文档

  • db.collection.insertMany(); 插入多个文档,使用数组表示

    例子:批量插入

    var arr = [];
    for(var i=1;i<5000;i++){
      arr.push({name:i});
    }
    db.student.insertMany(arr);

4.3.2 查询文档

  • db.collection.find()  查询集合中所有符合条件的文档,返回是一个数组

    例子:查询姓名是苏武的文档

    db.student.find( { name: "苏武" } )

    只返回部分字段,使用投影

    db.student.find({name:"苏武"},{ name:1,_id:-1});//1代表显示,-1代表不显示

  • db.collection.findOne 查询集合中符合条件的一个文档,返回的是一个文档对象

    例子:db.student.findOne({name:"苏武"})

  • db.collection.count(); 查询集合文档数

  • db.student.find().skip(10*1).limit(10); 分页查询,limit参数为每页条数,skip参数为((页码-1) * 每页条数)

  • db.collection.find().sort({name:1}); sort排序,1表示顺序,-1表示倒序

4.3.3 更新文档

filter:查询条件   update:新对象   options:参数选择

  • db.collection.update(<filter>, <update>, <options>);

    例子:修改文档中指定属性,把姓名为苏武的文档的年龄修改为28

    db.stus.update({name:"苏武"},{$set:{age:28}})

    $set:修改指定字段

    $unset:删除指定字段

    https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/

  • db.collection.updateOne(<filter>, <update>, <options>);

  • db.collection.updateMany(<filter>, <update>, <options>);

  • db.collection.replaceOne(<filter>, <update>, <options>);

  1. 局部修改

    db.comment.update({_id:"2"},{$set:{likenum:NumberInt(889)}})

  2. 批量的修改

    //默认只修改第一条数据db.comment.update({userid:"1003"},{$set:{nickname:"凯撒2"}})//修改所有符合条件的数据db.comment.update({userid:"1003"},{$set:{nickname:"凯撒大帝"}},{multi:true})

  3. 列值增长的修改

    db.comment.update({_id:"3"},{$inc:{likenum:NumberInt(1)}})

4.3.4 删除文档

  • db.collection.remove(); 删除满足条件的所有文档

    例子:删除姓名为苏武的文档

    db.student.remove({name:"苏武"});

    删除集合所有文档:db.student.remove({});  

    删除集合:db.student.drop();

  • db.collection.deleteMany(); 删除满足条件的多个文档

  • db.collection.deleteOne(); 删除满足条件的一个文档

4.3.5 常用命令

选择切换数据库:use articledb插入数据:db.comment.insert({bson数据})查询所有数据:db.comment.find();条件查询数据:db.comment.find({条件})查询符合条件的第一条记录:db.comment.findOne({条件})查询符合条件的前几条记录:db.comment.find({条件}).limit(条数)查询符合条件的跳过的记录:db.comment.find({条件}).skip(条数)修改数据:db.comment.update({条件},{修改后的数据}) 或db.comment.update({条件},{$set:{要修改部分的字段:数据})修改数据并自增某字段值:db.comment.update({条件},{$inc:{自增的字段:步进值}})删除数据:db.comment.remove({条件})统计查询:db.comment.count({条件})模糊查询:db.comment.find({字段名:/正则表达式/})条件比较运算:db.comment.find({字段名:{$gt:值}})包含查询:db.comment.find({字段名:{$in:[值1,值2]}})或db.comment.find({字段名:{$nin:[值1,值2]}})条件连接查询:db.comment.find({$and:[{条件1},{条件2}]})或db.comment.find({$or:[{条件1},{条件2}]})

5 索引-Index

5.1 概述

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

5.2 索引的类型

5.2.1 单字段索引

MongoDB支持在文档的单个字段上创建用户定义的升序/降序索引,称为单字段索引(Single Field Index)。对于单个字段索引和排序操作,索引键的排序顺序(即升序或降序)并不重要,因为MongoDB可以在任何方向上遍历索引。

面试官问你:“你用过MongoDB吗?”

5.2.2 复合索引

MongoDB还支持多个字段的用户定义索引,即复合索引(Compound Index)。

复合索引中列出的字段顺序具有重要意义。例如,如果复合索引由 { userid: 1, score: -1 } 组成,则索引首先按userid正序排序,然后 在每个userid的值内,再在按score倒序排序。

面试官问你:“你用过MongoDB吗?”

5.2.3 其他索引

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

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

5.3 索引的管理操作

5.3.1 索引的查看

//返回一个集合中的所有索引的数组。
db.collection.getIndexes()
默认索引是_id
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_"
}

提示:该语法命令运行要求是MongoDB 3.0+

5.3.2 索引的创建

//在集合上创建索引。
db.collection.createIndex(keys, options)

提示:注意在 3.0.0 版本前创建索引方法为 db.collection.ensureIndex() ,之后的版本使用了 db.collection.createIndex() 方法, ensureIndex() 还能用,但只是 createIndex() 的别名。

【示例】

(1)单字段索引示例:对 userid 字段建立索引:

db.comment.createIndex({userid:1}) //参数1:按升序创建索引

(2)复合索引:对 userid 和 nickname 同时建立复合(Compound)索引:

db.comment.createIndex({userid:1,nickname:-1})

5.3.3 索引的移除

//指定索引的移除
db.collection.dropIndex(index)

【示例】

删除 comment 集合中 userid 字段上的升序索引:

 db.comment.dropIndex({userid:1})

所有索引的移除

db.collection.dropIndexes()

提示:_id 的字段的索引是无法删除的,只能删除非 _id 字段的索引。

5.4 索引的使用

5.4.1 执行计划

分析查询性能(Analyze Query Performance)通常使用执行计划(解释计划、Explain Plan)来查看查询的情况,如查询耗费的时间、是否基于索引查询等。通常,我们想知道,建立的索引是否有效,效果如何,都需要通过执行计划查看。

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

【示例】

查看根据userid查询数据的情况:

> db.comment.find({userid:"1003"}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "articledb.comment",
"indexFilterSet" : false,
"parsedQuery" : {
"userid" : {
"$eq" : "1003"
}
},
"winningPlan" : {
"stage" : "COLLSCAN",
"filter" : {
"userid" : {
"$eq" : "1003"
}
},
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "9ef3740277ad",
"port" : 27017,
"version" : "4.0.10",
"gitVersion" : "c389e7f69f637f7a1ac3cc9fae843b635f20b766"
},
"ok" : 1
}
关键点看:"stage" : "COLLSCAN", 表示全集合扫描

下面对userid建立索引

> db.comment.createIndex({userid:1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}

再次查看执行计划:

> db.comment.find({userid:"1013"}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "articledb.comment",
"indexFilterSet" : false,
"parsedQuery" : {
"userid" : {
"$eq" : "1013"
}
},
"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" : [
"[\"1013\", \"1013\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "9ef3740277ad",
"port" : 27017,
"version" : "4.0.10",
"gitVersion" : "c389e7f69f637f7a1ac3cc9fae843b635f20b766"
},
"ok" : 1
}

关键点看:"stage" : "IXSCAN" ,基于索引的扫描

5.4.2 涵盖的查询

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


6 Java案例-文章评论

6.1 需求分析

某头条的文章评论业务如下:

文章示例参考:早晨空腹喝水,是对还是错?https://www.toutiao.com/a6721476546088927748/

需要实现以下功能:

1)基本增删改查API

2)根据文章id查询评论

3)评论点赞

6.2 表结构分析

数据库:articledb

集合:comment

字段名称 字段含义 字段类型 备注
_id ID ObjectId或String Mongo的主键的字段
articleid 文章ID String
content 评论内容 String
publishtime 发布日期 Date
userid 评论人ID String
nickname 评论人昵称 String
createdatetime 评论的日期时间 Date
likenum 点赞数 Int32
replynum 回复数 Int32
state 状态 String 0:不可见;1:可见
parentid 上级ID String 如果为0表示文章的顶级评论

6.3 技术选型

5.3.1 mongodb-driver(了解)

mongodb-driver是mongo官方推出的java连接mongoDB的驱动包,相当于JDBC驱动。我们通过一个入门的案例来了解mongodb-driver的基本使用。官方驱动说明和下载:http://mongodb.github.io/mongo-java-driver/官方驱动示例文档:http://mongodb.github.io/mongo-java-driver/3.8/driver/getting-started/quick-start/

6.3.2 SpringDataMongoDB

SpringData家族成员之一,用于操作MongoDB的持久层框架,封装了底层的mongodb-driver。官网主页:https://projects.spring.io/spring-data-mongodb/

6.4 文章微服务模块搭建

使用idea新建springboot项目或者maven项目。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>2.1.17.RELEASE</version>
       <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>com</groupId>
   <artifactId>mongodb</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>mongodb</name>
   <description>Demo project for Spring Boot</description>

   <properties>
       <java.version>1.8</java.version>
   </properties>

   <dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-data-mongodb</artifactId>
       </dependency>

       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
   </dependencies>

   <build>
       <plugins>
           <plugin>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-maven-plugin</artifactId>
           </plugin>
       </plugins>
   </build>

</project>

配置文件application.yml

spring:
 #数据源配置
 data:
   mongodb:
     host: 127.0.0.1
     database: test
     port: 27017
     #也可以使用uri
     #uri: mongodb://127.0.0.1:27017/test

启动类

@SpringBootApplication
public class MongodbApplication {

   public static void main(String[] args) {
       SpringApplication.run(MongodbApplication.class, args);
  }

}

PO Comment


/**
* 文章评论实体类
*/
//把一个java类声明为mongodb的文档,可以通过collection参数指定这个类对应的文档。
//@Document(collection="mongodb 对应 collection 名")
// 若未加 @Document ,该 bean save 到 mongo 的 comment collection
// 若添加 @Document ,则 save 到 comment collection
@Document(collection="comment")//可以省略,如果省略,则默认使用类名小写映射集合
//复合索引
// @CompoundIndex( def = "{'userid': 1, 'nickname': -1}")
@Data
public class Comment implements Serializable {
   //主键标识,该属性的值会自动对应mongodb的主键字段"_id",如果该属性名就叫“id”,则该注解可以省略,否则必须写
   @Id
   private String id;//主键
   //该属性对应mongodb的字段的名字,如果一致,则无需该注解
   @Field("content")
   private String content;//吐槽内容
   private Date publishtime;//发布日期
   //添加了一个单字段的索引
   @Indexed
   private String userid;//发布人ID
   private String nickname;//昵称
   private LocalDateTime createdatetime;//评论的日期时间
   private Integer likenum;//点赞数
   private Integer replynum;//回复数
   private String state;//状态
   private String parentid;//上级ID
   private String articleid;
}

评论持久层dao- CommentRepository

/**
* 评论的持久层接口
* @ClassName CommentRepository
* @Author luo jin jiang
* @Date 2020/10/8 20:06
* @Version 1.0
*/
public interface CommentRepository extends MongoRepository<Comment,String> {

   /**
    * 根据父id,查询子评论的分页列表
    * @param parentid
    * @param pageable
    */
   Page<Comment> findByParentid(String parentid, Pageable pageable);
}

评论业务逻辑层service - CommentService

@Service
public class CommentService {

   @Autowired
   private CommentRepository commentRepository;
   //注入MongoTemplate
   @Autowired
   private MongoTemplate mongoTemplate;

   /**
    * 保存一个评论
    *
    * @param comment
    */
   public void saveComment(Comment comment) {
       commentRepository.save(comment);
  }

   /**
    * 更新一个评论
    *
    * @param comment
    */
   public void updateComment(Comment comment) {
       commentRepository.save(comment);
  }

   /**
    * 根据id删除评论
    *
    * @param id
    */
   public void deleteCommentById(String id) {
       commentRepository.deleteById(id);
  }

   /**
    * 查询所有评论
    *
    * @return
    */
   public List<Comment> findCommentList() {
       return commentRepository.findAll();
  }

   /**
    * 根据id查询评论
    *
    * @param id
    * @return
    */
   public Comment findCommentById(String id) {
       Optional<Comment> comment = commentRepository.findById(id);
       if (comment.isPresent()) {
           return comment.get();
      }
       return null;
  }
   /**
    * 根据父id查询分页列表
    *
    * @param parentid
    * @param page
    * @param size
    * @return
    */
   public Page<Comment> findCommentListPageByParentid(String parentid, int page, int size) {
       return commentRepository.findByParentid(parentid, PageRequest.of(page - 1, size));
  }

   /**
    * 点赞-效率低
    * 虽然实现起来比较简单,但是执行效率并不高,因为我只需要将点赞数加1就可以了,没必要查询出所有字段修改后再更新所有字
    * 段。(蝴蝶效应)
    *
    * @param id
    */
   public void updateCommentThumbupToIncrementingOld(String id) {
       Comment comment = commentRepository.findById(id).get();
       comment.setLikenum(comment.getLikenum() + 1);
       commentRepository.save(comment);
  }
   /**
    * 点赞数+1
    *
    * @param id
    */
   public void updateCommentLikenum(String id) {
       //查询对象
       Query query = Query.query(Criteria.where("_id").is(id));
       //更新对象
       Update update = new Update();
       //局部更新,相当于$set
       // update.set(key,value)
       //递增$inc
       // update.inc("likenum",1);
       update.inc("likenum");
       //参数1:查询对象
       //参数2:更新对象
       //参数3:集合的名字或实体类的类型Comment.class
       mongoTemplate.updateFirst(query, update, "comment");
  }
}

测试类

//SpringBoot的Junit集成测试
@RunWith(SpringRunner.class)
//SpringBoot的测试环境初始化,参数:启动类
@SpringBootTest(classes = MongodbApplication.class)
public class CommentServiceTest {
   //注入Service
   @Autowired
   private CommentService commentService;
   /**
    * 保存一个评论
    */
   @Test
   public void testSaveComment(){
       for (int i = 3; i <= 3; i++) {
           Comment comment=new Comment();
           comment.setId(i+"");
           comment.setArticleid("100000");
           comment.setContent("测试添加的数据");
           comment.setCreatedatetime(LocalDateTime.now());
           comment.setUserid("1003"+i);
           comment.setNickname("苏苏"+i);
           comment.setState("1");
           comment.setLikenum(0);
           comment.setReplynum(0);
           comment.setParentid("3");
           commentService.saveComment(comment);
      }
  }
   /**
    * 查询所有数据
    */
   @Test
   public void testFindAll(){
       List<Comment> list = commentService.findCommentList();
       System.out.println(list);
  }
  /**
    * 测试根据id查询
    */
  @Test
   public void testFindCommentById(){
       Comment comment = commentService.findCommentById("3");
       System.out.println(comment);
  }
  /**
    * 测试根据id查询
    */
   @Test
   public void testDeleteCommentById(){
       commentService.deleteCommentById("4");
  }
   /**
    * 测试根据父id查询子评论的分页列表
    */
   @Test
   public void testFindCommentListPageByParentid(){
       Page<Comment> pageResponse = commentService.findCommentListPageByParentid("3", 1, 6);
       System.out.println("----总记录数:"+pageResponse.getTotalElements());
       System.out.println("----当前页数据:"+pageResponse.getContent());
  }
  /**
    * 点赞数+1
    */
   @Test
   public void testUpdateCommentLikenum(){
       //对3号文档的点赞数+1
       commentService.updateCommentLikenum("3");
  }
}


学无止境,关注我,我们一起进步。如果觉得文章还可以,点个赞呗,谢谢~我们下期见。