2. MongoDB入门
在探花交友APP中,会涉及很多的数据保存,比如用户的动态,点赞信息,评论信息,位置信息等。这些数据有一下的特点:
- 海量数据
- 读多写少
- 数据价值低
- 地址位置相关数据
- 更新十分频繁
针对上述特点,传统的MySQL数据库有些力不从心,因此,我们引入了MongoDB。
2.1 MongoDB简介
MongoDB:是一个高效的非关系型数据库(不支持表关系:只能操作单表)
MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。
MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的,它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。
MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
2.1.1 MongoDB的存储模型
MongoDB采用的是内存和磁盘结合的存储方式,比较常用的数据都保存在内存中,如果内存中找不到数据,再去磁盘中查找,这样就加快了查询的速度。
为了保证数据的安全性,内存中的数据每一分钟都会同步到磁盘中。
此外,为了更加保护数据的安全性,MongoDB在新版本中引入了日志,日志在内存和硬盘中各一份,每10ms同步一次。这样即使断电的时候,内存中的数据没有保存到磁盘,也可以通过日志文件进行恢复,从而保证了数据的安全性。
但是即使这样,也不能MongoDB不能够保证数据100%不丢失。
此外,MongoDB支持数据分页技术,可以将保存的数据进行分页,从而可以非常方便的进行存储容量的扩充
2.1.2 MongoDB与其他数据库对比
- 和Redis对比
- Redis是纯内存数据库,存储容量有限
- Redis支持的查询比较简单,而MongoDB支持结构化查询
- 和MySQL对比
- MongoDB不支持事务和联表查询
- MongoDB支持动态字段,MySQL数据表一旦创建,很难修改字段
- 查询效率
- Redis > MongoDB > MySQL
2.1.3 MongoDB的应用
- 游戏装备数据
- 特征:修改频率很高
- 物流行业数据
- 特征:地理位置信息,海量数据
- 直播打赏数据
- 特征:数据量很大,修改频繁
- 日志数据
- 特征:数据量大,结构多变
2.2 MongoDB的数据和体系结构
下面是MongoDB和MySQL数据库概念上的对比
MongoDB中存储BSON数据,类似JSON
MongoDB 的逻辑结构是一种层次结构。主要由: 文档(document)、集合(collection)、数据库(database)这三部分组成的。逻辑结构是面 向用户的,用户使用 MongoDB 开发应用程序使用的就是逻辑结构。
- MongoDB 的文档(document),相当于关系数据库中的一行记录。
- 多个文档组成一个集合(collection),相当于关系数据库的表。
- 多个集合(collection),逻辑上组织在一起,就是数据库(database)。
- 一个 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字段设置为主键 |
MongoDB中常用的数据结构如下:
MongoDB中常见的数据类型有:
- 数据格式: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-字符保存到数据库中,二进制数据是唯一的方式。
2.3 MongDB的简单操作操作
2.3.1 新增数据
在MongoDB中,存储的文档结构是一种类似于json的结构,称之为bson(全称为:Binary JSON)。
#插入数据
#语法:db.表名.insert(json字符串)
db.user.insert({id:1,name:'zhangsan'}) #插入数据
2.3.2 更新数据
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.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.3.3 删除数据
通过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.3.4 查询数据
MongoDB 查询数据的语法格式如下:
db.user.find([query],[fields])
- query :可选,使用查询操作符指定查询条件
- fields :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。
条件查询:
操作 | 格式 | 范例 | RDBMS中的类似语句 |
等于 |
|
|
|
小于 |
|
|
|
小于或等于 |
|
|
|
大于 |
|
|
|
大于或等于 |
|
|
|
不等于 |
|
|
|
实例:
#插入测试数据
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、索引
索引能够极大提高查询的效率。类似于MySQL,MongoDB中叶提供了索引支持。在没有索引的时候,MongoDB需要便利整个集合,查询效率很低。
为MongoDB创建索引的命令如下,为age域设置一个递增的索引
#创建索引
#说明:1表示升序创建索引,-1表示降序创建索引。
> db.user.createIndex({'age':1})
3. MongoDB 集群
3.1 单机MongoDB存在的问题
单机MongoDB并不适用于企业场景,原因主要有以下两点:
- 单点故障:MongoDB一旦发生宕机,则会导致应用的崩溃
- 海量数据的存储问题:单机无法满足海量数据存储的需求。
为了解决上述的问题,便引入了MongoDB 集群。
3.2 MongoDB 集群的三种形式
- 主从集群(Master-Slaver):是一种主从副本模式,现在已经弃用。
- 副本集群(Replica Set):取代主从集群,可以解决单点故障的问题
- 分片集群(Sharding):可以解决单点故障和海量数据存储的问题。
3.3 副本集群
副本集群有一下特点:
- 一个集群中包含主节点和副本节点
- 主节点只能有一个,可以完成数据的读写操作
- 副本节点可以有多个,只能读取数据
- 多个节点之间有心跳感应,并进行数据同步
- 主节点宕机后,会自动从副本节点中选择一个成为主节点。
然而,尽管副本节点解决了单点故障的问题,但是并没有解决海量数据存储的问题。
3.4 分片集群
该模式适合存储海量数据,将数据分成不同的片,分别存储在不同的服务器上,从而实现容量的扩充。
分片集群包含三个重要的结构
- 分片服务:用于保存数据片
- 配置服务:当集群启动的时候,会读取分片的信息
- 路由服务:根据客户端的请求,到配置服务中取选择合适的分片服务,然后将请求转发到对应的分片服务上。
此外,我们发现,一旦某一个分片服务宕机,那么数据就不完整了。因此在实际应用中,通常是结合分片集群和副本集群。对每一个分片服务设立副本集群,从而提高集群的健壮性。
MongoDB通过分片策略决定将数据保存到哪一个分片上。MongoDB有两种分片策略,根据集合字段来指定。
- 范围指定:根据指定字段的数据按照范围进行划分,根据范围到不同的分片服务器读取数据
- 数据哈希:根据指定字段进行哈希运算获取分片服务器
如上图所示,可以根据年龄进行分片。可以指定1-35岁的保存在分片1
也可以以id分片,利用哈希
4. Springboot整合MongoDB
Spring-data对MongoDB做了支持,使用spring-data-mongodb可以简化MongoDB的操作,封装了底层的mongodb-driver。在Springboot中使用MongoDB的步骤如下:
- 导入依赖
- 编写配置信息
- 编写集合实体类
- 注入MongoTemplate对象,完成CRUD操作
导入依赖
<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>
编写application.yml
spring:
data:
mongodb:
uri: mongodb://192.168.136.160:27017/test
编写集合对应的实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(value="tb_person")
public class Person {
@Id
private ObjectId id;
@Field("myname")
private String name;
private int age;
private String address;
}
- 只要是MongoDB的集合实体类,都需要添加
@Document(value="XXX")
注解,在MongoDB中,集合的名称就为XXX- 在实体类中,需要指定MongoDB的文档的ID,这个ID由MongoDB自动生成,不会重复。在ID字段上使用
@Id
指定。ID的类型应为ObjectId
- 可以通过
@Field("myname")
来指定保存集合的域的名称,如果不指定,默认就以变量面作为域名
引入MongoTemplate
@Resource
private MongoTemplate mongoTemplate;
下面编写了一个测试类,来以Java代码的方式对MongoDB进行CRUD操作
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MongoApplication.class)
public class MongoTest {
@Resource
private MongoTemplate mongoTemplate;
@Test
public void testInsert() {
// 插入一条数据
Person person = new Person();
person.setAddress("上海");
person.setAge(23);
person.setName("Robert");
this.mongoTemplate.save(person);
}
@Test
public void testFindAll() {
// 查询所有数据
List<Person> all = this.mongoTemplate.findAll(Person.class);
all.forEach(System.out::println);
}
@Test
public void testFindByCondition() {
// 根据条件查询
Criteria criteria = Criteria.where("age")
.is(23).and("myname").is("Robert");
Query query = new Query(criteria);
List<Person> people = this.mongoTemplate.find(query, Person.class);
people.forEach(System.out::println);
}
@Test
public void testFindPage() {
int page = 2;
int size = 5;
// 条件分页
Criteria criteria = Criteria.where("age").lt(55);
Query query = new Query(criteria);
query.skip((page - 1) * size).limit(size)
.with(Sort.by(Sort.Order.desc("age")));
List<Person> people = this.mongoTemplate.find(query, Person.class);
people.forEach(System.out::println);
}
@Test
public void testUpdate() {
Query query = new Query(Criteria.where("id").is("63a431e998e6c17444113fc9"));
// updateFirst是更新满足条件的第一个记录
// 设置要更新的内容
Update update = new Update();
update.set("myname", "Jack");
this.mongoTemplate.updateFirst(query, update, Person.class);
}
@Test
public void testDelete() {
// 根据条件删除
Query query = new Query(Criteria.where("myname").is("Jack"));
this.mongoTemplate.remove(query, Person.class);
}
}