文章目录
人生哪能多如意,万事只求半称心。
1. MongoDB概述
MongoDB 官网:https://www.mongodb.com
MongoDB 中文社区:https://www.mongodb.org.cn
MongoDB是一个开源、高性能、支持海量数据存储的文档型数据库(也是nosql大家庭的一员),它是C++编写的,问世于2000年代中期。
MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,数据格式是 BSON,一种类似 JSON 的二进制形式的存储格式,简称 Binary JSON,和 JSON 一样支持内嵌的文档对象和数组对象,因此可以存储比较复杂的数据类型。
MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
什么类型的数据适合存储MongoDB中?
- 数据存储量较大,甚至是海量数据。(使用Redis存储成本较高)
- 对数据读写的响应速度要求较高。
- 某些数据安全性要求不高,可以接受一定范围内的误差。
MongoDB特点:
-
是一个开源的、高性能的文档型数据库,属于NoSQL的一种;
-
数据存储方面:内存+磁盘;
-
高扩展方面:内置数据分片,方便水平扩展;
MongoDB与Redis和MySQL对比:
-
MongoDB与Redis对比:
- Redis,纯内存数据库,内存不足触发淘汰策略(数据会丢失);
- MongoDB,结构化存储格式(Bson),方便扩展。
-
MongoDB与MySQL对比:
- MongoDB不支持事务和多表操作;
- MongoDB支持动态字段管理(而MySQL一旦确定表结构,就很难修改)。
-
三者查询效率对比:
- Redis>MongoDB>MySQL;
MongoDB适用场景:
- 游戏场景,使用MongoDB存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、更新;
- 物流场景,使用MongoDB存储订单信息,订单状态在运送过程中会不断更新,以MongoDB内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来;
- 社交场景,使用MongoDB存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能;
- 物联网场景,使用MongoDB存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析;
- 视频直播,使用MongoDB存储用户信息、礼物信息等;
- 大数据应用,使用云数据库MongoDB作为大数据的云存储系统,随时进行数据提取分析,掌握行业动态;
MongoDB体系结构与术语:
MongoDB 主要由: 文档(document)、集合(collection)、数据库(database)这三部分组成的。
- MongoDB 的文档(document),相当于关系数据库中的一行记录。
- 多个文档组成一个集合(collection),相当于关系数据库的表。
- 多个集合(collection),逻辑上组织在一起,就是数据库(database)。
- 一个 MongoDB 实例支持多个数据库(database)。 文档(document)、集合(collection)、数据库(database)的层次结构如下图:
MongoDB与关系型数据库(RDBMS)术语对比:
SQL术语/概念 | MongoDB术语/概念 | 解释/说明 |
---|---|---|
database | database | 数据库 |
table | collection | 数据库表/集合 |
row | document | 表中的一条数据 |
column | field | 数据字段/域 |
index | index | 索引 |
table joins | 表连接,MongoDB不支持 | |
primary key | primary key | 主键,MongoDB自动将 _id 字段设置为主键 |
如果不指定id,mongodb会自动生成一个id。
关系型数据库和MongoDB数据对应关系:
MongoDB数据类型:
- 数据格式:BSON ,{aa:bb} 语法类似json的数据格式;
- null:用于表示空值或者不存在的字段,{“x”:null} ;
- 布尔型:布尔类型有两个值true和false,{“x”:true} ;
- 数值:shell默认使用64为浮点型数值。{“x”:3.14}或{“x”:3}。对于整型值,可以使用 NumberInt(4字节符号整数)或NumberLong(8字节符号整数), {“x”:NumberInt(“3”)}{“x”:NumberLong(“3”)} ;
- 字符串:UTF-8字符串都可以表示为字符串类型的数据,{“x”:“哈喽爪哇”} ;
- 日期:日期被存储为自新纪元依赖经过的毫秒数,不存储时区,{“x”:new Date()};
- 正则表达式:查询时,使用正则表达式作为限定条件,语法与JavaScript的正则表达式相 同,{“x”😕[abc]/} ;
- 数组:数据列表或数据集可以表示为数组,{“x”: [“a“,“b”,”c”]} ;
- 内嵌文档:文档可以嵌套其他文档,被嵌套的文档作为值来处理,{“x”:{“y”:3 }} ;
- 对象Id:对象id是一个12字节的字符串,是文档的唯一标识,{“x”: objectId() } ;
- 二进制数据:二进制数据是一个任意字节的字符串。它不能直接在shell中使用。如果要 将非utf-字符保存到数据库中,二进制数据是唯一的方式。
2. MongoDB安装
基于Docker部署MongoDB环境。
1、下载镜像
docker pull mongo:4.2.1
2、启动容器
docker run -di --name mongo-service --restart=always -p 27017:27017 -v /docker/mongodata:/data mongo:4.2.1
3、使用客户端工具连接mongodb(默认端口号:27017)
- Studio3T,https://studio3t.com/ (免费试用30天)
- Robo3T,https://download.studio3t.com/robomongo/windows/robo3t-1.4.3-windows-x86_64-48f7dfd.exe(免费版)
本文以Robo3T工具为例,使用教程如下:
3. MongoDB快速入门
通过命令操作mongodb。
3.1 数据库以及表的操作
#查看所有的数据库
> show dbs
#通过use关键字切换数据库
> use admin
#创建数据库
#说明:在MongoDB中,数据库是自动创建的,通过use切换到新数据库中,进行插入数据即可自动创建数据库
> use testdb
> show dbs #并没有创建数据库
> db.user.insert({id:1,name:'zhangsan'}) #插入数据
> show dbs
#查看表
> show tables
> show collections
#删除集合(表)
> db.user.drop()
true #如果成功删除选定集合,则 drop() 方法返回 true,否则返回 false。
#删除数据库
> use testdb #先切换到要删除的数据中
> db.dropDatabase() #删除数据库
3.2 新增数据
在MongoDB中,存储的文档结构是一种类似于json的结构,称之为bson(全称为:Binary JSON)。
#插入数据
#语法:db.表名.insert(json字符串)
> db.user.insert({id:1,username:'zhangsan',age:20})
> db.user.find() #查询数据
3.3 更新数据
update() 方法用于更新已存在的文档。语法格式如下:
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 :可选,抛出异常的级别。
#查询全部
> db.user.find()
#更新数据
> db.user.update({id:1},{$set:{age:22}})
#注意:如果这样写,会删除掉其他的字段
> db.user.update({id:1},{age:25})
#更新不存在的字段,会新增字段
> db.user.update({id:2},{$set:{sex:1}}) #更新数据
#更新不存在的数据,默认不会新增数据
> db.user.update({id:3},{$set:{sex:1}})
#如果设置第一个参数为true,就是新增数据
> db.user.update({id:3},{$set:{sex:1}},true)
3.4 删除数据
通过remove()方法进行删除数据,语法如下:
db.collection.remove(
<query>,
{
justOne: <boolean>,
writeConcern: <document>
}
)
参数说明:
- query :(可选)删除的文档的条件。
- justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。
- writeConcern :(可选)抛出异常的级别。
示例:
#删除数据
> db.user.remove({})
#插入4条测试数据
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})
> db.user.remove({age:22},true)
#删除所有数据
> db.user.remove({})
3.5 查询数据
MongoDB 查询数据的语法格式如下:
db.user.find([query],[fields])
- query :可选,使用查询操作符指定查询条件
- fields :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。
条件查询:
操作 | 格式 | 范例 | RDBMS中的类似语句 |
---|---|---|---|
等于 | {<key>:<value> } | db.col.find({"by":"Java之父ysj"}).pretty() | where by = 'Java之父ysj' |
小于 | {<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 |
实例:
#插入测试数据
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})
db.user.find() #查询全部数据
db.user.find({},{id:1,username:1}) #只查询id与username字段
db.user.find().count() #查询数据条数
db.user.find({id:1}) #查询id为1的数据
db.user.find({age:{$lte:21}}) #查询小于等于21的数据
db.user.find({$or:[{id:1},{id:2}]}) #查询id=1 or id=2
#分页查询:Skip()跳过几条,limit()查询条数
db.user.find().limit(2).skip(1) #跳过1条数据,查询2条数据
db.user.find().sort({id:-1}) #按照id倒序排序,-1为倒序,1为正序
3.6 索引
索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。
这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构
#创建索引
> db.user.createIndex({'age':1})
#查看索引
> db.user.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "testdb.user"
}
]
#说明:1表示升序创建索引,-1表示降序创建索引。
3.7 执行计划
MongoDB 查询分析可以确保我们建议的索引是否有效,是查询语句性能分析的重要工具。
#插入1000条数据
for(var i=1;i<1000;i++)db.user.insert({id:100+i,username:'name_'+i,age:10+i})
#查看执行计划
> db.user.find({age:{$gt:100},id:{$lt:200}}).explain()
#测试没有使用索引
> db.user.find({username:'zhangsan'}).explain()
#winningPlan:最佳执行计划
#"stage" : "FETCH", #查询方式,常见的有COLLSCAN/全表扫描、IXSCAN/索引扫描、FETCH/根据索引去检索文档、SHARD_MERGE/合并分片结果、IDHACK/针对_id进行查询
4、SpringBoot整合MongoDB
4.1 环境搭建
1、构建springboot项目
2、导入mongodb依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- spring-data-mongodb -->
<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>
</dependencies>
3、向application.yml添加配置
server:
port: 9998
spring:
data:
mongodb:
host: 127.0.0.1
port: 27017
database: mongo-test
4、编写实体类
package com.example.mongodemo.pojo;
import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;
/**
* 学生实体类
*
* @author 白豆五
* @version 2023/06/9
* @since JDK8
*/
@Data
@Document("student") //与mongodb集合进行绑定
public class Student {
private String id;
private String name;
private char sex;
private Integer age;
private String major;
private Double score;
}
4.2 新增文档
新增方法有:insert、save()方法。
package com.example.mongodemo.test;
import com.example.mongodemo.pojo.Student;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.MongoTemplate;
/**
* @author 白豆五
* @version 2023/06/9
* @since JDK8
*/
@SpringBootTest
public class MongoTest {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 测试新增文档
*/
@Test
public void testAddDocument() {
for (int i = 0; i < 10; i++) {
Student student = new Student();
student.setId(String.valueOf(i));
student.setName("user" + i);
student.setSex((i % 2 == 0 ? '女' : '男'));
student.setAge(20);
student.setMajor("Java");
student.setScore(98.0);
// mongoTemplate.insert(student);//可以新增一条或多条数据
mongoTemplate.save(student); //表示新增或更新一条数据,不能操作多条
}
/*
insert和save方法的区别:
1.insert可以批量保存多条数据,save只能保存一条数据;
2.insert方法只能表示新增,而使用save方法在数据不存在的时候是新增,在数据存在时则是更新。
*/
}
}
4.3 查询文档
查询方法有:findById、findOne、findAll、find()方法。
/**
* 测试查询文档
*/
@Test
public void testQueryDocument() {
// 1.查询一条数据,根据主键id查询
Student s1 = mongoTemplate.findById("1", Student.class);
System.out.println(s1); //Student(id=1, name=user1, sex=男, age=20, major=Java, score=98.0)
// 2.查询一条数据,根据指定域查询(字段) ,sql: select * from student where name='user1';
Student s2 = mongoTemplate.findOne(Query.query(
// 构造查询条件
Criteria.where("name").is("user3") //Criteria类提供了很多查询条件
), Student.class);
System.out.println(s2); // Student(id=3, name=user3, sex=男, age=20, major=Java, score=98.0)
// 3.查询列表,查询全部数据
List<Student> list = mongoTemplate.findAll(Student.class);
System.out.println(list.size());//10
// 4.查询列表,指定条件查询(查询所有年龄大于18岁的男生信息)
list = mongoTemplate.find(Query.query(
Criteria.where("sex").is('男').and("age").gt(18)
// is 等于查询
// gt le ...
// in 范围查询
// ne 不等于查询
// like 模糊查询
// 拼接条件用and方法
), Student.class);
list.forEach(s -> System.out.println(s));
/*
Student(id=1, name=user1, sex=男, age=20, major=Java, score=98.0)
Student(id=3, name=user3, sex=男, age=20, major=Java, score=98.0)
Student(id=5, name=user5, sex=男, age=20, major=Java, score=98.0)
Student(id=7, name=user7, sex=男, age=20, major=Java, score=98.0)
Student(id=9, name=user9, sex=男, age=20, major=Java, score=98.0)
*/
System.out.println("===========");
// 5.查询列表,根据id倒排序
list = mongoTemplate.find(Query.query(Criteria.where("sex").is('女')).with(Sort.by(Sort.Direction.DESC, "id")), Student.class);
list.forEach(s -> System.out.println(s));
/*
Student(id=8, name=user8, sex=女, age=20, major=Java, score=98.0)
Student(id=6, name=user6, sex=女, age=20, major=Java, score=98.0)
Student(id=4, name=user4, sex=女, age=20, major=Java, score=98.0)
Student(id=2, name=user2, sex=女, age=20, major=Java, score=98.0)
Student(id=0, name=user0, sex=女, age=20, major=Java, score=98.0)
*/
System.out.println("===========");
// 6.查询列表,只查前3条数据limit(3)
list = mongoTemplate.find(Query.query(Criteria.where("sex").is('女'))//指定查询条件
.with(Sort.by(Sort.Direction.DESC, "id")) //排序
.limit(3) //限制查询条数
, Student.class);
list.forEach(s -> System.out.println(s));
/*
Student(id=8, name=user8, sex=女, age=20, major=Java, score=98.0)
Student(id=6, name=user6, sex=女, age=20, major=Java, score=98.0)
Student(id=4, name=user4, sex=女, age=20, major=Java, score=98.0)
*/
}
4.4 更新文档
更新方法有:save、update、findAndModify()方法。
/**
* 测试更新文档
*/
@Test
public void testUpdateDocument() {
// 方式1:使用save方法更新文档(根据id更新)
Student student = mongoTemplate.findById("1", Student.class);
if (student != null) {
student.setAge(18);
mongoTemplate.save(student);
}
// 方式2:根据指定条件更新文档(根据用户名更新)(非线程安全方法)
mongoTemplate.updateFirst(
Query.query(Criteria.where("name").is("user2")) //更新条件
, new Update().set("name", "小明") //更新的值
, Student.class //数据类型
);
// 方式2:根据指定条件更新文档(对小明的成绩+1)(非线程安全方法)
mongoTemplate.updateFirst(
Query.query(Criteria.where("name").is("小明")) //更新条件
, new Update().inc("score", 1) //inc 1自增, -1 自减
, Student.class //数据类型
);
// 方式3:指定更新条件(线程安全方法,保证操作数据的原子性)
mongoTemplate.findAndModify(
Query.query(Criteria.where("name").is("小明")) //更新条件
, new Update().inc("score", 1) //inc 1自增, -1 自减
, Student.class //数据类型
);
}
4.5 删除文档
删除方法有:remove、update、findAndModify()方法。
/**
* 测试删除文档
*/
@Test
public void testDeleteDocument() {
// 删除方式1:根据ID删除
Student student = mongoTemplate.findById("1", Student.class);
if (student != null) {
mongoTemplate.remove(student);
}
// 删除方式2:指定条件删除(删除年龄大于18的学生信息)
mongoTemplate.remove(Query.query(Criteria.where("sex").gt(18)),Student.class);
}