MongoDB 入门
六月份到了,六月的第一周来写 MongoDB。
MongoDB 是一种面向文档的非关系型数据库,面向文档的意思是,把半结构化的数据存储为文档(XML、YAML、JSON 等),MongoDB 把数据存储为 BSON(Binary JSON, is a binary-encoded serialization of JSON-like documents)格式。
有关面向文档,看到这么一篇知乎文章:《常见 NoSQL 数据库的应用场景是怎么样的?》
在文档数据库中的“文档”和传统意义的“文档”没什么关系,它不是书、信或者文章,这里说的“文档”其实是一个数据记录,这个记录能够对包含的数据类型和内容进行“自我描述”。XML 文档、HTML 文档和 JSON 文档就属于这一类。文档数据库可以包含非常复杂的数据结构,比如嵌套对象并且不需要使用特定的数据模式,每个文档可以具有完全不同的结构。
如果做一个简单映射,那么关系型数据库的表
,就是 MongoDB 的集合(Collection)
;关系型数据库的行
,就是 MongoDB 的文档(Document)
,关系型数据库的这两行数据:
ID | NAME | AGE |
---|---|---|
1 | 张三 | 20 |
2 | 李四 | 22 |
就是 MongoDB 中的这两个文档:
1 | { |
术语和概念
SQL 术语和 MongoDB 术语之间的映射关系:
SQL Terms/Concepts | MongoDB Terms/Concepts |
---|---|
database | database |
table | collection |
row | document or BSON document |
column | field |
index | index |
table joins | $lookup, embedded documents |
primary key | primary key(在 MongoDB 中,primary key 是自动生成并设置为设置为 _id 字段的 |
aggregation (e.g. group by) | aggregation pipeline(聚合管道) |
一个样例文档(Document):
(对应着 SQL 中的一条数据)
1 | { |
语法
CRUD
实际上有很多函数可用,例如删除相关的函数有 delete
、deleteOne
、deleteMany
、remove
、findOneAndRemove
、findAndModify()
等,下面列出我自认为常用的,此外还可以参考官方文档(当页往下翻)。
MySQL | MongoDB | |
---|---|---|
查找 | select * from user | db.user.find() |
select name, age from user | db.user.find({},{name:1,age:1}) | |
select * from user where name = ‘ZhangSan’ | db.user.find({name:”ZhangSan”}) | |
select * from user where name is not null | db.user.find({name:{$ne:null}}) | |
select * from user where name like ‘%name%’ | db.user.find({name:{$regex:”name”}}) db.user.find({name:/name/}) |
|
select * from user order by age desc | db.user.find().sort({age:-1}) | |
select * from user group by age | 参见聚合 | |
增加 | INSERT INTO user (name, age) VALUES (‘pz’, 23) | db.user.insert({name:”pz”, age:23}) |
插入多条 | db.user.insertMany([ {name:”boy1”, age:20}, {name:”boy2”, age:18, tags:[“funny”]} ]) |
|
更改 | UPDATE user SET age=21 WHERE age=20 | db.user.updateMany({age:20},{$set:{age:21}}) |
UPDATE user SET age=21 WHERE age=20 limit 1 | db.user.update({age:20},{$set:{age:21}}) | |
删除 | delete from user where age = 20 | db.user.deleteMany({age:20}) |
delete from user where age = 20 limit 1 | db.user.delete({age:100}) |
聚合(aggregate)
聚合可以简单理解成高级操作,官方的解释有点绕:聚合处理数据并返回结果(Aggregation operations process data records and return computed results)。
聚合有三种类型,分别是聚合管道(Aggregate Pipeline)、Map-Reduce(一般不翻译)、单用途聚合操作(Single Purpose Aggregation Operations)。一般提到聚合,指的是第一种:聚合管道。
下面逐个学习这三种聚合。
1. 聚合管道
MongoDB 指的 pipeline 像是 Java 中的流运算,或者是 linux 系统中的管道,意思是文档(数据)经历一系列的节点(当然也可以是一个节点),一个节点接一个地处理文档,上个节点运算完的结果,作为下个节点的输入,最终输出一堆聚合文档,比如下面这个例子:
1 | db.user.aggregate( |
aggregate | 描述 | 样例 | 备注 |
---|---|---|---|
$project | 指定字段 | db.user.aggregate([ { $project:{name:1, age:1} } ]) | _id 默认指定,如果不想要,则需 _id:0 |
指定字段(起别名) | db.user.aggregate([ { $project:{alias:”$name”} } ]) | 别名在冒号前,字段名在冒号后 | |
$match | 匹配字段 | db.user.aggregate([ { $match:{age:{$gt:10}} } ]) (年龄大于10岁) |
|
$count | 计数 | db.user.aggregate([ { $count:”countNum” } ]) | 冒号后代表展示字段名 |
$group | 分组 | db.user.aggregate([ { $group:{_id:”$age”,nameList:{$addToSet:”$name”}} } ])(按年龄统计,获取名字列表) | _id 必填,指定要分组的字段,字段均需要带$ 符号,更多操作可参考 这篇博文 |
$sort | 排序 | db.user.aggregate([ { $sort:{age:1} } ]) | 1:升序、-1:降序 如果在 $sort 操作之前发生 $project 、$unwind 或 $group ,则 $sort 不能使用任何索引。 |
$lookup | 左连接 | db.user.aggregate([ { $lookup:{ from:”organization”, localField:”organization_name”, foreignField:”name”, as:”orgInfo” } } ]) | from :要关联的集合(表)、localField :本集合字段、foreignField :要关联的字段、as :新字段名 |
$limit | 只取前几条 | db.user.aggregate([ { $limit:10 } ]) | |
$skip | 跳过前N个文档 | db.user.aggregate([ { $skip:5 } ]) | |
$unwind | 一个文档(数据)拆成多个文档 | db.user.aggregate([ { $unwind:”$tags” } ]) | 注意 $ 符号 |
$sample | 随机选N个文档 | db.user.aggregate([ { $sample:{size:10} } ]) | |
$out | 将结果保存成新表 | db.user.aggregate([ { $limit:10 }, { $out:”test” } ]) (将 user 表的前 10 条数据存入 test 表中 |
|
$bucket | 分组 | db.user.aggregate([ { $bucket:{ groupBy:”$sort”, boundaries:[5,8,10,12], default:”other”, output:{ count:{$sum:1}, titles:{$push:”$sort”} } } } ]) | 难以描述,用得不多,现用现查 |
$graphLookup | 递归往上查 | { $graphLookup:{ from:”user”, startWith:”$parentId”, connectFromField:”parentId”, connectToField:”_id”, as:”fatherList” } } | 注意 startWith 后面跟的内容带 $ 符号,结果会按递归查到的顺序逆序排列 |
还有一些别的,用到了再回来补。
2. Map-Reduce
我目前用到的还不多,所以不想写了(hh),可以参考这篇文章《MongoDB Map-Reduce详细操作总结》,写得很容易理解概念。
以后需要用到了再回来补。
3. 单用途聚合操作
就是简化版的聚合管道,官方目前(Version 4.2)在文档中提到了三种,简单整理成下表:
MySQL | MongoDB | |
---|---|---|
计数 | select count(*) from user | db.collection.estimatedDocumentCount() |
db.user.count()(废弃) | ||
返回字段的所有值 | (有点像)select name from user group by name | db.user.distinct(“name”)(不理解的话点击看官方文档) |
除了这三种应该还有的是方法,但是我分辨不出来是不是属于这一类,因此先不整理了。
操作符
操作符 | 描述 | 示例 |
---|---|---|
$eq | 等于 | db.user.find({name:{$eq:”pz”}}) |
$ne | 不等于 | db.user.find({name:{$ne:null}}) |
$gt | 大于 | db.user.find({age:{$gt:10}}) |
$gte | 大于等于 | db.user.find({age:{$gte:10}}) |
$lt | 小于 | db.user.find({age:{$lt:10}}) |
$lte | 小于等于 | db.user.find({age:{$lte:10}}) |
$in | 匹配 array 中指定的任何值 | db.user.find({age:{$in:[10,20]}}) |
$nin | 匹配 array 中指定的任何值之外 | db.user.find({age:{$nin:[10,20]}}) |
$and | 并且 | db.user.find({$and:[{age:23},{name:”pz”}]}) |
$not | 非 | db.user.find({name:{$not:/name/}}) |
$nor | 都非 | db.user.find({$nor:[{name:”pz”},{age:10}]}) |
$or | 或 | db.user.find({$or:[{name:”pz”},{age:10}]}) |
$exists | 具有指定字段(包括null) | db.user.find({age:{$exists:true}}) |
$type | 字段是指定类型 | db.user.find({name:{$type:”string”}}) |
$regex | 匹配正则表达式 | db.user.find({name:{$regex:”^name.*$”}}) |
$expr | 允许在查询语言中使用聚合表达式 | db.user.find({$expr:{$gt:[“$age”,10]}}) |
$mod | 指定字段是取模成功的 | db.user.find({age:{$mod:[10,0]}}) |
剩下的用到再补,英文文档参考《Query and Projection Operators》,汉化文档(机翻,版本 v3.6)参考《查询和投影操作员》。
Spring Data MongoDB
数据库连接、事务之类的操作就先不学习了,本篇主要学习 CRUD。
Spring 操作 MongoDB 的类有两种(在我的工作中),分别是 MongoTemplate
和 ReactiveMongoTemplate
,前者是普通操作数据库,后者是响应式操作数据库(响应式以后应该会写博文),操作这两个类的 API 几乎是完全相同的,区别只在返回值上:
1 | // 插入一条数据,返回这个数据 |
下文以 MongoTemplate
为例,学习 Spring Data MongoDB(其实是一样的,区别只在响应式上)。
普通操作
作为 Spring Data 中的一族,Spring Data Mongo 的普通操作(CRUD)跟其他 ORM 框架操作很相近,用过几次就很熟悉了,这里不详细用语言描述了,只整理表格如下(表格下有备注):
类型 | 方法 | 样例(方法返回值不写了,写了不直观) | 描述 |
---|---|---|---|
查 | find | mongoTemplate.find(query, User.class); | |
mongoTemplate.find(query, User.class, “user”); | 指定表名,用于表和输出结果不匹配的情况 | ||
findById | mongoTemplate.findById(id, User.class); | ||
mongoTemplate.findById(id, User.class, “user”); | 指定表名,同find | ||
findOne | mongoTemplate.findOne(query, User.class); | ||
mongoTemplate.findOne(query, User.class, “user”); | 指定表名,同find | ||
findAll | mongoTemplate.findAll(User.class); | ||
mongoTemplate.findAll(User.class, “user”); | 指定表名,同find | ||
findDistinct | mongoTemplate.findDistinct(“name”, User.class, String.class) | 查指定字段(一共有四种,就写了两种) | |
mongoTemplate.findDistinct(query, “name”, User.class, String.class) | |||
计数 | count | mongoTemplate.count(query, User.class); | |
mongoTemplate.count(query, “user”); | |||
mongoTemplate.count(query, UserDto.class, “user”); | 指定输出类、表名 | ||
插入 | insert | mongoTemplate.insert(user); | 单条插入 |
mongoTemplate.insert(user, “user”); | |||
mongoTemplate.insert(userList, “user”); | 批量插入 | ||
mongoTemplate.insert(userList, User.class); | |||
insertAll | mongoTemplate.insertAll(userList); | ||
保存 | save | mongoTemplate.save(user); | 只能保存单条数据,有则更新,无则插入 |
mongoTemplate.save(user, “user”); | |||
更新 | updateFirst | mongoTemplate.updateFirst(query, update, User.class); | 修改第一条 |
mongoTemplate.updateFirst(query, update, “user”); | |||
mongoTemplate.updateFirst(query, update, UserDto.class, “user”); | 指定输出类、表名 | ||
updateMulti | mongoTemplate.updateMulti(query, update, User.class); | 修改所有 | |
mongoTemplate.updateMulti(query, update, “user”); | |||
mongoTemplate.updateMulti(query, update, UserDto.class, “user”); | 指定输出类、表名 | ||
更新或插入 | upsert | mongoTemplate.upsert(query, update, User.class); | 有则更新,无则插入 |
mongoTemplate.upsert(query, update, “user”); | |||
mongoTemplate.upsert(query, update, User.class, “user”); | |||
存在 | exists | mongoTemplate.exists(query, User.class); | |
mongoTemplate.exists(query, “user”); | |||
mongoTemplate.exists(query, User.class, “user”); | 第二个参数没有意义,可以为null, 查询时以第三个参数为依据 |
||
删除 | remove | mongoTemplate.remove(user); | |
mongoTemplate.remove(user, “user”); | |||
mongoTemplate.remove(query, User.class); | |||
mongoTemplate.remove(query, “user”); | |||
mongoTemplate.remove(query, User.class, “user”); |
备注:
查询时常用到
Query
和Criteria
类,这两个类我懒得写了,贴一点代码吧,看一看就懂了:1
2
3
4
5
6
7
8
9
10
11
12
13// 写法1
Query query = Query.query(Criteria.where("name").is("pz"));
// 写法2
Criteria criteria = Criteria.where("name").is("pz")
.and("age").is(23)
.and("height").gt(170);
Query query = new Query(criteria);
// 写法3
Criteria criteria = Criteria.where("name").is("pz");
Query query = new Query();
query.addCriteria(criteria);Update
类是跟Query
同等地位的类,用于 MongoDB 的更新操作,代码如下:1
2
3
4
5
6
7
8
9
10// 相当于{$set: {name: "pz"}},内部Java实现是return new Update().set(key, value);
Update update = Update.update("name", "pz");
Update update = new Update();
// 相当于{$set: {name: "pz"}}
update.set("name", "pz");
// 相当于{$push: {tags: "IT"}},即tags字段(列表)增加一个元素"pz"
update.push("tags", "IT");
// 相当于{$inc: {age: 2}},即age字段增加2
update.inc("age",2);find 还有几种方法:
findAndModify
、findAndRemove
、findAndReplace
、findAllAndRemove
,我觉得没什么用,没整理。
聚合操作
Spring Data MongoDB 涉及到聚合相关的类有四个,分别是:
Aggregation
:代表着 MongoDB 的 aggregate(聚合),例如下面这个例子1
2// 相当于db.user.aggregate([...])
mongoTemplate.aggregate(aggregation, User.class);TypedAggregation
:是 Aggregation 的子类,在 Aggregation 的基础上增加了类型,来指定是对哪张表进行聚合操作。1
2// 指定操作user表的aggregate
TypedAggregation<User> typedAggregation = Aggregation.newAggregation(User.class, operations);AggregationOperation
:聚合的具体操作,有很多子类,对应着一个个的MongoDB aggregation pipeline operation
,举例而言1
2// 相当于{$project: {a: 1, b: 1, thing2: $thing1}}
Aggregation.project("a", "b").and("thing1").as("thing2");AggregationResults
:聚合查询结果(非响应式)1
AggregationResults<User> result = mongoTemplate.aggregate(typedAggregation, User.class);
举一个例子,把 Aggregation
、AggregationOperation
、AggregationResults
串起来:
1 | // db.user.aggregate([ |
学习 Spring Data MongoDB 聚合,主要是学习 AggregationOperation
的所有子类,把各种聚合操作都熟悉之后,用 Aggregation.newAggregation()
组合在一起执行就可以了。
有关聚合的所有操作,基本都有两种创建方式:静态工厂方法和构造方法,官方推荐使用静态工厂方法创建,而非构造方法(尽管背后实现是完全相同的):
1 | // 官方推荐,这样更易于阅读 |
接下来一个个 AggregationOperation
说,把常用的聚合操作学习完。
1. ProjectionOperation
指定字段
1 | // { |
and()
需要配合as()
一起用,表示别名,不能单独使用(因为返回类型不一样)andInclude()
和and()
的区别是,前者单独使用,后者配合as()
使用
2. MatchOperation
查询(相当于 Query)
1 | // { |
3. LookupOperation
联表
1 | // { |
4. SortOperation
1 | // { |
5. LimitOperation
取前 N 条
1 | // { |
6. CountOperation
计数
1 | // { |
7. UnwindOperation
拆分字段,常见于联表查询之后
1 | // { |
8. SkipOperation
跳过前 N 条数据
1 | // { |
9. SampleOperation
随机取样 N 条数据
1 | // { |
10. GroupOperation
合并
1 | // { |
11. GraphLookupOperation
1 | // { |
这篇就先写到这里了。