MongoDB介绍
行业应用场景:
游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、更新
物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新
以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来
社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息
通过地理位置索引实现附近的人、地点等功能
物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析
视频直播,使用 MongoDB 存储用户信息、礼物信息等
性能优越:
在使用场合下,千万级别的文档对象,近10G的数据,对有索引的ID的查询不会比mysql慢
而对非索引字段的查询,则是全面胜出
mysql实际无法胜任大数据量下任意字段的查询(因为在没有索引的情况下,基本需要排除,因为不可能所有字段都设置索引)
而mongodb的查询性能实在牛x
MySQL、MongoDB简单的性能测试,网上看到一组测试数据,分享给大家
测试环境:Windows 10、内存8G、CPU i5 3.30GHZ,均无索引
测试语言:Python
链接工具:pymysql、pymongo
MySQL && Mongo 测试数据统计
虽然MongoDB基本上比Mysql好,但有些领域还是Mysql好的,并不能完全替换
实际上无论是Redis还是Mysql还是MongoDB都有各自擅长的领域,因为各有优点,也有缺点
MongoDB的优点:
弱一致性(最终一致,或者说是最终一致性),更能保证用户的访问速度:
当我们说外部一致性时,是针对分布式系统所说的CAP理论中的一致性
简单来说就是如何使得多台机器的副本保持一致
实际上Mongodb只能做到最终一致性,总会有"不一致时间窗口"
这是由于Mongodb在更新操作的时候,需要将同样的更新复制到副本节点当中,在将更新后的数据同步到其他副本时(由于单个副本更新是原子的,就如mysql的写锁一样,所以不会出现问题,但是其他副本会)
由于这段同步的时间无法保证reader读到的一定是最新数据(正是因为这样,使得访问速度快,因为不用等待同步)
即只能保证返回目前大多数节点的所持有的数据
而不一定是最新的数据(比如,只有primary节点更新完成,其它所有secondary节点都还没有更新完成就获取了)
文档结构的存储方式,能够更便捷的获取数据:
对于一个层级式的数据结构来说,如果要将这样的数据使用扁平式的,表状的结构来保存数据
这无论是在查询还是获取数据时都十分困难
举例1:就拿一个"字典项"来说,虽然并不十分复杂,但还是会关系到"定义"、“词性”、"发音"或是"引用"等内容
大部分工程师会将这种模型使用关系型数据库 中的主键和外键表现出来
但把它看作一个"文档"而不是"一系列有关系的表"岂不更好
使用 "dictionary.definition.partOfSpeech=‘noun’"来查询也比表之间一系列复杂(虽然往往代价也很高)的连接查询方便 且快速
举例2:在一个关系型数据库中,一篇博客(包含文章内容、评论、评论的投票)会被打散在多张数据表中
在MongoDB中,能用一个文档来表示一篇博客, 评论与投票作为文档数组,放在正文主文档中
这样数据更易于管理,消除了传统关系型数据库中影响性能和水平扩展性的"JOIN(连接)"操作
以下是后面的例子(可以不必理会):
db.blogposts.save({
title : "My First Post",
author: {name : "Jane", id :1},
comments : [{ by: "Abe", text: "First" },{ by
: "Ada", text : "Good post" }]
});
db.blogposts.find( { "author.name" : "Jane" } );
db.blogposts.findOne({
title : "My First Post",
"author.name": "Jane",
comments : [{ by: "Abe", text: "First" },{ by
: "Ada", text : "Good post" } ]
});
db.blogposts.find( { "comments.by" : "Ada" } );
举例3:MongoDB是一个面向文档的数据库,目前由10gen(也可叫Mongo)开发并维护
它的功能丰富,齐全,基本完全可以替代MySQL
在使用MongoDB做产品原型的过程中,我们总结了MonogDB的一些亮点:
使用JSON风格语法,易于掌握理解:MongoDB使用JSON的变种BSON作为内部存储的格式和语法
针对MongoDB的操作都使用JSON风格语法,客户端提交或接收的数据都使用JSON形式来展现
相对于SQL来说,更加直观,容易理解和掌握
Schema-less,支持嵌入子文档:MongoDB是一个Schemafree的文档数据库
一个数据库可以有多个Collection,每个Collection是Documents的集合
Collection和Document和传统数据库的Table和Row并不对等,无需事先定Collection,随时可以创建
Collection中可以包含具有不同schema的文档记录,这意味着
你上一条记录中的文档有3个属性,而下一条记录的文档可以有10个属 性
属性的类型既可以是基本的数据类型(如数字、字符串、日期等)
也可以是数组或者散列,甚至还可以是一个子文档(embed document)
这样,可以实现逆规范化(denormalizing)的数据模型,提高查询的速度
内置GridFS,支持大容量的存储:
GridFS是一个出色的分布式文件系统,可以支持海量的数据存储
内置了GridFS的MongoDB,能够满足对大数据集的快速范围查询
内置Sharding :
提供基于Range的Auto Sharding机制:一个collection可按照记录的范围,分成若干个段,切分到不同的Shard上
Shards可以和复制结合,配合Replica sets能够实现Sharding+fail-over,不同的Shard之间可以负载均衡
查询是对 客户端是透明的,客户端执行查询,统计,MapReduce等操作,这些会被MongoDB自动路由到后端的数据节点
这让我们关注于自己的业务,适当的 时候可以无痛的升级
MongoDB的Sharding设计能力最大可支持约20 petabytes(PB,1PB=1024TB,1TB=1024GB),足以支撑一般应用
保证MongoDB运行在便宜的PC服务器集群上,PC集群扩充起来非常方便并且成本很低
避免了"sharding"操作的复杂性和成本
第三方支持丰富:
(这是与其他的NoSQL相比,MongoDB也具有的优势)
现在网络上的很多NoSQL开源数据库完全属于社区型的,没有官方支持,给使用者带来了很大的风险
而开源文档数据库MongoDB背后有商业公司10gen为其提供供商业培训和支持
而且MongoDB社区非常活跃,很多开发框架都迅速提供了对MongDB的支持
不少知名大公司和网站也在生产环境中使用MongoDB
越来越多的创新型企业转而使用MongoDB作为和Django,RoR来搭配的技术方案
MongoDB的缺点:
事务支持不友好:
所以事务要求严格的系统(如果银行系统)肯定不能用它
这点和优点中弱一致性(最终一致)是对应的,因为可能操作的数据还是同一个
占用空间过大 :
关于其原因,在官方的FAQ中,提到有如下几个方面:
1、空间的预分配:为避免形成过多的硬盘碎片,mongodb每次空间不足时都会申请生成一大块的硬盘空间
而且申请的量从64M、128M、256M那 样的指数递增,直到2G为单个文件的最大体积
随着数据量的增加,你可以在其数据目录里看到这些整块生成容量不断递增的文件
2、字段名所占用的空间:为了保持每个记录内的结构信息用于查询
mongodb需要把每个字段的key-value都以BSON的形式存储,如果 value域相对于key域并不大
比如存放数值型的数据,则数据的overhead是最大的,一种减少空间占用的方法是把字段名尽量取短一些
这样占用 空间就小了,但这就要求在易读性与空间占用上作为权衡了
我曾想过把字段名做个index,每个字段名用一个字节表示,这样就不用担心字段名取多长 了
但作者的担忧也不无道理,这种索引方式需要每次查询得到结果后把索引值跟原值作一个替换,再发送到客户端
这个替换也是挺耗费时间的,现在的实现算是拿空间来换取时间吧
3、删除记录不释放空间:这很容易理解,为避免记录删除后的数据的大规模挪动,原记录空间不删除
只标记"已删除"即可,以后还可以重复利用。
4、可以定期运行db.repairDatabase()来整理记录,但这个过程会比较缓慢
安装与配置:
安装:
官网: www.mongodb.org
下载社区版 MongoDB 4.1.3(但是我们可以直接使用Linux来进行下载)
[root@A opt]
将压缩包解压即可:
[root@A opt]
启动:
[root@A mongodb-linux-x86_64-4.1.3]
[root@A opt]
配置文件样例:
在mongo的根目录下(好像随便一个目录都可以,因为只需要指定目录即可)创建配置文件mongo.conf:
[root@A mongodb-linux-x86_64-4.1.3]
dbpath=/data/mongo/
port=27017
bind_ip=0.0.0.0
fork=true
logpath=/data/mongo/MongoDB.log
logappend=true
auth=false
指定配置文件方式的启动:
[root@A mongodb-linux-x86_64-4.1.3]
使用mongo shell进入mongo(可以输入命令的客户端):
[root@A /]
指定主机和端口的方式进入mongo(跟上面一样的进入,只是命令不同):指定ip和端口
[root@A mongodb-linux-x86_64-4.1.3]
Mongodb GUI(图形用户界面)工具:
MongoDB Compass Community :
MongoDB Compass Community由MongoDB开发人员开发,这意味着更高的可靠性和兼容性
它为MongoDB提供GUI mongodb工具,以探索数据库交互,具有完整的CRUD功能并提供可视方案
借助内置模式可视化,用户可以分析文档并显示丰富的结构,为了监控服务器的负载,它提供了数据库操作的实时统计信息
就像MongoDB一样,Compass也有两个版本,一个是Enterprise(付费),社区可以免费使用
适用于Linux,Mac或Windows
NoSQLBooster(mongobooster) :
NoSQLBooster是MongoDB CLI界面中非常流行的GUI工具,它正式名称为MongoBooster
NoSQLBooster是一个跨平台,它带有一堆mongodb工具来管理数据库和监控服务器
这个Mongodb工具包括服务器监控工具,Visual Explain Plan,查询构建器,SQL查询,ES2017语法支持等等…
它有免费,个人和商业版本,当然,免费版本有一些功能限制,NoSQLBooster也可用于Windows,MacOS和Linux
数据库基本操作:
下载图形化界面,下载地址:
官网:https://www.nosqlbooster.com/
由于前面的配置中,设置了不用账号密码登录,所有只需要连接即可
点击左上角的三角形,然后出现From URL进入,将localhost修改成自己的对应启动MongoDB的主机即可连接(可以进行测试)
连接后出现:
点击主机名称那里,右键,选择Run SQL Query…即可打开命令行的操作
注意:这个榆Mysql不一样,这是肯定的,基本没有什么结束符号
比如show dbs,可以执行,但是show dbs;不能执行,多一个分号,所有最好不要加分号,下面的对应命令中,也基本并不会加分号
若有分号,可以自行删除,当然有些是可以加的
因为有自带结束的操作,比如db.dept.find(),且有些自带开始,比如var,实际上基本都会自带开始
但最好是不加,且要换行的执行,这样基本不会出现问题
查看数据库:
show dbs
切换数据库 (对于sql界面来说的显示,可以将MongoDB的命令看成sql)
如果没有对应的数据库则创建(新建的库默认不显示,必须要插入一个文档也就是数据后才会显示,自连接的客户端也是一样的)
use 数据库名
db.laosun.insert({"did":1,"dname":"开发部","loc":"A区1座"})
数据库里有集合(可以看成表,集合里面有数据,上面的命令中{}里面的数据)
创建集合:
db.createCollection("集合名")
查看集合(下面两条命令结果一样):
show tables
show collections
删除集合:
db.集合名.drop()
删除数据库:
db.dropDatabase()
添加:
插入一条数据,自动创建dept集合(集合中的数据格式,就是json格式)
db.dept.insert(
{
"did":1,
"dname":"开发部",
"loc":"A区1座"
})
查看集合中的所有数据:
db.dept.find()
插入数据:
var dataVar = {
"did":2,
"dname":"市场部",
"loc":"深圳",
"count":20
}
db.dept.insert(dataVar)
数据随便定义,所以基本没法(有)查看集合结构的方法,没有固定结构:
var dataVar = {
"did":2,
"dname":"市场部"
}
db.dept.insert(dataVar)
MongoDB每一行记录都有 _ id,这个_ id是由 时间戳 + 机器码 + 进程的pid + 计数器
这个_id值是绝对不会重复(基本上)的,这个ID信息是MongoDB为用户服务的,不能被改变
插入数组数据:
db.dept.insert([
{"did":3,"dname":"人事部"},
{"did":4,"dname":"保洁部"}
])
for(var i = 1; i<100;i++){
db.dept.insert({"did":i,"dname":"测试"+i+"部"});
}
MongoDB是部分显示数据,一般默认每页显示20条数据
其中使用查询语句一般可以全部查询,但直接的点击进去,一般只会显示100条数据(应该可以改变)
假设忘记了命令,那么可以点击如下:
可以看到命令的模板,一般是根据当前谁打开的命令界面进行模板的默认,可以自己测试
最后注意: " 和 ’ 都可以当成字符串的符号,但不能交替使用,且key-value中,key不使用的话
一般会默认变成字符串(可以相当于默认加上""),但value却要防止变量的识别,即一般不能写字母
删除 :
remove( ) 有两个可选项:
满足条件的删除
是否只删除一条,true或1,就是删除一条
删除指定的某个数据:
db.dept.remove({
"_id" : ObjectId("6114d0d2430f6b4dcc876c41")
})
db.dept.remove(
{id:3},
{justOne: 1}
)
{id:3},
1
)
删除名称中含有"测试"的数据,默认会将匹配的数据全部删除:
db.dept.remove({
"dname" : /测试/
})
删除名称中含有"测试"的数据,要求只删除一条:
db.dept.remove({
"dname" : /测试/
},true)
清空集合中的全部数据:
db.dept.remove({})
删除集合:
db.dept.drop()
查看当前所在的数据库:
db
删除当前所在的数据库:
db.dropDatabase()
修改:
关系型数据库的数据更新,对于Mongo而言,最好的办法不是修改,而是删掉,重新添加
若要进行修改,那么可以输入如下命令:
db.laosun.update(
{"id":55},
{"id":88}
)
db.laosun.update(
{"id":55},
{$set:{"id":88}},
false,
true
)
实际上上面的更新操作用的就是函数,接下来说明一下该函数
函数:
update语法:
db.集合.update(更新条件 , 新对象数据 , upsert , multi)
upsert:函数的动作,修改还是添加,有如下的两个参数:
false:如果没有匹配的数据,啥也不干
true:如果没有匹配的数据,执行添加操作
multi:如果条件匹配多个结果,有如下的两个参数:
false:匹配项只修改一行,默认的
true:匹配项全部修改
实际上大多数的参数都是{}的,而不是字符串和数组
修改数据根据_Id:
var dataVar = {
"id":1,
"name":"吕布",
"age":29,
"address":"灵界",
}
db.student.insert(dataVar)
var dataNew = {
"id":1,
"name":"吕小布",
"age":29,
"address":"灵幻界",
}
db.student.update({
"_id": ObjectId("6114df1d430f6b4dcc876c42"),
},dataNew)
修改年龄27岁的人的地址为"大东北":
db.student.update(
{"age":27},
{"$set":{"address":"大东北"}},
false,
false
)
修改99岁的名字为"上仙",如果没有99岁的人,则添加本条数据
db.student.update(
{"age":99} ,
{"$set":{"name":"上仙"}},
true,
false
)
只修改 27岁的第一条 地址为"东北三省"
db.student.update(
{"age":27},
{"$set":{"address":"东北三省"}},
true,
false
)
修改全部 27岁的地址为"东北三省":
db.student.update(
{"age":27},
{"$set":{"address":"东北三省"}},
true,
true
)
修改器:
初始化一条测试数据:
db.student.insert({
"name":"地三鲜",
"age":88,
"score":99
})
$inc主要针对数字字段,增加某个数字字段的值,如果不是数字字段,那么会报错
当然需要首先匹配成功再说(第一个参数,一般指方法里的第一个{},不包括里面的),因为先后顺序
语法:{“$inc”:{“成员”:增减量}
修改年龄88岁的人,年龄+2岁,评分-10分
db.student.update(
{"age":88},
{
"$inc":{
"age":2,
"score":-10
}
}
)
$set对内容重新设定(相同的设置,不同的创建):
语法:{“$set”:{“成员”:新的内容}
将90岁的人,评分设置为90分
db.student.update(
{"age":90},
{
"$set":{
"score":100
}
}
)
$unset删除某个成员:
语法:{“$unset”:{“成员”:1}
删除”地三鲜”的年龄和分数信息
db.student.update(
{"name":"地三鲜"},
{
"$unset":{
"age":1,
"score":1
}
}
)
$push将指定内容追加到指定成员(基本上是数组,追加的基本都是数组):
语法:{“$push”:{“成员”:value}
给"地三鲜"添加一门"毛概"(此时地三鲜没有课程信息)
db.student.update(
{"name":"地三鲜"},
{
"$push":{
"subject":"毛概"
}
}
)
给"地三鲜"追加一门"邓论"
db.student.update(
{"name":"地三鲜"},
{
"$push":{
"subject":"邓论"
}
}
)
$pushAll追加数组的方式已经在高版本中淘汰了(好像将pushAll来替换each后,执行会报错)
可以使用$each添加一个数组到属性值中
添加一道菜"毛血旺",在多追加两种配菜"[“豆腐”,“油菜”]"
db.student.insert({"name":"毛血旺"})
db.student.update(
{"name":"毛血旺"},
{
"$push":{
"cai":{
"$each":["豆腐","鸭血","豆芽"]
}
}
}
)
db.student.update(
{"name":"毛血旺"},
{
"$push":{
"caiii9":["豆腐","鸭血","豆芽"]
}
}
)
$addToSet追加到数据内容,先判断是否存在,如果存在,不做任何修改,如果不存在,则会追到到数组内容中
语法:{“$addToSet”:{“成员”:内容}
向"毛血旺"多加一道配菜"豆腐"
db.student.update(
{"name":"毛血旺"},
{
"$addToSet":{
"cai":"豆腐"
}
}
)
db.student.update(
{"name":"毛血旺"},
{
"$addToSet":{
"cai":"油菜"
}
}
)
$pop删除数组内的数据:
语法:{“$pop”:{“成员”:数组}
删除地三鲜的第一门课程
db.student.update(
{"name":"地三鲜"},
{
"$pop":{
"subject":-1
}
}
)
删除地三鲜的最后一门课程:
db.student.update(
{"name":"地三鲜"},
{
"$pop":{
"subject":1
}
}
)
1就是最后,-1就是第一
$pull从数组中删除指定数据,存在,删除,不存在,没变化
语法:{“$pull”:{“成员”:数组}
删除毛血旺配菜中的鲍鱼,不存在鲍鱼,所有没变化
db.student.update(
{"name":"毛血旺"},
{
"$pull":{
"cai":"鲍鱼"
}
}
)
删除毛血旺配菜中的油菜,油菜存在,所有删除
db.student.update(
{"name":"毛血旺"},
{
"$pull":{
"cai":"油菜"
}
}
)
$pullAll一次性删除多个内容
语法:{“$pullAll”:{“成员”:[“值1”,“值2”,…]}
删除孙大仙的毛概和计算机两门课
db.student.insert({
"name":"孙大仙",
"subject":["毛概","邓论","计算机"]
})
db.student.update(
{"name":"孙大仙"},
{
"$pullAll":{
"subject":["毛概","计算机"]
}
}
)
$rename为成员名称重命名
语法:{“$rename”:{“旧的成员名称”:“新的成员名称”}
将毛血旺的 “cai"改成"配菜”
db.student.update(
{"name":"毛血旺"},
{
"$rename":{
"cai":"配菜"
}
}
)
实际上:key可以[]单个字符串,其他基本不可以,且对应的key-value好像自带结束
也就是说执行到上面的配菜后面的 " 就可以了,可以自己尝试一下(可能是语法不严谨,或者自带补全等等)
查询:
语法:db.dept.find({查询条件})
查看一条数据:
db.dept.insert({
"did":1,
"dname":"开发部",
"address":"北京海淀"
})
db.dept.insert({
"did":2,
"dname":"市场部",
"address":"上海虹桥"
})
db.dept.findOne()
查询did=1的数据:
db.dept.find({
"did":1
})
投影查询(查询部分属性,控制属性的显示)did=1的数据,其中控制属性的显示的对于key的值可以是,0不显示,1显示
实际上除了0,其他的只要可以符合写入,那么都算作显示,比如2,-1,"a"等,只是1更加直观
投射里,要么都不是0(一般都设置为1),要么全是0
否则就报错:“Projection cannot have a mix of inclusion and exclusion.”(投影不能同时包含和排除)
可能_id不受限制(但通常都会受限制)
db.dept.find(
{"did":1},
{"dname":1}
)
db.dept.find(
{"did":1},
{"dname":1,"address":1}
)
漂亮的显示,列太少看不出来效果,不妨自己动手,多创建些列,使得列多一些
在命令行模式下,会格式化bson
db.dept.find(
{"did":1},
{"dname":1,"address":1}
).pretty()
findOne()自带格式化功能,也是列多才有效果
db.dept.findOne()
关系运算:
初始化数据:
db.student.drop();
db.student.insert({"name":"张三","sex":"男","age":19,"address":"高碑店"})
db.student.insert({"name":"李四","sex":"女","age":18,"address":"海淀区"})
db.student.insert({"name":"王五","sex":"女","age":21,"address":"东城区"})
db.student.insert({"name":"赵六","sex":"女","age":21,"address":"高碑店"})
db.student.insert({"name":"孙七","sex":"男","age":22,"address":"东城区"})
db.student.insert({"name":"田八","sex":"女","age":22,"address":"王府井"})
db.student.insert({"name":"钱九","sex":"男","age":23,"address":"东城区"})
db.student.insert({"name":"周十","sex":"男","age":17,"address":"高碑店"})
查询女同学:
db.student.find({
"sex":"女"
})
查询年龄大于18岁的学生(json格式中套用json格式),实际上大多数的参数都是{}的:
db.student.find({
"age": {
"$gt": 18
}
})
db.student.find({
"age": {
"$gt": 18
},
"sex":"女"
})
逻辑运算:
db.student.find({
"age": {
"$gte": 18,
"$lte": 20
},
})
18岁以上,或者性别是女的同学
db.student.find({
"$or":[
{"age":{"$gt":18}},
{"sex":{"$eq":"女"}}
]
})
db.student.find({
"$nor":[
{"age":{"$gt":22}},
{"sex":{"$eq":"女"}}
]
})
db.student.find( {
"age":{
"$not": {
"$lt":21
}
}
})
求模:
求模(求余数)
年龄正好是2的倍数,也就是age%2 == 0
db.student.find({
"age":{
"$mod":[2,0]
}
})
db.student.find({
"age":{
"$mod":[20,1]
}
});
范围:
只要是数据库,必然会存在这些操作(下面两个):
在范围内 $in
不在范围内 $nin
姓名是张三,李四,王五的范围查询
db.student.find({
"name":{
"$in":["张三","李四","王五"]
}
})
范围取反:
db.student.find({
"name":{
"$nin":["张三","李四","王五"]
}
})
数组查询 :
初始化数据:
db.student.insert({"name":"老孙 - A","sex":"男","age":17,"address":"中关村1",
"subject":["语文","数学","英语"]})
db.student.insert({"name":"老孙 - B","sex":"女","age":18,"address":"中关村2",
"subject":["语文","数学","英语","物理"]})
db.student.insert({"name":"老孙 - C","sex":"男","age":19,"address":"中关村3",
"subject":["语文","数学","英语","物理","化学"]})
db.student.insert({"name":"老孙 - D","sex":"女","age":20,"address":"中关村4",
"subject":["物理","化学"]})
db.student.insert({"name":"老孙 - E","sex":"男","age":21,"address":"中关村5",
"subject":["语文","数学","化学"]})
查询同时参加 语文 和 数学 课程的学生:
db.student.find({
"subject" : {
"$all" : ["语文","数学"]
}
})
查询地址是"中关村3"的学生的另一种写法:
db.student.find({
"address" : {
"$all" : ["中关村3"]
}
})
现在,集合中保存的信息是数组信息,那么我们就可以使用索引查询了key.index
查询第二门(index=1)课程是数学的学生
db.student.find({
"subject.1" : "数学"
})
查询只参加两门课程的学生:
db.student.find({
"subject" : {
"$size": 2
}
})
有些的$操作,一般格式最好不要修改,数组就是数组,单个值就是单个值
作用对象也不要进行修改,不同的对象,一般不会操作,甚至可能报错
比如$size一般只会操作数组,若操作字符串,若是负数一般会报错
因为他们本来就是这样的,一般修改后,可能不会操作(比如单个值,看到数组或者{},一般会当成整体),甚至会报错
所有最好不要修改他们规定的格式
查询年龄19岁,但是课程只返回前两门课程,使用$slice关键字:
db.student.find(
{"age":19},
{
"subject" : {
"$slice":2
}
})
返回后两门课程:
db.student.find(
{"age":19},
{
"subject" : {
"$slice":-2
}
})
返回中间的课程 索引1开始(包含索引),返回2门课:
db.student.find(
{"age":19},
{
"subject" : {
"$slice": [1,2]
}
})
嵌套集合 :
初始化数据,养宠物
db.student.insert({
"name":"孙大仙",
"sex":"男",
"age":31,
"address":"天宫院",
"subject":["java","mongo","html"],
"pets":[
{
"name":"大G",
"age":3,
"brand":"狗"
},
{
"name":"大咪",
"age":4,
"brand":"猫"
}
]
})
db.student.insert({"name":"乔丹","sex":"男","age":47,"address":"芝加哥","subject":["数学","英语"],
"pets":[
{"name":"杰瑞","age":2,"brand":"鼠"},
{"name":"山姆","age":3,"brand":"猫"}
]})
db.student.insert({"name":"kobe","sex":"男","age":40,"address":"洛杉矶","subject":["英语","篮球"],
"pets":[
{"name":"克里斯提娜","age":2,"brand":"金丝雀"},
{"name":"雪瑞","age":12,"brand":"马"}
]})
查询20岁以上并且养猫的人:
db.student.find(
{
"age": {
"$gte":20
},
"pets" : {
"$elemMatch" : {
"brand":"猫"
}
}
}
)
字段是否存在判断 :
查询养宠物的学生(不养的就是false)
db.student.find({
"pets":{
"$exists" : true
}
})
条件过滤where:
年龄40岁以上的学生
db.student.find({
"$where":"this.age > 40"
})
db.student.find({
$where: "this.age > 40"
})
db.student.find("this.age>=40");
查询30到40岁之间的同学:
db.student.find({
$and:[
{$where:"this.age > 30"},
{$where:"this.age < 40"}
]
})
这样的写法,可以实现数据的查询
但是最大的缺点是将MongoDB中的BSON数据转成了Javascript语法结构循环验证(比如this.age > 30)
这样的话,不方便使用数据库的索引机制,并不推荐使用
因为MongoDB中索引的效果,对查询的提升是相当明显的
正则运算:
这里想模糊查询,基本必须要使用正则
姓名包含孙字的学生(不要加双引号):
db.student.find({
"name" : {
"$regex" : /孙/
}
})
以下用简略的写法:
db.student.find({
"name" : /孙/
})
姓名中包含字母a,忽略大小写:
db.student.find({
"name" : /a/i
})
数组查询也同理,查询学习课程有化字的
db.student.find({
"subject" : /化/
})
排序:
年龄升序排列:
db.student.find().sort({
"age" : 1
})
年龄降序排列:
db.student.find().sort({
"age" : -1
})
自然排序:数据保存的先后顺序
最新添加的靠前(从上到下的看,靠前):
db.student.find().sort({
"$natural" : -1
})
分页:
skip(n):跨过多少行 (相对于下标从0开始来说,正好就是,比如说跨0行,那么下标也就是0,跨5行,那么下标也就是5了)
limit(n):每页数量
年龄排序:第1页
db.student.find().skip(0).limit(5).sort({
"age":1
})
年龄排序:第2页
db.student.find().skip(5).limit(5).sort({
"age":1
})
游标 :
所谓的游标,就是让数据一行行的操作,类似与resultSet数据结构
使用find()函数返回游标 var you = db.student.find()
要想操作返回的游标,我们有两个函数可以使用
hasNext() 判断是否有下一行数据(在前面开始,类似于java的迭代器)
next() 取出当前数据
循环取值:
var you = db.student.find()
while(you.hasNext()){
print(you.next().name)
}
按照json格式打印:
var you = db.student.find()
while(you.hasNext()){
printjson(you.next())
}
索引:
与以往的数据库一样,加快检索性能
索引分为两种,一种自动创建的,一种手动创建的
一般索引可以先行创建,而不用先创建数据,因为他只是用来进行操作的(对比操作)
且索引创建时,若没有对应的集合,那么也会顺便创建出来对应集合
准备一个简单的集合:
db.person.insert({"name":"吕布","sex":"男","age":19,"address":"高碑店"})
db.person.insert({"name":"赵云","sex":"女","age":18,"address":"海淀区"})
db.person.insert({"name":"典韦","sex":"女","age":21,"address":"东城区"})
db.person.insert({"name":"关羽","sex":"女","age":21,"address":"高碑店"})
db.person.insert({"name":"马超","sex":"男","age":22,"address":"东城区"})
db.person.insert({"name":"张飞","sex":"女","age":22,"address":"王府井"})
db.person.insert({"name":"黄忠","sex":"男","age":23,"address":"东城区"})
db.person.insert({"name":"夏侯惇","sex":"男","age":17,"address":"高碑店"})
查看当前集合下的索引:
db.person.getIndexes()
结果:
v:版本,1代表是升序,name:索引名,ns:所属数据库的集合
我们发现,有默认的索引,即对应的_id是升序的
这大概是作用与没有设置排序的查询吧,因为直接的查看,是降序,而find()就是升序
创建一个索引:
语法:db.person.ensureIndex({列:1})
为age列创建一个降序索引
db.person.ensureIndex({
"age" : -1
})
再次查看:索引的名字是自动命名的
删除一个索引:
db.person.dropIndex({
"age" : -1
})
删除全部索引,默认的索引不能删除
db.person.dropIndexes()
唯一索引 :
用在某一个字段,让该字段的内容不能重复
让name不能重复,添加重复的name数据,报错
db.user1.insert({
"name":"a1"
})
db.user1.find()
db.user1.ensureIndex(
{
"name":1
},
{
"unique":true
}
)
过期索引:
程序会出现若干秒之后,信息删除
这个间隔时间大多数情况并不是很准确
设置10秒后,索引过期(如果觉得时间太长,可以自己调低一点)
db.phones.ensureIndex(
{"time":1},
{"expireAfterSeconds":10}
)
添加数据:
db.phones.insert({"num":110,"time": ISODate("2020-01-01T22:55:13.369Z")})
db.phones.insert({"num":119,"time": ISODate("2020-01-02T22:55:13.369Z")})
db.phones.insert({"num":120,"time": ISODate("2020-01-03T22:55:13.369Z")})
db.phones.insert({"num":114,"time": new Date()})
10秒(时间不一定准确,应该说一段时间后)后,再次查询,数据全都消失了
对于过期索引,有如下要注意:
MongoDB的注释就是 //
全文索引:
在以往的应用中,经常会用到模糊查询,而模糊查询往往并不是很准确,因为他只能查询A列或者B列
简单来说,就是只能操作一个字段,而不能操作多个字段,为了可以操作多个字段,则出现了全文索引
全文索引就来解决这个问题
创建一个新的集合:
db.news.insert({"title":"NoSQL","nei":"MongoDB"})
db.news.insert({"title":"js java","nei":"前端技术"})
db.news.insert({"title":"编程语言","nei":"java"})
db.news.insert({"title":"java","nei":"好语言"})
db.news.insert({"title":"java","nei":"java"})
设置全文索引:
db.news.ensureIndex({
"title":"text",
"nei":"text"
})
进行模糊查询
查询单个内容:
db.news.find({
"$text":{
"$search":"java"
}
})
查询java或js的新闻:
db.news.find({
"$text":{
"$search":"java js"
}
})
查询同时包含java和js的新闻:
db.news.find({
"$text":{
"$search":"\"java\" \"js\""
}
})
查询包含java,但是不包含js的内容:
db.news.find({
"$text":{
"$search":"java -js"
}
})
MongoDB的特色:在进行全文检索的时候,还可以使用相似度打分的机制来检验查询的结果
给相似度打分:
db.news.find(
{
"$text":{
"$search":"java"
}
},
{
"score":{
"$meta":"textScore"
}
}
)
分值越大,越相似,也可以进行打分的排序(降序)
db.news.find(
{
"$text":{
"$search":"java"
}
},
{
"score":{
"$meta":"textScore"
}
}
).sort({
"score":{
"$meta":"textScore"
}
})
为所有字段设置全文索引(先删掉全部索引,再创建):
db.news.dropIndexes()
db.news.ensureIndex({"$**":"text"})
虽然很简单,但是尽量别用,因为"慢",就如java使用if-else会比for快,但是麻烦
因为for相当于嵌套很多if-else且也有其他程序执行,还有他们之间的转换也需要时间
地理信息索引:
2DSphere:球面索引
2D索引:摇一摇,大众点评,美团都是基于2D索引的,保存的信息都是坐标,坐标都是经纬度
初始化店铺集合:
db.shops.insert({'loc':[10,10]})
db.shops.insert({'loc':[11,11]})
db.shops.insert({'loc':[13,12]})
db.shops.insert({'loc':[50,14]})
db.shops.insert({'loc':[66,66]})
db.shops.insert({'loc':[110,119]})
db.shops.insert({'loc':[93,24]})
db.shops.insert({'loc':[99,54]})
db.shops.insert({'loc':[77,7]})
创建2D索引:
db.shops.ensureIndex({
"loc":"2d"
})
2D索引创建完成之后,我们就可以实现坐标的查询了,有两种方式:
结果会将集合中基本所有的点(也就是坐标)都返回(可能有些可以设置前100)
若是有很多数据,那么就太多了,我们要设置范围
设置查询范围,5个点内的(好像只是增加点,并没有减少点,所以操作不了减少距离)
db.shops.find({
"loc":{
"$near":[11,11],
"$maxDistance":5
}
})
虽然支持最大的距离(比如上面的5),但是不支持最小距离(好像并没有这样的设置),但我们可以设置形状内查询
db.shops.find({
"loc":{
"$geoWithin":{
"$box":[[9,9],[11,11]]
}
}
})
db.shops.find({
"loc":{
"$geoWithin":{
"$center":[[9,9],2]
}
}
})
db.shops.find({
"loc":{
"$geoWithin":{
"$polygon":[[9,9],[12,15],[66,7]]
}
}
})
索引底层实现原理分析:
MongoDB 是文档型的数据库,是一种 NOSQL,它使用BSON 格式保存数据
比如之前我们的表可能有用户表、订单表等等,还要建立他们之间的外键关联关系
但是BSON就不一样了,这种形式更简单,通俗易懂
MongoDB使用B-树,实际上-不是减号,而是分割符号,所以也称为B树,或者叫做B减(-)树
所有节点都有Data域,只要找到指定索引就可以进行访问,无疑单次查询平均快于Mysql
Mysql作为一个关系型数据库,使用B+树,数据的关联性是非常强的,区间访问是常态
B+树由于数据全部存储在叶子节点,并且通过指针串在一起,这样就很容易的进行区间遍历甚至全部遍历
B-树是一种自平衡的搜索树,形式很简单:
B-树的特点:
1:多路,非二叉树
2:每个节点既保存索引,又保存数据
3:搜索时相当于二分查找
B+树是B-树的变种:
B+树的特点:
1:多路非二叉
2:只有叶子(白色部分)节点保存数据
3:搜索时相当于二分查找
4:增加了相邻接点的指向指针
从上面我们可以看出最核心的区别主要有俩:
一个是数据的保存位置:B树保存在所有的节点中,B+树保存在叶子节点
一个是相邻节点的指向:B树叶子节点之间没有指针,B+树有
就是这俩造成他们的差别:
1:B+树查询时间复杂度固定是 O(logn),B-树查询复杂度最好是 O(1)
2:B+树相邻接点的指针可以大大增加区间遍历性,而B-树每个节点 key 和 data 在一起,遍历不方便
3:B+树更适合外部存储,也就是磁盘存储。由于内节点无 data 域,每个节点能索引的范围更大更精确
4:注意这个区别相当重要,是基于1,2,3的,B树每个节点即保存数据又保存索引
所以磁盘IO的次数很少,B+树只有叶子节点保存,磁盘IO多,但是区间访问比较好
总结:
1:区间访问是关系型数据库的法宝(因为有关联),所以要使用B+树
2:单一查询mongo用的最多,所以使用B-树
为什么关系型数据库遍历访问较多,而mongo单一访问较多呢:
mysql经常做表关联查询,而单表查询是不常见的,商品->订单->库存->用户等等多级关联,是家常便饭
mongo虽然有也lookup类似join的关联查询,但基本不用
在MongoDB中,根本不推荐这么设计数据关联,反之,如果你非要设计成mysql的数据关联
那么,你就用错mongo了,还是放弃mongo,选择mysql吧
因为mongo在数据关联(分开的,分开的数据关联也就是mysql数据关联)这里比不上mysql
这是数据结构的底层原因
用mongo如何设计数据关联呢 ,看下面的代码:
db.class.insert(
{
"id":1,
"className":"一年一班",
"students":[
{"name":"吕布", "gender":"男"},
{"name":"貂蝉", "gender":"女"}
]
},
{
"id":2,
"className":"一年二班",
"students":[
{"name":"韩立", "gender":"男"},
{"name":"南宫婉", "gender":"女"}
]
},
)
索引的限制:
聚合:
MongoDB的产生,是依靠着大数据时代的到来
所谓的大数据,其实就进行数据的抓取,收集,汇总,这样就产生信息的统计操作,这就叫做"聚合",也就是"分组统计"
集合的数据量 :
集合中数据量
db.student.count()
模糊查询之后的数据量,包含"孙":
db.student.count({"name":/孙/i})
查询全部也是模糊查询的一种,只不过是没有条件的模糊查询
没有条件的查询,永远比条件查询快很多
消除重复数据:
沿用了oracle的关键字,Distinct
没有直接的函数支持(没有集合.方法),所以我们只能用最原始的runCommand()
查询所有name,消除掉重复的name
db.student.find()
db.runCommand({
"distinct":"student",
"key":"name"
})
MapReduce :
Map:数据映射(数据取出,获取,来源)
Reduce:归约(数据处理)
可以理解为数据聚集一起,统计,找出需要的各种结果,可以百度百科"MapReduce",了解更深层次的内容
更简单的理解:select xxxx + group by
初始化集合数据,员工集合
db.emps.insert({"name":"张三","sex":"男","age":19,"job":"程序猿","salary":5000})
db.emps.insert({"name":"李四","sex":"女","age":18,"job":"美工","salary":6000})
db.emps.insert({"name":"王五","sex":"女","age":21,"job":"测试","salary":7000})
db.emps.insert({"name":"赵六","sex":"女","age":21,"job":"程猿","salary":5500})
db.emps.insert({"name":"孙七","sex":"男","age":22,"job":"测试","salary":8000})
db.emps.insert({"name":"田八","sex":"女","age":22,"job":"程序猿","salary":3000})
db.emps.insert({"name":"钱九","sex":"男","age":23,"job":"美工","salary":4500})
db.emps.insert({"name":"周十","sex":"男","age":17,"job":"程序猿","salary":9000})
需求:按照职位分组,取出每个职位的人名
编写分组定义:
var jobMap = function(){
emit(this.job , this.name)
}
var jobReduce = function(key,values){
return {job:key,names:values};
}
db.runCommand({
"mapreduce":"emps",
"map":jobMap,
"reduce":jobReduce,
"out":"emps_job_names"
})
db.emps_job_names.find()
结果:
发现,的确是每组的key-value放入了这里,总共有四组,每组都有多个value
这里就是按照job分组,分成了4个,每个组都有对应的name当成一个字段,也就是name
其中第3组,只有一条数据(这里就不展开了),也就是说,就算没有分组之前,也是没有其他相同的job的
案例:统计出男女的人数,平均工资,最高工资,最低工资,员工姓名
sql中几乎写不出来(因为有员工姓名的存在,使得非常麻烦,且分组一般只能操作一次)
即基本只能存储过程(其中操作多个姓名放在一起,比如使用 “,” 分开等等)或重名调用(当成一个结果表,进行关联等等)
接下来我们通过Mongo来搞定(因为他的键值可以是数组)
var sexMap = function(){
emit(this.sex,{
"ccount":1,
"csal":this.salary,
"cavg":this.salary,
"cmax":this.salary,
"cmin":this.salary,
"cname":this.name
})
}
var sexReduce = function(key , values){
var total = 0
var sum = 0
var max = values[0].cmax
var min = values[0].cmin
var names = new Array()
for(var i in values){
total += values[i].ccount
sum += values[i].csal
if(max < values[i].cmax){
max = values[i].cmax
}
if(min > values[i].cmin){
min = values[i].cmin
}
names[i] = values[i].cname
}
var avg = (sum/total).toFixed(2)
return {
"count":total,
"avg":avg,
"sum":sum,
"max":max,
"min":min,
"names":names
}
}
db.runCommand({
"mapreduce":"emps",
"map":sexMap,
"reduce":sexReduce,
"out":"emps_info"
})
db.emps_info.find()
聚合框架:
MapReduce虽然强大,但是写起来的复杂度也是相当的高
Mongo2.x之后,提供了聚合框架函数:aggregate()
$group:
分组:
每个职位的人数 $sum每次累加1
db.emps.aggregate({
"$group":{
"_id":"$job",
"job_count":{
"$sum":1
}
}
})
每个职位的总工资:
db.emps.aggregate({
"$group":{
"_id":"$job",
"job_salary_sum":{
"$sum":"$salary"
}
}
})
每个职位的总工资,平均工资,最高工资,最低工资:
db.emps.aggregate({
"$group":{
"_id":"$job",
"sum":{
"$sum":"$salary"
},
"avg":{
"$avg":"$salary"
},
"max":{
"$max":"$salary"
},
"min":{
"$min":"$salary"
},
}
})
每个职位的工资数据(使用$push,以数组的形式显示每个职工的工资)
db.emps.aggregate({
"$group":{
"_id":"$job",
"salary":{
"$push":"$salary"
}
}
})
每个职位的员工姓名:
db.emps.aggregate({
"$group":{
"_id":"$job",
"xingming":{
"$push":"$name"
}
}
})
使用$push,可以数组显示,但是会出现重复,我们使用一个关键字来取消重复
取消员工姓名的重复
db.emps.aggregate({
"$group":{
"_id":"$job",
"xingming":{
"$addToSet":"$name"
}
}
})
$project:
数据列的显示规则,投影查询
1 | true 显示
0 | false不显示
不显示_id,显示name
一般没有指定条件的,基本都是查询所有,即操作所有
db.emps.aggregate({
"$project":{
"_id":0,
"name":1
}
})
起别名显示:
db.emps.aggregate({
"$project":{
"_id":0,
"姓名":"$name",
"职位":"$job",
"工资":"$salary"
}
})
聚合管道运算符中文文档:
https://www.docs4dev.com/docs/zh/mongodb/v3.6/reference/reference-operator-aggregation-abs.html
支持四则运算
加 $add
减 $subtract
乘 $multiply
除 $divide
求模 $mod
求年薪:
db.emps.aggregate({
"$project":{
"_id":0,
"姓名":"$name",
"职位":"$job",
"工资":"$salary",
"年薪":{
"$multiply":["$salary",12]
}
}
})
支持关系运算:
大小比较 $cmp
等于 $eq
大于 $gt
大于等于 $gte
小于 $lt
小于等于 $lte
不等于 $ne
判断null $ifNull
支持逻辑运算:
与 $and
或 $or
非 $not
字符串操作:
连接 $concat
截取 $substr
小写 $toLower
大写 $toUpper
忽略大小写比较 $strcasecmp
找出工资大于等于8000的员工姓名,工资
db.emps.aggregate({
"$project":{
"_id":0,
"姓名":"$name",
"职位":"$job",
"工资":"$salary",
"money":{
"$gte":["$salary",8000]
}
}
})
查询职位是"程序猿"
正常比较
db.emps.aggregate({
"$project":{
"_id":0,
"姓名":"$name",
"职位":"$job",
"is程序猿":{
"$eq":["$job","程序猿"]
}
}
})
转成大写比较:
db.emps.aggregate({
"$project":{
"_id":0,
"姓名":"$name",
"职位":"$job",
"is程序猿":{
"$eq":["$job",{"$toUpper":"程序猿"}]
}
}
})
忽略大小写比较:
db.emps.aggregate({
"$project":{
"_id":0,
"姓名":"$name",
"职位":"$job",
"is程序猿":{
"$strcasecmp":["$job","程序猿"]
}
}
})
截取字符串第一个字:
在操作之前,需要先注意以下解释:
运算符使用 UTF-8 编码字节的索引,其中每个代码点或字符都可以使用一到四个字节进行编码
US-ASCII (英文字母)字符使用一个字节编码
带有变音符号的字符和其他拉丁字母字符(即英语字母之外的拉丁字符)使用两个字节进行编码
中文,日文和韩 Literals 符通常需要三个字节,而其他 Unicode 平面(表情符号,math 符号等)则需要四个字节
db.emps.aggregate({
"$project":{
"_id":0,
"姓名":"$name",
"职位":"$job",
"姓氏":{
"$substrBytes":["$name",0,3]
}
}
})
$sort :
1:年龄升序
db.emps.aggregate({
"$sort":{
"age":1
}
})
-1:工资降序
db.emps.aggregate({
"$sort":{
"salary":-1
}
})
年龄升序,如果年龄相等(相等自然按照从上到下的位置排列,一般是老的在前,基本都是如此,虽然直接查看的是新的在前)
则工资降序排列,反之亦然,都相等,就按照从上到下的位置排列
db.emps.aggregate({
"$sort":{
"age":1,
"salary":-1
}
})
一般没指定排序的,查询基本都是从老到新的显示
$分页处理:
$limit:取出个数
$skip:跨过个数
取出3个值
db.emps.aggregate(
{
"$project":{
"_id":0,
"name":1,
"age":1,
"job":1
}
},
{
"$limit":3
}
)
跨过3行数据 (先跨,再取)
db.emps.aggregate(
{
"$project":{
"_id":0,
"name":1,
"age":1,
"job":1
}
},
{
"$skip":3
},
{
"$limit":3
}
)
$unwind:
查询数据的时候,会返回数组信息,数组不方便浏览
我们要将数组变成独立的字符串
初始数据
db.dept.insert({"name":"技术部","业务":["研发","培训","维护"]})
db.dept.insert({"name":"人事部","业务":["招聘","调岗","发工资"]})
db.dept.insert({"name":"市场部","业务":["销售","渠道","开拓"]})
转换:
db.dept.aggregate([{"$unwind":"$业务"}])
发现的确如此
$out :
将查询结果输出到指定集合中,类似mapreduce
将投影结果,输出到一个集合中
db.emps.aggregate(
{
"$project":{
"_id":0,
"name":1,
"job":1
}
},
{
"$skip":3
},
{
"$limit":3
},
{
"$out":"emps_test"
}
)
db.emps_test.find()
$geoNear :
得到附近的坐标点
帮助文档:
https://docs.mongodb.com/manual/reference/operator/aggregation/geoNear/#mongodb-pipeline-pipe.-geoNear
初始化数据
db.shops.drop()
db.shops.insert({'loc':[10,10]})
db.shops.insert({'loc':[11,11]})
db.shops.insert({'loc':[13,12]})
db.shops.insert({'loc':[50,14]})
db.shops.insert({'loc':[66,66]})
db.shops.insert({'loc':[110,119]})
db.shops.insert({'loc':[93,24]})
db.shops.insert({'loc':[99,54]})
db.shops.insert({'loc':[77,7]})
得到附近的坐标点
必须先建立2D索引,否则查询报错
db.shops.ensureIndex({
"loc":"2d"
})
db.shops.aggregate({
"$geoNear":{
"near":[11,11],
"distanceField":"loc",
"maxDistance":2,
"spherical":true
}
},{
"$limit":5
})
$lookup :
多表关联(3.2版本新增)主要功能:
是将每个输入待处理的文档,经过$lookup 阶段的处理
输出的新文档中会包含一个新生成的数组列(户名可根据需要命名新key的名字 )
数组列存放的数据 是 来自 被Join 集合的适配文档,如果没有,集合为空(即 为[ ])
基本语法:
{
$lookup:
{
from: <collection to join> 从表,
localField: <field from the input documents> 主表主键,
foreignField: <field from the documents of the "from" collection> 从表主键,
as: <output array field> 输出文档的新增值命名
}
}
以上的语法介绍有些枯燥,不易理解,我们直接操作案例
订单集合,测试数据 如下:
db.orders.insert([
{ "order_id" : 1, "pname" : "可乐", "price" : 12, "count" : 2 },
{ "order_id" : 2, "pname" : "薯片", "price" : 20, "count" : 1 },
{ "order_id" : 3 }
])
库存集合 ,测试数据 如下:
db.stock.insert([
{ "s_id" : 1, "sku" : "可乐", "description": "product 1", "instock" : 120 },
{ "s_id" : 2, "sku" : "面包", "description": "product 2", "instock" : 80 },
{ "s_id" : 3, "sku" : "香肠", "description": "product 3", "instock" : 60 },
{ "s_id" : 4, "sku" : "薯片", "description": "product 4", "instock" : 70 },
{ "s_id" : 5, "sku" : null, "description": "Incomplete" },
{ "s_id" : 6 }
])
此集合中的 sku 等同于 订单 集合中的 pname
在这种模式设计下,如果要查询订单表对应商品的库存情况,应如何写代码呢
很明显这需要两个集合Join,场景简单,不做赘述,直送答案
如下:
db.orders.aggregate({
"$lookup":{
from: "stock",
localField: "pname",
foreignField: "sku",
as: "shop_stock"
}
})
上述过程,其实和关系型数据库中的左外连接(left)查询非常相像(保证操作的orders集合数据完全)
固定集合:
规定集合大小,如果保存内容超过了集合的长度,那么会采用LRU的算法(最近最少使用的原则,或者是最老删除原则)
将最早(也就是最早加的,或者说是最老的)的数据移除,来保存新的数据,就是名将韩信管理粮仓的政策"推陈出新"
创建集合必须明确创建一个空集合
创建一个集合并设置集合参数
db.createCollection("depts",{
"capped":true,
"size":1024,
"max":5
})
初始化数据:
db.depts.insert({"name":"开发部-1","loc":"北京"})
db.depts.insert({"name":"开发部-2","loc":"北京"})
db.depts.insert({"name":"开发部-3","loc":"北京"})
db.depts.insert({"name":"开发部-4","loc":"北京"})
db.depts.insert({"name":"开发部-5","loc":"北京"})
再加入一条
db.depts.insert({"name":"开发部-6","loc":"北京"})
Java操作:
这里介绍spring提供对MongoDB操作的工具类的使用
对应的目录:
对应的依赖:
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-mongodbartifactId>
<version>2.0.9.RELEASEversion>
dependency>
spring配置如下:在配置文件中配置 MongoTemplate (resources下创建application.xml)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/springbeans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/data/mongo
http://www.springframework.org/schema/data/mongo/spring-mongo.xsd">
<mongo:db-factory id="mongoDbFactory" client-uri="mongodb://192.168.164.128:27017/laosun"/>
<bean id="mongoTemplate"
class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory"
ref="mongoDbFactory">constructor-arg>
bean>
<context:component-scan base-package="dao">context:component-scan>
beans>
若打开idea的项目时,maven的仓库路径会改变,可以到启动的idea页面那里进行修改(这时大概是全局的)
对应的Emp类:
package entity;
import org.springframework.data.mongodb.core.mapping.Document;
import java.io.Serializable;
@Document(collection = "emps")
public class Emp implements Serializable {
private String _id;
private String name;
private String sex;
private double age;
private String job;
private double salary;
@Override
public String toString() {
return "Emp{" +
"_id='" + _id + '\'' +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
", job='" + job + '\'' +
", salary=" + salary +
'}';
}
public Emp() {
}
public Emp(String name, String sex, double age, String job, double salary) {
this.name = name;
this.sex = sex;
this.age = age;
this.job = job;
this.salary = salary;
}
public Emp(String _id, String name, String sex, double age, String job, double salary) {
this._id = _id;
this.name = name;
this.sex = sex;
this.age = age;
this.job = job;
this.salary = salary;
}
public String get_id() {
return _id;
}
public void set_id(String _id) {
this._id = _id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public double getAge() {
return age;
}
public void setAge(double age) {
this.age = age;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
对应的EmpDao接口及其实现类:
package dao;
import entity.Emp;
import java.util.List;
public interface EmpDao {
void save(Emp emp);
void delete(String id);
void update(Emp emp);
Emp findById(String id);
List<Emp> findListPage(Integer pageIndex,Integer pageSize,String name);
}
操作实现类之前,首先先介绍对应的Mongo使用类的方法:
常用方法:
mongoTemplate.findAll(Emp.class)
mongoTemplate.findById(Object id, Emp.class)
mongoTemplate.find(Query query, Emp.class)
mongoTemplate.upsert(Query query, Update update, Emp.class)
mongoTemplate.remove(Query query, Emp.class)
mongoTemplate.insert(Object Emp)
Query对象:
对应的实现类:
package dao.impl;
import dao.EmpDao;
import entity.Emp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository("empDao")
public class EmpDaoImpl implements EmpDao {
@Autowired
private MongoTemplate mongoTemplate;
@Override
public void save(Emp emp) {
mongoTemplate.insert(emp);
}
@Override
public void delete(String id) {
}
@Override
public void update(Emp emp) {
}
@Override
public Emp findById(String id) {
}
@Override
public List<Emp> findListPage(Integer pageIndex, Integer pageSize, String name) {
return null;
}
}
测试TestMongo类:
package test;
import dao.EmpDao;
import entity.Emp;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestMongo {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new
ClassPathXmlApplicationContext("application.xml");
EmpDao empDao = context.getBean("empDao", EmpDao.class);
Emp emp = new Emp("1", "1", 1, "1", 1);
empDao.save(emp);
System.out.println("添加成功");
}
}
执行成功,可以看看对应的数据库是否有数据了,若有数据,则完成了添加操作,接下来进行扩展实现类
实现修改,删除,查询:
扩展的实现类EmpDaoImpl:
package dao.impl;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import dao.EmpDao;
import entity.Emp;
import org.springframework.beans.factory.annotation.Autowired;
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.stereotype.Repository;
import org.springframework.util.StringUtils;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.regex.Pattern;
@Repository("empDao")
public class EmpDaoImpl implements EmpDao {
@Autowired
private MongoTemplate mongoTemplate;
@Override
public void save(Emp emp) {
mongoTemplate.insert(emp);
}
@Override
public void delete(String id) {
Query query = new Query(Criteria.where("_id").is(id));
DeleteResult remove = mongoTemplate.remove(query, Emp.class);
long deletedCount = remove.getDeletedCount();
if (deletedCount > 0){
System.out.println("删除成功");
}else{
System.out.println("删除失败");
}
}
@Override
public void update(Emp emp) {
Query query = new Query(Criteria.where("_id").is(emp.get_id()));
Update update = new Update();
update.set("name",emp.getName());
update.set("age",emp.getAge());
update.set("sex",emp.getSex());
update.set("job",emp.getJob());
update.set("salary",emp.getSalary());
UpdateResult upsert = mongoTemplate.upsert(query, update, Emp.class);
long modifiedCount = upsert.getModifiedCount();
if(modifiedCount > 0){
System.out.println("修改成功");
}else{
System.out.println("修改失败");
}
}
@Override
public Emp findById(String id) {
Emp byId = mongoTemplate.findById(id, Emp.class);
return byId;
}
@Override
public List<Emp> findListPage(Integer pageIndex, Integer pageSize, String name) {
Query query = new Query();
if(!StringUtils.isEmpty(name)) {
String format = String.format("%s%s%s", "^.*", name, ".*$");
Pattern compile = Pattern.compile(format, Pattern.CASE_INSENSITIVE);
query.addCriteria(Criteria.where("name").regex(compile));
}
long count = mongoTemplate.count(query, Emp.class);
System.out.println(count);
List<Emp> emps = mongoTemplate.find(query.skip((pageIndex-1) * pageSize).limit(pageSize),
Emp.class);
return emps;
}
}
对应的扩展测试类TestMongo:
package test;
import dao.EmpDao;
import entity.Emp;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class TestMongo {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new
ClassPathXmlApplicationContext("application.xml");
EmpDao empDao = context.getBean("empDao", EmpDao.class);
List<Emp> listPage = empDao.findListPage(1,5,"哈");
System.out.println(listPage);
}
}
执行,测试,至此,增删改查全部完成
上面使用了String.format()方法,我们最后,说明一下这个方法:
根据案列来进行操作:
创建test类,随便一个地方即可,因为只是进行测试的
package dao.impl;
public class test {
public static void main(String[] args) {
String str=null;
str=String.format("Hi,%s", "小超");
System.out.println(str);
str=String.format("Hi,%s %s %s", "小超","是个","大帅哥");
System.out.println(str);
System.out.printf("Hi,%s", "小超");
}
}