推荐微信公众号:【矿洞程序员】文章由高端社区fameLink创始人陶德及其他社区大佬原创。
2、本次预计讲解的知识点
1、 传统的关系型数据库与NOSQL数据库间的对应关系;
2、 MongoDB的安装以及使用;
3、 MongoDB中针对于MapReduce的实现;
4、 MongoDB数据库的用户管理;
5、 使用Java操作MongoDB数据库。
3、具体内容
MongoDB数据库是现在使用较为广泛的数据库,但是使用它都是需要一定的应用环境,在实际的开发环境之中,传统的关系型数据库依然是使用的主体,因为这样的数据属于结构化数据,而MongoDB这样的NOSQL数据库只是一个辅助,与Node.JS结合的时候它就是主力。
3.1、MongoDB数据库简介
NoSQL数据库并不是一个新的概念,其实早在关系型数据库发展之初就已经出现了NoSQL数据库。最早的时候是由IBM开发出了SQL标准,而到今天此标准依然被广泛的使用着,但是在数据库最早的发展时期,有些人不喜欢使用SQL语句,它们习惯于使用一些操作函数,并且通过函数(insert()、find())实现数据库的操作。这个时候NoSQL数据库就已经开始慢慢发展。而NoSQL数据库发展初期的宗旨:Not Use SQL。而到了后来,人们发现关系型数据库依然具备着很强大的天生优势,所以关系数据库开始逐步的走红于世界,一直到今天,依然还是项目开发的主要环境,但是这个时候NoSQL数据库在大环境的驱使下,开始受到越来越多的人们的关注,因为现在NoSQL所提倡的宗旨:Not Only SQL,它把自己的定位非常明确,我就是要和传统的关系型数据库结合使用。
为了清楚NoSQL数据库的存在意义,来观察一种情况:在开发之中如果要进行数据异步加载,往往使用的是JSON结构,那么就会存在有如下的转换过程:
· 数据表 → 利用JDBC读取 → 使用ResultSet接收 → 将数据在数据层中以VO的形式进行转换 → 而后在业务层中将数据转送给控制层 → 控制层进行迭代操作生成JSON数据发送给客户端。
那么如果说此时数据库里面保存的就是一个结构良好的JSON数据,那么这一系列的转换过程实际上就不用出现。而且这些JSON数据还可以进行一些数据的筛选、更新操作,那么这样的数据库一定可以为我们的开发带来良好的支持。
· 这样的转换过程就可以进行简化:NoSQL数据库 → 生成客户端使用的JSON结构。
MongoDB就是NoSQL概念中使用最为广泛的数据库,但是要想使用它之前,必须首先要清楚一些对应的概念:
No. | 关系型数据库 | NoSQL数据库 |
1 | 数据库 | 数据库 |
2 | 数据表 | 数据集合 |
3 | 行 | 文档 |
4 | 列 | 成员 |
5 | 主键 | ObjectID(自动维护) |
也就是说在整个NoSQL设计过程之中,设计的操作形式并不像关系型数据库那样复杂,但是NoSQL的出现是为了弥补关系型数据库的缺陷 —— 数据查询。而NoSQL数据库就是为了方便数据的统计操作。在实际的开发过程之中,强烈建议大家只将NoSQL数据库作为查询操作使用,但是这样就要求在NoSQL数据库的集合定义的时候就必须考虑好它的结构。
MongoDB数据库使用的是BSON数据保存标准,实际上都是通过JSON发展而来,也就是说类似于JSON结构,如果真的是类似于JSON结构的话,那么整个的数据保存里面就可能保存各种嵌套的复杂数据,例如:在一个key里面保存一个数组,数组里面保存多个数据,每个数据都保存数组。
在整个MongoDB开发过程之中,集合结构的设计是最为重要的,因为NoSQL数据库不像关系型数据库那样每个列的组成是固定的,可能A文档有B数据并且存在有C数组,但是到了X文档有B数组里面存在着C数据。
MongoDB数据库天生就是为了大数据环境而准备的,所以在进行数据存储的过程之中可以方便的进行存储空间的扩充,并且支持了很多高级的索引操作,如果是一个DBA的话也可以轻松的维护MongoDB数据库。
3.2、MongoDB的安装与配置
MongoDB数据库依然是属于发展中的产物,所以本身也会存在有一些版本的差异。
MongoDB从3.x开始,已经支持了windows的自动安装,而最早的时候只能够进行解压缩操作。
MongoDB是一个依靠命令行控制的数据库(可以使用一些第三方的前台工具)所以需要在path环境属性里面配置mongodb的使用命令:E:\Program Files\MongoDB\Server\3.0\bin。
如果需要使用mongodb数据库,那么必须由用户自己建立一个文件夹,这个文件夹负责保存mongodb数据库中的全部的存储数据。例如:在D盘上建立一个mymongo的文件夹。随后就可以利用mongod的命令启动mongodb数据库,同时要求设置数据库的保存路径为:d:\mymongo目录。
范例:启动mongodb数据库服务
· 使用默认的端口号启动:
mongod --dbpath d:\mymongo |
· 指定端口号启动:
mongod --dbpath d:\mymongo --port=27017 |
如果现在设置了端口号,那么在以后使用客户端连接的时候也需要设置端口号信息。
范例:使用客户端连接数据库
mongo --port=27017 |
而随后可以直接利用如下的命令查看mongodb中的数据库信息:
show databases |
因为MongoDB数据库依然保存了数据库的概念,所以里面会存在有多个数据库。
以上的操作的确是可以成功的启动MongoDB数据库的服务,但是这个启动过程之中需要填写的内容太多了,所以一般情况下都会设置一个启动的配置文件,直接利用此配置文件进行mongodb服务的启动。
范例:在D:\mongo.conf文件
# 设置数据库的保存路径 dbpath=d:\mymongo # 描述的是日志的信息 logpath=d:\mymongo\log\mongo.log # 打开日志的操作支持 logappend=true # 如果日后有用户问题 noauth=true # 连接的端口号 port=27017 |
范例:利用配置文件启动mongodb数据库
mongod -f d:\mongo.conf |
随后可以切换到mldn数据库;use mldn。这个时候不会出现这个数据库,因为在 MongoDB里面只有创建了一个集合之后才会出现当前数据库。
3.3、MongoDB数据库的基本使用
MongoDB数据库里面唯一需要注意的就是集合,以及数据的操作问题。
3.3.1、集合操作
所有的集合一定要求其保存在数据库之中,所以在操作集合之前首先需要切换到要使用的数据库。
use mldn |
MongoDB数据库的命令是区分大小写的,这一点发现oracle设计就比较强悍了。
范例:创建集合
db.createCollection("emp") ; |
{ "ok" : 1 } |
所有的创建结果都会利用JSON的结构返回,本次操作之中给了一个“OK:1”实际上1就表示true,如果失败就是0。
范例:查看集合
show collections ; |
但是千万要记住一点,传统的关系型数据库之中是可以查询数据表的结构的,但是MongoDB数据库不能够做这样的事情,因为MongoDB数据库的集合保存的文档结构是肯定不同的。
范例:增加数据
db.emp.insert({"empno":7369,"ename":"SMITH"}) ; |
如果随后要想确认数据是否已经存在,则可以使用如下的形式查询。
范例:查询数据
db.emp.find() ; |
{ "_id" : ObjectId("5670c6ba0bd1269ff3663f15"), "empno" : 7369, "ename" : "SMITH" } |
原本只是处理的empno与ename的key,但是最终发现会自动的生成一个“_id”,这个id是由mongodb自己维护的,用于区分不同的数据。
范例:继续增加数据
var empData = {"empno":7369,"ename":"SMITH","job":["CLERK","MANAGER","PRESIDENT"],"family":[ {"father":"ALLEN","salary":8000} , {"mother":"JONES","salary":1000}]} ; db.emp.insert(empData) ; |
MongoDB里面的操作可以直接使用JavaScript的语法来实现存储过程的定义。
整个MongoDB数据库之中的文档操作都不是严谨的,它的文档的组成结构可以由用户任意的定义,但是从开发的角度来看,要是进行一些信息的加载的时候,还是固定好一个格式比较合理。
范例:查询一个数据
db.emp.findOne() ; |
{ "_id" : ObjectId("5670c6ba0bd1269ff3663f15"), "empno" : 7369, "ename" : "SMITH" } |
在进行单个数据显示的时候发现查询出来的数据的显示效果要比之前查询全部要强。
范例:格式化查询
db.emp.find().pretty() ; |
随后数据还可以进行删除。
范例:删除数据
· 根据id查询:
db.emp.find({"_id" : ObjectId("5670c7b40bd1269ff3663f1c")}).pretty() ; |
· 删除这个文档数据:
db.emp.remove({"_id" : ObjectId("5670c7b40bd1269ff3663f1c")}) ; |
范例:删除集合
db.emp.drop() ; |
范例:删除当前正在使用的数据库
db.dropDatabase() |
那么这些操作就是在实际环境之中,MongoDB的基本操作形式。
3.3.2、数据查询
对于数据查询主要使用的就是find()方法,而这个方法需要两个参数:查询条件、显示的字段。
范例:准备出以下的数据
use mldn ; db.emp.insert({"empno":7369,"ename":"SMITH","salary":800,"dept":"财务部"}) ; db.emp.insert({"empno":7566,"ename":"FORD","salary":2450,"dept":"财务部"}) ; db.emp.insert({"empno":7966,"ename":"MARTIN","salary":3000,"dept":"市场部"}) ; db.emp.insert({"empno":8866,"ename":"marry","salary":2100,"dept":"市场部"}) ; |
范例:查询雇员编号是7369的信息
db.emp.find({empno:7369}) ; |
但是默认情况下此时的查询会返回该文档中的全部内容,如果有需要也可以返回部分。
范例:只返回编号和姓名
db.emp.find({empno:7369},{"_id":0,"empno":1,"ename":1}) ; |
没有出现在里面的字段都是不进行显示的。
但是在查询的过程里面一定会存在有关系运算:大于($gt)、小于($lt)、大于等于($gte)、小于等于($lte)、不等于($ne)、等于($eq)。
范例:查询工资高于2000的员工
db.emp.find({"salary":{"$gt":2000}}) ; |
范例:查询姓名不是SMITH的雇员
db.emp.find({"ename":{"$ne":"SMITH"}}) ; |
在进行查询的过程之中往往需要使用多个查询操作,所以依然提供有逻辑运算:与($adn)、或($or)、非($nor)。
范例:查询部门是财务部,并且工资大于1000的员工
db.emp.find({"dept":{"$eq":"财务部"},"salary":{"$gt":2000}}) ; |
多个条件之间使用“,”分隔。
范例:查询部门是财务部,或者工资大于等于3000的员工
db.emp.find({"$or": [ {"dept":{"$eq":"财务部"}} , {"salary":{"$gte":3000}}]}) ; |
范例:求反操作
db.emp.find({"$nor":[{"salary":{"$gt":2000}}]}) ; |
在实际的开发之中,IN操作是一件经常使用到的查询语句。
范例:查询范围
db.emp.find({"ename":{"$in":["SMITH","FORD"]}}) ; |
在整个MongoDB之中还直接支持了正则查询。
范例:观察正则查询
· 区分大小写的查询:
db.emp.find({"ename":/M/}) ; |
· 不区分大小写的查询:
db.emp.find({"ename":/M/i}) ; |
在MongoDB里面数据的查询也可以进行排序操作。
范例:数据排序
· 按照升序排列:
db.emp.find().sort({"salary":1}) ; |
· 按照降序排列:
db.emp.find().sort({"salary":-1}) ; |
在进行排序的过程之中还有一种功能比较方便,按照自然排序。
db.emp.find().sort({"$natural":-1}) ; |
所谓的自然排序指的就是根据数据的保存顺序进行排序。
对于数据查询还有一项非常重要的功能,那么就是分页显示,在MongoDB里面对于分页显示的控制有两个函数:
· skip(n):跨过多少行;
· limit(n):取得内容。
范例:分页显示
db.emp.find().sort({"$natural":-1}).skip(2).limit(1) ; |
整个MongoDB设计的查询操作很大程度上方便了用户的使用。
3.3.3、JavaScript操作支持
现在希望可以通过循环向dept集合里面增加500行数据。
范例:批量数据增加
for (var x = 0 ; x < 500 ; x ++) { db.dept.insert({"deptno":x,"dname":"mldn - " + x}) ; } |
在整个MongoDB数据库之中考虑到其本身是JSON数据保存,那么也可以利用循环来操作每一条数据。
范例:循环操作
var cursor = db.dept.find().limit(10) ; while (cursor.hasNext()) { // 是否有下一个内容 var doc = cursor.next() ; // 取内容 printjson(doc) ; } |
游标的最大好处是可以取出每一个数据进行处理。
范例:处理游标数据
var cursor = db.dept.find().limit(10) ; while (cursor.hasNext()) { // 是否有下一个内容 var doc = cursor.next() ; // 取内容 print("部门名称:" + doc.dname + " - 处理") ; } |
var cursor = db.dept.find().limit(10) ; while (cursor.hasNext()) { // 是否有下一个内容 var doc = cursor.next() ; // 取内容 db.temp.insert({"ndname":doc.dname + " - 已处理"}) ; } |
游标是数据库进行逐行处理的有效技术手段,像ResultSet接口实际上就是利用游标这一概念实现的。
3.3.4、数据修改
在MongoDB之中如果要增加、删除、查询数据实际上都是比较容易的操作做法,但是数据的修改操作非常的麻烦。如果要修改可以使用update(更新条件,更新的内容,upsert,multi)命令。
· upsert:如果设置为true,表示在没有修改数据的时候自动执行增加;
· multi:是否更新多行数据,默认为false。
范例:修改数据
db.emp.update({"ename":"SMITH"},{"$set":{"salary":1200}},true,false) ; |
由于此时的数据存在,所以可以执行修改操作,但是也有可能数据不存在。
范例:修改数据但是数据条件不满足
db.emp.update({"ename":"KING"},{"$set":{"salary":1200}},true,false) ; |
此时为upsert属性的内容设置为了true,那么就表示如果现在的更新数据不存在,则会将新的数据保存到集合之中。
范例:批量修改
db.emp.update({"dept":"财务部"},{"$set":{"salary":5000}},true,true) ; |
如果要进行多个数据更新的话,那么必须将multi参数设置为true。否则你只能够更新一行数据。
3.4、数据聚合操作
如果要想进行数据的分页显示,那么一定需要知道数据的总记录数。
范例:得到总记录数
db.emp.count() ; |
既然MongoDB属于大数据库应用数据库,所以在大数据的开发环境之中存在有一个非常重要的概念:MapReduce。这个概念有两个组成部分;
· Map:指的是要找出需要处理的统计数据;
· Reduce:是针对于数据的统计操作。
但是必须要清楚的是整个MongoDB数据库之中的MapReduce设计的非常复杂。
范例:实现如下的信息显示
最终的显示的统计结果如下:
{dept:"财务部":names:["SMITH","FORD"]} {dept:"市场部":names:["MARTIN","marry"]} |
需要编写许多的处理步骤。
1、 实现分组定义操作,并且设置分组的数据,使用emit()函数完成。
var deptMapFun = function() { emit(this.dept,this.ename) ; } |
这个函数的操作目的实际上就是将数据进行了分组操作,按照dept进行数据分组,此时的内容形式:
· 第一组:{key : 0 , values : ["ename","ename"...]}
· 第二组:{key : 1 , values : ["ename","ename"...]}
2、 编写reduce操作
var deptReduceFun = function(key,value) { var ret = {dept : key , names : value} ; return ret ; } |
3、 整合操作代码:
db.runCommand({ mapreduce : "emp" , map : deptMapFun , reduce : deptReduceFun , out : "t_dept_emp" }) ; |
一定要清楚的概念就是Map负责数据的分析操作,而reduce负责数据的统计操作。
3.5、用户管理
如果需要进行数据库的连接操作,那么必须要使用用户。但是对于用户的创建版本之间也是有差别的。
在MongoDB数据库之中默认情况下是不需要用户名和密码的,同时发现也没有使用授权方式来启动MongoDB服务(noauth=true)。可是现在要进行程序连接或者远程用户使用,必须要使用安全认证。但是在启用安全认证之前,请先配置好用户名和密码。
需要提醒的是,整个MongoDB数据库的用户名和密码的配置都是针对于一个数据库完成的,所以要想设置这些用户名或密码的功能必须切换到要使用的数据库上。
范例:切换到mldn数据库
use mldn ; |
范例:创建用户(admin、java)
· 任何的用户都一定需要一些角色信息,那么常见的基础角色:read、readWrite。
范例:创建新的用户
db.createUser({ "user" : "admin" , "pwd" : "java" , "roles" : [{"role":"readWrite","db":"mldn"}] }) ; |
这个时候会出现一个admin的数据库,保存所有的管理员信息。
范例:修改mongo.conf文件启用安全认证