1、场景分析:
对于社交类软件的功能,我们需要对它的功能特点做分析:
- 数据量会随着用户数增大而增大
- 读多写少
- 价值较低
- 非好友看不到其动态内容
- 地理位置的查询
针对以上特点,对比分析:
mysql:关系型数据库(效率低)
redis:redis缓存(微博,效率高,数据格式不丰富)
- 对于数据量大而言,显然不能够使用关系型数据库进行存储,我们需要通过MongoDB进行存储
- 对于读多写少的应用,需要减少读取的成本
- 比如说,一条SQL语句,单张表查询一定比多张表查询要快
MongoDB与Redis和Mysql的对比
与Redis的对比
- Redis纯内存数据库,内存不足触发淘汰策略
- 结构化存储格式(Bson),方便扩展
与MySQL的对比
- MongoDB不支持事务和多表操作
- MongoDB支持动态字段管理
查询效率对比: Redis > MongoDB > MySQL
三者特点:
mongodb:存储业务数据(圈子,推荐的数据,小视频数据,点赞,评论等)
redis:承担的角色是缓存层(提升查询效率)
mysql:存储和核心业务数据,账户
1.1、MongoDB简介
MongoDB:是一个开源、高性能、支持海量数据存储的文档型数据库 是NoSQL数据库产品中的一种,是最像关系型数据库(MySQL)的非关系型数据库。(不支持表关系:只能操作单表)
MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。
MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的,它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。
MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
1.2、MongoDB的特点
MongoDB 最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。它是一个面向集合的,模式自由的文档型数据库。具体特点总结如下:
1. 面向集合存储,易于存储对象类型的数据
2. 模式自由
3. 支持动态查询
4. 支持完全索引,包含内部对象
5. 支持复制和故障恢复
6. 使用高效的二进制数据存储,包括大型对象(如视频等)
7. 自动处理碎片,以支持云计算层次的扩展性
8. 支持 Python,PHP,Ruby,Java,C,C#,Javascript,Perl及C++语言的驱动程序,
社区中也提供了对Erlang及.NET 等平台的驱动程序
9. 文件存储格式为 BSON(一种 JSON 的扩展)
应用场景
- 海量数据存储
- 不需要支持事务,不存在复杂的多表查询
- 存储数据可持久化
- 数据结构变化较快,数据模型无法确认
- 需要至少2000以上的读写QPS【高性能】
- 能支持快速水平扩展【高扩展】
- 99.999%高可用【高可用】
Mongodb的适用场景
游戏装备数据、游戏道具数据
- 特征:修改频度较高
物流行业数据
- 特征:地理位置信息,海量数据
直播数据、打赏数据、粉丝数据
- 特征:数据量大,修改频度极高
日志数据
- 特征:数据量巨大,结构多变
1.3 MongoDB体系与术语
MongoDB 的逻辑结构是一种层次结构。主要由: 文档(document)、集合(collection)、数据库(database)这三部分组成的。逻辑结构是面向用户的,用户使用 MongoDB 开发应用程序使用的就是逻辑结构。
1. MongoDB 的文档(document),相当于关系数据库中的一行记录。
2. 多个文档组成一个集合(collection),相当于关系数据库的表。
3. 多个集合(collection),逻辑上组织在一起,就是数据库(database)。
4. 一个 MongoDB 实例支持多个数据库(database)。 文档(document)、集合(collection)、数据库(database)的层次结构如下图:
与SQL中的概念进行对比:
| SQL术语/概念 | MongoDB术语/概念 | 解释/说明 |
| ------------ | ---------------- | ----------------------------------- |
| database | database | 数据库 |
| table | collection | 数据库表/集合 |
| row | document | 表中的一条数据 |
| column | field | 数据字段/域 |
| index | index | 索引 |
| table joins | | 表连接,MongoDB不支持 |
| primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
1.4 数据类型
数据格式:BSON {aa:bb}
- 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-字符保存到数据库中,二进制数据是唯一的方式。
1.5 通过docker安装MongoDB
在课程资料的虚拟机中已经提供了MongoDB的镜像和容器,我们只需要使用简单的命令即可启动
#进入base目录
cd /root/docker-file/base/
#批量创建启动容器,其中已经包含了redis,zookeeper,mongodb容器
docker-compose up -d
#查看容器
docker ps -a
或者直接安装MongoDB镜像:
拉取镜像
docker pull mongo
创建容器
docker run --name mongo-service -p 27017:27017 -v ~/data/mongodata:/data -d mongo
注意事项:
虚拟机中已经以docker-compose的形式提供了MongoDB容器。不需要重新拉取部署
企业中MongoDB往往需要配置操作用户密码,课程为了操作方便已经省略
可以看到mongoDB已经启动,对外暴露了27017的操作端口。
2、MongoDB入门
2.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() #删除数据库
2.2、新增数据
在MongoDB中,存储的文档结构是一种类似于json的结构,称之为bson(全称为:Binary JSON)。
#插入数据
#语法:db.表名.insert(json字符串)
> db.user.insert({id:1,username:'zhangsan',age:20})
> db.user.find() #查询数据
2.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)
2.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({})
2.5、查询数据
MongoDB 查询数据的语法格式如下:
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` |
实例:
#插入测试数据
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为正序
2.6、索引
索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。
这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构。
#创建索引
> db.user.createIndex({'age':1})
#查看索引
> db.user.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "testdb.user"
}
]
#说明:1表示升序创建索引,-1表示降序创建索引。
2.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进行查询
3.Springboot整合mongodb
Spring-data对MongoDB做了支持,使用spring-data-mongodb可以简化MongoDB的操作,封装了底层的mongodb-driver。
使用Spring-Data-MongoDB很简单,只需要如下几步即可:
*导入起步依赖
* 编写配置信息
* 编写实体类(配置注解 @Document,@Id)
* 操作mongodb
* **注入MongoTemplate对象,完成CRUD操作**
* 编写Repository接口,注入接口完成基本Crud操作
3.1 环境搭建
第一步,导入依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
第二步,编写application.yml配置文件
spring:
data:
mongodb:
uri: mongodb://192.168.136.160:27017/test
第三步,编写启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MongoApplication {
public static void main(String[] args) {
SpringApplication.run(MongoApplication.class, args);
}
}
3.2、完成基本操作
第一步,编写实体类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.mapping.Document;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(value="person")
public class Person {
private ObjectId id;
private String name;
private int age;
private String address;
}
第二步,通过MongoTemplate完成CRUD操作
import cn.itcast.mongo.MongoApplication;
import cn.itcast.mongo.domain.Person;
import org.bson.types.ObjectId;
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.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MongoApplication.class)
public class MongoTest {
/**
* SpringData-mongodb操作
* 1、配置实体类
* 2、实体类上配置注解(配置集合和对象间的映射关系)
* 3、注入MongoTemplate对象
* 4、调用对象方法,完成数据库操作
*/
@Autowired
private MongoTemplate mongoTemplate;
//保存
@Test
public void testSave() {
for (int i = 0; i < 10; i++) {
Person person = new Person();
person.setId(ObjectId.get()); //ObjectId.get():获取一个唯一主键字符串
person.setName("张三"+i);
person.setAddress("北京顺义"+i);
person.setAge(18+i);
mongoTemplate.save(person);
}
}
//查询-查询所有
@Test
public void testFindAll() {
List<Person> list = mongoTemplate.findAll(Person.class);
for (Person person : list) {
System.out.println(person);
}
}
@Test
public void testFind() {
//查询年龄小于20的所有人
Query query = new Query(Criteria.where("age").lt(20)); //查询条件对象
//查询
List<Person> list = mongoTemplate.find(query, Person.class);
for (Person person : list) {
System.out.println(person);
}
}
/**
* 分页查询
*/
@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)//设置每页查询条数
.skip(2) ; //开启查询的条数 (page-1)*size
List<Person> list = mongoTemplate.find(queryLimit, Person.class);
for (Person person : list) {
System.out.println(person);
}
}
/**
* 更新:
* 根据id,更新年龄
*/
@Test
public void testUpdate() {
//1、条件
Query query = Query.query(Criteria.where("id").is("5fe404c26a787e3b50d8d5ad"));
//2、更新的数据
Update update = new Update();
update.set("age", 20);
mongoTemplate.updateFirst(query, update, Person.class);
}
@Test
public void testRemove() {
Query query = Query.query(Criteria.where("id").is("5fe404c26a787e3b50d8d5ad"));
mongoTemplate.remove(query, Person.class);
}
}