在现代的计算系统上每天网络上都会产生庞大的数据量, 这些数据有很大一部分是由关系数据库管 理系统(RDBMS)来处理。 1970年 E.F.Codd’s提出的关系模型的论文 “A relational model of data for large shared data banks”,这使得数据建模和应用程序编程更加简单。
通过应用实践证明,关系模型是非常适合于客户服务器编程,远远超出预期的利益,今天它是结构 化数据存储在网络和商务应用的主导技术。
NoSQL 是一项全新的数据库革命性运动,早期就有人提出,发展至2009年趋势越发高涨。NoSQL 的拥护者们提倡运用非关系型的数据存储,相对于铺天盖地的关系型数据库运用,这一概念无疑是一种 全新的思维的注入。
NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的 关系型数据库的数据库管理系统的统称。
NoSQL用于超大规模数据的存储。(例如谷歌或Facebook每天为他们的用户收集万亿比特的数 据)。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。
今天我们可以通过第三方平台(如:Google,Facebook等)可以很容易的访问和抓取数据。用户的 个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加。我们如果要对这些 用户数据进行挖掘,那SQL数据库已经不适合这些应用了, NoSQL 数据库的发展却能很好的处理这些大 的数据。
C(一致性):所有节点上数据时刻保持同步
A(可用性):每个请求都能得到响应,无论成功或失败
P(分区容错):系统应该能持续提供服务,即使系统内部有消息丢失(分区)
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需 求,最多只能同时较好的满足两个。
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大 类:
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需 求,最多只能同时较好的满足两个。
BASE是NoSQL数据库对可用性及一致性的弱要求原则:
MongoDB 是由C++语言编写的,是一个基于分布式文挡存储的开源数据库系统。
MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。
对比项 | mongo | 数据库 |
---|---|---|
table | collection | 数据库表/集合 |
row | document | 数据记录行/文档 |
column | field | 数据字段/域 |
index | index | 索引 |
table joins | 表连接,MongoDB不支持 | |
primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oFGahbfA-1689774476280)(https://cdn.nlark.com/yuque/0/2023/png/26194198/1687786037504-c04ccddf-82dc-447f-ab33-fd80e1838a96.png#averageHue=%23d4d8c5&clientId=u5db58dc5-9299-4&from=paste&height=340&id=ua6d15fe2&originHeight=340&originWidth=1196&originalType=binary&ratio=1&rotation=0&showTitle=false&size=89504&status=done&style=none&taskId=u53132599-5840-4ae3-b927-551137dd160&title=&width=1196)]
数据逻辑层次关系:文档=>集合=>数据库\
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-02CKGvev-1689774476282)(https://cdn.nlark.com/yuque/0/2023/png/26194198/1687786078353-41daa3cb-3411-41d9-952e-ad63b1df3974.png#averageHue=%23eff3f3&clientId=u5db58dc5-9299-4&from=paste&height=382&id=ua8df788a&originHeight=382&originWidth=421&originalType=binary&ratio=1&rotation=0&showTitle=false&size=122140&status=done&style=none&taskId=u8fc361bd-e135-4890-903e-66aca86f2fa&title=&width=421)]
下面我们对里面的每一个概念进行详细解释
一个mongoDB的实例可以运行多个database,database之间是完全独立的,每个database有自己的权限,每个database存储于磁盘的不同文件。
有一些数据库名是保留的,可以直接访问这些有特殊作用的数据库。
show dbs;
#创建tmpdb数据库
use tmpdb;
show dbs;
注意:在 MongoDB 中,只有在数据库中插入集合后才会创建! 就是说,创建数据库后要再插入一 个集合,数据库才会真正创建。
show dbs;
use tmpdb;
db;
#删除数据库
db.dropDatabase();
show dbs;
相当于关系数据库的表,不过没有数据结构的定义。它由多个document组成。
因为是无结构定义的,所以你可以把任何document存入一个collection里。每个collection用一个 名字标识,需要注意以下几点:
可以通过 db.createCollection(name,option) 创建集合
参数说明:
name: 要创建的集合名称
options: 可选参数, 指定有关内存大小及索引的选项
# 创建或选择tmpdb数据库
use tmpdb;
# 在db数据库创建一个blog的集合
db.createCollection("blog");
show collections;
show tables;
MongoDB 中使用 drop() 方法来删除集合 db.collection.drop()
mongoDB的基本单位,相当于关系数据库中的行,它是一组有序的key/value键值对,使用json 格式, 如:{“foo” : 3, “greeting”: “Hello, world!”}。
key是个UTF-8字符串,以下几点是需要注意的地方:
插入一条或多条数据需要带有允许插入多条的参数,这个方法目前官方已经不推荐了 注意:若插入的数据主键已经存在,则会抛 org.springframework.dao.DuplicateKeyException 异常,提示主键重复,不保存当前数据。
db.blog.insert({
"title": "MongoDB 教程",
"description": "MongoDB 是一个 Nosql 数据库",
"by": "我的博客",
"url": "http://www.baiyp.ren",
"tags": [
"mongodb",
"database",
"NoSQL"
],
"likes": 100
});
如果没有添加 _id 参数会自动生成 _id 值的,也可以自定义指定 _id
官方推荐的写法,向文档中写入一个文档
db.blog.insertOne({
"title": "MySql 教程",
"description": "Mysql是一个传统数据库",
"by": "我的博客",
"url": "http://www.baiyp.ren",
"tags": [
"Mysql",
"database"
],
"likes": 10000
});
该语句是进行批量插入的,可以直接进行批量插入
db.blog.insertMany([
{
"title": "MySql 教程1",
"description": "Mysql是一个传统数据库",
"by": "我的博客",
"url": "http://www.baiyp.ren",
"tags": [
"Mysql",
"database"
],
"likes": 10000
},
{
"title": "MySql 教程2",
"description": "Mysql是一个传统数据库",
"by": "我的博客",
"url": "http://www.baiyp.ren",
"tags": [
"Mysql",
"database"
],
"likes": 10000
}
]);
find 方法用于查询已存在的文档,MongoDB 查询数据的语法格式如下
db.blog.find();
db.blog.find().pretty();
我们查询 blog 表中 title=‘MySql 教程2’ 的数据
db.blog.find({
"title": "MySql 教程2"
}).pretty();
projection 选择可以控制某一列是否显示,语法格式如下 find({},{“title”:1}) 其中如果 title 是 1 则该列显示,否则不显示
// 只显示title列的数据
db.blog.find({"title":"MySql 教程2"},{"title":1}).pretty();
// 只显示title和description列的数据
db.blog.find({"title":"MySql 教程2"},{"title":1,"description":1}).pretty();
// 不显示 title和description列的数据
db.blog.find({"title":"MySql 教程2"},{"title":0,"description":0}).pretty();
update() 方法用于更新已存在的文档,更新的时候需要加上关键字 $set
db.blog.find({"_id":"1"});
db.blog.update({"_id":"1"},{$set:{"likes":666}})
save() 方法通过传入的文档来替换已有文档,_id 主键存在就更新,不存在就插入
db.blog.save({
"_id": "1",
"title": "MySql 传统教程教程3",
"description": "Mysql是一个传统数据库",
"by": "我的博客",
"url": "http://www.baiyp.ren",
"tags": [
"Mysql",
"database"
],
"likes": 100000
});
remove() 方法可以删除文档 db.blog.remove({"_id":"1"})
官方推荐使用 deleteOne() 和 deleteMany() 方法删除文档
删除单个文档
deleteOne 只会删除符合条件的第一个文档,和 remove({},true) 效果一致 db.blog.deleteOne({});
批量删除文档
deleteMany 可以进行批量删除文档,和 remove({}) 效果一致
db.blog.deleteMany({});
操作 | 格式 | 范例 | 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 }} | ||
大于 | {key:{$gt:value }} | ||
大于或 等于 | {key:{$gte:value }} | ||
不等于 | {key:{$ne:value }} | ||
包含 | {key:{$in:value }} | ||
不包含 | {key:{$nin:value }} | ||
判断字段存在 | {key:{“$exists” :true }} | ||
多条件查询 ( 有时候存在一个字段需要多个条件,比如 pop>=10 and pop<50 这个如何表示呢 ) | {key:{“ g t e " : 10 , " gte" :10," gte":10,"lte”:100 }} | db.zips.find({ "pop": { "$gte": 10, "$lt": 50 } }).pretty(); |
|
操作 | 格式 | 例子 |
---|---|---|
AND | {key:value,key:value} | db.zips.find({ “state”: “NY”, “pop”: { “$gt”: 100000 } }) |
OR | “KaTeX parse error: Expected '}', got 'EOF' at end of input: …ey": { "lt”: 0 } }] |
db.zips.find({ “KaTeX parse error: Expected '}', got 'EOF' at end of input: …p": { "lt”: 0 } }] }) |
db.zips.find({
"$or": [
{
"$and": [
{
"state": "NY"
},
{
"pop": {
"$gt": 10,
"$lte": 50
}
}
]
},
{
"$and": [
{
"state": {
"$in": [
"MD",
"VA"
]
}
},
{
"pop": {
"$gt": 10,
"$lte": 50
}
}
]
}
]
}).pretty();
### 对应sql
select * from zips where (state='NY' and pop>10 and pop <= 50) or (state
in('MD','VA') and pop>10 and pop <= 50)
在MongoDB中使用sort()方法对数据进行排序,sort()方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而-1是用于降序排列。
语法格式:db.COLLECTION_NAME.find().sort({KEY1:1,KEY2:-1,....})
MongoDB提供了skip()和limit()方法。
// 第一页数据
db.zips.find({},{"_id":1}).skip(0).limit(10);
// 第二页数据
db.zips.find({},{"_id":1}).skip(10).limit(10);
// 第三页页数据
db.zips.find({},{"_id":1}).skip(20).limit(10);
遇到的问题 看起来,分页已经实现了,但是官方文档并不推荐,说会扫描全部文档,然后再返回结果。
我们假设基于_id的条件进行查询比较,事实上,这个比较的基准字段可以是任何你想要的有序的 字段,比如时间戳 实现步骤如下 1. 对数据针对于基准字段排序 2. 查找第一页的最后一条数据的基准字段的数据 3. 查找超过基准字段数据然后向前找pagesize条数据
// 第一页数据
db.zips.find({},{_id:1}).sort({"_id":1}).limit(10);
// 第二页数据
db.zips.find({"_id":{$gt:"01020"}},{_id:1}).sort({"_id":1}).limit(10);
// 第三页数据
db.zips.find({"_id":{$gt:"01035"}},{_id:1}).sort({"_id":1}).limit(10);
ObjectId生成规则
比如 “_id” : ObjectId(“5b1886f8965c44c78540a4fc”)
Objectid = 时间戳(4字节) + 机器(3个字节)+ PID(2个字节)+ 计数器(3个字节)
取id的前4个字节。由于id是16进制的string,4个字节就是32位,1个字节是两个字符,4个字节对应 id前8个字符。即 5b1886f8 , 转换成10进制为 1528334072 . 加上1970,就是当前时间。
MongoDB的ObjectId应该是随着时间而增加的,即后插入的id会比之前的大。但考量id的生成规 则,最小时间排序区分是秒,同一秒内的排序无法保证。当然,如果是同一台机器的同一个进程生 成的对象,是有序的。
如果是分布式机器,不同机器时钟同步和偏移的问题。所以,如果你有个字段可以保证是有序的, 那么用这个字段来排序是最好的。 _id 则是最后的备选方案,可以考虑增加 雪花算法ID作为排序ID
count
db.zips.find({
"pop": {
"$not": {
"$gte": 10
}
}
}).count();
distinct
无条件排重
db.zips.distinct("state");
有条件排重
db.zips.distinct("state", {
"pop": {
"$gt": 70000
}
});
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
server:
port: 8080
spring:
application:
name: spring-boot-test
data:
mongodb:
database: test
host: 192.168.10.30
port: 27017
Blog类
@Document("blog")
public class Blog {
@Id
private String id;
private String title;
private String by;
private String url;
private List<String> tags;
private int likes;
setter getter ....
}
Dao
@Component
public class BlogDao {
@Autowired
private MongoTemplate mongoTemplate;
public void insert(Blog blog) {
mongoTemplate.insert(blog);
}
public Blog findByID(String id) {
return mongoTemplate.findById(id, Blog.class);
}
public void deleteByID(String id) {
mongoTemplate.remove(Query.query(Criteria.where("_id").is(id)),
Blog.class);
}
public List<Blog> find(Blog blog) {
if (null == blog) {
return null;
}
Criteria criteria = getFilter(blog);
return mongoTemplate.find(Query.query(criteria), Blog.class);
}
public Criteria getFilter(Blog blog) {
Criteria criteria = new Criteria();
if (!StringUtils.isEmpty(blog.getTitle())) {
criteria.andOperator(Criteria.where("title").is(blog.getUrl()));
}
if (!StringUtils.isEmpty(blog.getBy())) {
criteria.andOperator(Criteria.where("by").is(blog.getBy()));
}
if (!StringUtils.isEmpty(blog.getLikes())) {
criteria.andOperator(Criteria.where("likes").is(blog.getLikes()));
}
if (null != blog.getTags() && !blog.getTags().isEmpty()) {
criteria.andOperator(Criteria.where("tags").in(blog.getTags()));
}
return criteria;
}
}
Controller
@RestController
@RequestMapping("/blog")
public class WebController {
@Resource
private BlogDao blogDao;
@RequestMapping("/{id}")
@ResponseBody
public String getBlogInfo(@PathVariable("id") String id) {
Blog blog = blogDao.findByID(id);
if (null == blog) {
return "访问的数据不存在";
}
return JSON.toJSONString(blog);
}
@RequestMapping("/add")
@ResponseBody
public String addBlog(@RequestBody Blog blog) {
blogDao.insert(blog);
return JSON.toJSONString(blog);
}
public void batchAdd(){
}
}