今天我们将通过这一篇博客来了解MongoDB的体系结构,命令行操作和在JAVA 当中使用SpringData-MongoDB 来 操作MongoDB。
如果没有安装的小伙伴 可以看一下 这篇文章 (59条消息) 开源的文档型数据库–MongoDB(安装)_一切总会归于平淡的博客-CSDN博客,将MongoDB安装一下。
我们来看看MongoDB 的数据特征:
- 数据存储量较大,甚至是海量
- 对数据读写的响应速度较高
- 数据安全性不高,有一定范围内的误差
看到这里,有的小伙伴可能就会问?哎呀,我去,这个MongoDB 这么牛的吗?
那它为什么这么牛?我们就要看看它的特点。
1、特点
1.1 数据存储
MongoDB的第一个特点:数据存储
MongoDB是借助 内存 磁盘 共同 来完成数据存储的,那客户端和MongoDB进行交互有分成了两个部分。
客户端的操作,首先操作的是内存,那我们知道,内存的操作速度和操作磁盘的速度的是吧,那走内存肯定是比走磁盘的速度要快很多的。
所以说,如果你的内存足够大的话,我要想从mengoDB查询,直接就能从内存来进行查询,就避免了走磁盘查询。
当然,如果内存没有的话,它还会从磁盘当中来进行读取,接着返回给客户端。
上面说的是查询,写入也是先写入到内存当中,那接着就返回给客户端,所以要进行写入的话,其实直接操作的就是内存,那它的效率就嘎嘎的高。
那大家可能就会有疑问了,现在数据在内存当中,那我这个服务器重启,数据岂不是都消失了?
这一点,MongoDB会借助操作系统的机制,它会把内存中的数据自动映射到磁盘,只不过,它会有一个时间的规则,每60秒会写入一次。
这有没有问题呢?
那肯定是有的,如果说内存当中,已经写入了数据,还没有同步到磁盘上去,这个断电了,那是不是意味着刚刚这60秒的数据就丢失了,这也就解释了为什么MongDB它的效率比较高,因为它操作的是内存。
然后就是MongoDB为什么会有数据丢失的问题呢?因为它涉及到了内存和磁盘的数据同步。
为了解决这个问题,MongoDB在后面的版本当中,对结构进行了优化。
它把内存分成两部分,一个是代表日志,一个是真正的业务数据,同样的磁盘也分成了两块,一个是日志文件,一个是业务数据文件。
客户端发送请求到内存当中,首先要把你的操作记录日志,记录好之后写入到业务数据的内存部分,那日志的内存部分会跟磁盘上的日志部分进行10毫秒数据同步。
那业务数据部分,会经过60秒数据同步。
这种设计它有什么好处?首先如果当服务器它再次断电了,由于日志它们进行数据同步的时间比价短,毕竟都从60缩到了10毫秒,所以不间断的吧所有的操作日志都同步到了日志文件上。
虽然业务数据可能会有60时间的丢失,但是没有关系,日志文件会出手,当服务器 重启的时候,它会解析日志文件里面的内容和业务数据的内容,将它俩进行对比。
将丢失的内容太补偿到文件当中进行存储,但是,不管mongoDB再怎么努力,都会有一定时间间隔的数据丢失。
1.2 高扩展性
mongoDB 的搞扩展性是借助内置数据分片来实现的,在我们使用MongoDB的时候,往往会有这种情况,mongDB由于自己的硬盘存储容量有限,导致多余的数据可能就存不下去了。
那这个时候怎么办?借助内置的数据分片,我们可以将多个mongoDB服务器串联到一起,每台机器存储一部分,这样一来,数据存储量就很多了。
使用mongDB的内置数据分片可以很轻松的存下海量的数据内容,这也为海量数据打下基础。虽然MySQL也支持数据分片,只不过需要借助第三方的服务和组件来实现,实现成本可能会高一些。
2、对比
看了上面对mengoDB的特点介绍,大家可能会有一点懵,我嘞个去,redis已经很厉害了,mysql 也很牛,现在又来了一个mongoDB,我该如何选择呢?
- 与Redis的对比
- Redis纯内存数据库,内存不足触发淘汰策略,那这部分内容就真的丢失了!
- 结构化存储格式(Bson),方便扩展。
- mongDB可以根据某个字段去查询,而这并不是Redis 擅长的。
- 与MySQL对比
- MongoDB不支持事务和多表操作; 比如用户的账号需要满足多个操作的同时成功/失败,那用mongDB就不太合适了。
- MongoDB支持动态字段管理。 例:数据的字段有两项,你再保存一条 变成了三项,在保存一条四项,字段的个数和字段的类型是灵活变化的,但mysql一旦将字段定义完成,就很难修改。
从查询效率上来进行对比:
Redis -> MongoDB -> MySQL
3、使用场景
- 游戏装备数据、游戏道具数据
- 特征:修改频度较高
- 物流行业数据
- 特征:地理位置信息,海量数据
- 直播数据、打赏数据、粉丝数据
- 特征:数据量大,修改频度极高
- 日志数据
- 特征:数据量巨大,结构多变
以上就是mengoDB的适用场景吗,如果大家在实际项目中遇到类似的场景,或许可以选择将数据存储到mengoDB当中来。
4、MongoDB的体系结构与术语
MongoDB 是最像关系型数据库的非关系型数据库,之所以这样子说,是因为它的体系结构和MySQL 是比较像的。
我们通过对比的形式对 MongoDB 的体系结构做一个初步的了解。
SQL术语/概念 | MongoDB术语/概念 | 解释/说明 |
---|---|---|
database | database | 数据库 |
table | collection | 数据库表/集合 |
row | document | 表中的一条数据 |
column | field | 数据字段/域 |
index | index | 索引 |
table joins | 表连接,MongoDB不支持 | |
primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
了解了MongoDB的体系结构我们来看看它的数据结构。
MongoDB中使用Bson存储数据( Binary JSON ),一种类似Json的数据格式。
我们来看看一条数据如何已BSON 的形式显示出来,我呢通过MySQL 对比看看。
MySQL:
MongoDB:
5、MongoDB 命令行操作
了解MongoDB的基本概念之后我们就可以来进行对MongoDB 的入门了。
5.1 数据库以及表的操作
1、查询所有数据库。
代码语言:javascript复制show dbs
2、通过use关键字切换数据库。
代码语言:javascript复制use 切换的数据库
3、创建数据库 :在MongoDB中,数据库是自动创建的,通过use切换到新数据库中,进行插入数据即可自动创建数据库。
代码语言:javascript复制use testdb2
现在查询数据库,并未出现数据库。
插入数据。
代码语言:javascript复制db.user.insert({id:1,name:'zhangsan'})
现在查询。
4、查看表。
代码语言:javascript复制show tables
代码语言:javascript复制show collections
5、删除集合(表)。
代码语言:javascript复制db.user.drop()
6、删除数据库 (需要先切换到要删除的数据中)
代码语言:javascript复制use 要切换的数据库
删除
代码语言:javascript复制db.dropDatabase()
5.2 新增数据
1、插入数据(语法:db.表名.insert(json字符串))
代码语言:javascript复制db.user.insert({id:1,username:'zhangsan',age:20})
2、查询数据
代码语言:javascript复制db.user.find()
这里可能大家会有疑问,为什么还有一个下划线id,这是因为MongoDB它自己有一个默认的主键ID,就是这个_id。
5.3 更新数据
update() 方法用于更新已存在的文档。语法格式如下:
代码语言:javascript复制db.collection.update(
<query>,
<update>,
[
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>
]
)
参数说明:
- query : update的查询条件,类似sql update查询内where后面的。
- update : update的对象和一些更新的操作符(如
inc.$set)等,也可以理解为sql update查询内set后面的
- upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
- multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
- writeConcern :可选,抛出异常的级别。
案例:
代码语言:javascript复制db.user.update({id:1},{$set:{age:22}})
更新不存在的数据,默认不会新增数据。
代码语言:javascript复制db.user.update({id:2},{$set:{sex:1}})
5.4 删除数据
通过remove()方法进行删除数据,语法如下:
代码语言:javascript复制db.collection.remove(
<query>,
{
justOne: <boolean>,
writeConcern: <document>
}
)
参数说明:
- query :(可选)删除的文档的条件。
- justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。
- writeConcern :(可选)抛出异常的级别。
代码演示:
首先我们先插入数据。
代码语言:javascript复制db.user.insert({id:1,username:'zhangsan',age:20})
db.user.insert({id:2,username:'lisi',age:21})
db.user.insert({id:3,username:'wangwu',age:22})
db.user.insert({id:4,username:'zhaoliu',age:22})
删除年龄为22 的数据,只删除一个。
代码语言:javascript复制db.user.remove({age:22},true)
删除所有数据。
代码语言:javascript复制db.user.remove({})
5.5 查询数据
MongoDB 查询数据的语法格式如下:
代码语言:javascript复制db.user.find([query],[fields])
- query :可选,使用查询操作符指定查询条件
- fields :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。
条件查询:
操作 | 格式 | 范例 | RDBMS中的类似语句 |
---|---|---|---|
等于 | {<key>:<value>} | db.col.find({"by":"一切总会归于平淡"}).pretty() | where by = '一切总会归于平淡' |
小于 | {<key>:{$lt:<value>}} | db.col.find({"likes":{$lt:50}}).pretty() | where likes < 50 |
小于或等于 | {<key>:{$lte:<value>}} | db.col.find({"likes":{$lte:50}}).pretty() | where likes <= 50 |
大于 | {<key>:{$gt:<value>}} | db.col.find({"likes":{$gt:50}}).pretty() | where likes > 50 |
大于或等于 | {<key>:{$gte:<value>}} | db.col.find({"likes":{$gte:50}}).pretty() | where likes >= 50 |
不等于 | {<key>:{$ne:<value>}} | db.col.find({"likes":{$ne:50}}).pretty() | where likes != 50 |
代码演示:
插入数据:
代码语言:javascript复制db.user.insert({id:1,username:'zhangsan',age:20})
db.user.insert({id:2,username:'lisi',age:21})
db.user.insert({id:3,username:'wangwu',age:22})
db.user.insert({id:4,username:'zhaoliu',age:22})
1、查询全部数据:
代码语言:javascript复制db.user.find()
2、只查询id与username字段。
代码语言:javascript复制db.user.find({},{id:1,username:1})
3、查询数据条数
代码语言:javascript复制db.user.find().count()
4、查询id为1的数据
代码语言:javascript复制db.user.find({id:1})
5、查询age小于等于21的数据
代码语言:javascript复制db.user.find({age:{$lte:21}})
6、查询id=1 or id=2
代码语言:javascript复制db.user.find({$or:[{id:1},{id:2}]})
7、分页查询:Skip()跳过几条,limit()查询条数
跳过1条数据,查询2条数据
代码语言:javascript复制db.user.find().limit(2).skip(1)
按照id倒序排序,-1为倒序,1为正序
代码语言:javascript复制db.user.find().sort({id:-1})
5.6 索引
为了提高查询效率,MongoDB中也支持索引。
创建索引.
代码语言:javascript复制db.user.createIndex({'age':1})
注意:1 :升序索引 -1 :降序索引
查看索引.
代码语言:javascript复制db.user.getIndexes()
5.7、执行计划
MongoDB 查询分析可以确保我们建议的索引是否有效,是查询语句性能分析的重要工具。
插入1000条数据。
代码语言:javascript复制for(var i=1;i<1000;i )db.user.insert({id:100 i,username:'name_' i,age:10 i})
查看执行计划。
代码语言:javascript复制db.user.find({age:{$gt:100},id:{$lt:200}}).explain()
测试没有使用索引。
代码语言:javascript复制db.user.find({username:'zhangsan'}).explain()
winningPlan:最佳执行计划; “stage” : “FETCH”, #查询方式,常见的有COLLSCAN/全表扫描、IXSCAN/索引扫描、FETCH/根据索引去检索文档、SHARD_MERGE/合并分片结果、IDHACK/针对_id进行查询
6、SpringData-Mongo
简单的了解MongoDB 的基本命令和索引,我们接下来就要进入到本篇博客当中的重点。
我们要在SpringBoot程序中操作MongoDB, 说到JAVA代码操作MongoDB啊,不外乎两种方式。
- 使用官方驱动,类似与使用最基础的JDBC驱动操作mysql这种方式。
- 使用Spring Data 提供的Spring Data Mongo DB。
使用第一种方式过于麻烦(本人喜欢偷懒),所以我们使用第二种方式。
Spring-data对MongoDB做了支持,使用spring-data-mongodb可以简化MongoDB的操作,封装了底层的mongodb-driver。 地址:https://spring.io/projects/spring-data-mongodb
使用Spring-Data-MongoDB很简单,只需要如下几步即可:
6.1 环境搭建
6.1.1 创建工程
springBoot版本不要选3.0或3.0以上的,如果你的jdk版本是17或17以上当我没说。
6.1.2 编写YML文件
代码语言:javascript复制spring:
data:
mongodb:
uri: mongodb://192.168.136.160:27017/testdb2
6.2 完成基本操作
第一步,编写实体类.
代码语言:javascript复制import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(value = "tb_person") // 指定实体类和MongoDB集合的映射关系
public class Person {
@Id
private ObjectId id;
@Field("name")
private String name;
@Field("age")
private int age;
@Field("address")
private String address;
}
第二步,通过MongoTemplate完成CRUD操作。
这里直接在测试类演示。
代码语言:javascript复制 /**
* 注入模板对象
*/
@Resource
private MongoTemplate mongoTemplate;
/**
* 增加
*/
@Test
public void testSave() {
for (int i = 0; i < 10; i ) {
Person person = new Person();
//ObjectId.get():获取一个唯一主键字符串
person.setId(ObjectId.get());
person.setName("张三" i);
person.setAddress("北京顺义" i);
person.setAge(18 i);
mongoTemplate.save(person);
}
}
查询所有。
代码语言:javascript复制/**
* 注入模板对象
*/
@Resource
private MongoTemplate mongoTemplate;
/**
* 查询所有
*/
@Test
public void testFindAll() {
List<Person> list = mongoTemplate.findAll(Person.class);
for (Person person : list) {
System.out.println(person);
}
}
查询年龄小于20的所有人.
代码语言:javascript复制/**
* 注入模板对象
*/
@Resource
private MongoTemplate mongoTemplate;
/**
* 查询年龄小于20的所有人
*/
@Test
public void testFind() {
Query query = new Query(Criteria.where("age").lt(20)); //查询条件对象
//查询
List<Person> list = mongoTemplate.find(query, Person.class);
list.forEach(System.out::println);
}
分页查询.
代码语言:javascript复制/**
* 注入模板对象
*/
@Resource
private MongoTemplate mongoTemplate;
/**
* 分页查询
*/
@Test
public void testPage() {
Criteria criteria = Criteria.where("age").lt(30);
//1、查询总数
Query queryCount = new Query(criteria);
long count = mongoTemplate.count(queryCount, Person.class);
System.out.println(count);
//2、查询当前页的数据列表, 查询第二页,每页查询2条
Query queryLimit = new Query(criteria)
//设置每页查询条数
.limit(2)
//开启查询的条数 (page-1)*size
.skip(2);
List<Person> list = mongoTemplate.find(queryLimit, Person.class);
list.forEach(System.out::println);
}
根据id,修改年龄.
代码语言:javascript复制/**
* 注入模板对象
*/
@Resource
private MongoTemplate mongoTemplate;
/**
* 修改:
* 根据id,修改年龄
*/
@Test
public void testUpdate() {
//1、条件
Query query = Query.query(Criteria.where("id").is("63d26be79e8d6402ffda6b21"));
//2、修改后的数据
Update update = new Update();
update.set("age", 99);
mongoTemplate.updateFirst(query, update, Person.class);
}
删除:根据id删除。
代码语言:javascript复制/**
* 注入模板对象
*/
@Resource
private MongoTemplate mongoTemplate;
@Test
public void testRemove() {
Query query = Query.query(Criteria.where("id").is("63d26be79e8d6402ffda6b21"));
mongoTemplate.remove(query, Person.class);
}