MongoDB 教程

MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。

MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。

参考地址

MongoDB 官网地址:https://www.mongodb.com/

MongoDB 官方英文文档:https://docs.mongodb.com/manual/

MongoDB 各平台下载地址:https://www.mongodb.com/download-center#community Contents:

NoSQL简介

NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库(RDMBSs)的数据库管理系统的统称。

NoSQL用于超大规模数据的存储。(例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。用户的个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。我们如果要对这些用户数据进行挖掘,那SQL数据库已经不适合这些应用了, NoSQL数据库的发展也却能很好的处理这些大的数据。

NoSQL vs RDBS

NoSQL
  • 代表着不仅仅是SQL
  • 没有声明性查询语言
  • 没有预定义的模式
  • 键-值对存储,列存储,文档存储,图形数据库
  • 最终一致性,而非ACID属性
  • 非结构化和不可预知的数据
  • CAP定理
  • 高性能,高可用性和可伸缩性
RDBS
  • 高度组织化结构化数据
  • 结构化查询语言(SQL)
  • 数据和关系都存储在单独的表中。
  • 数据操纵语言,数据定义语言
  • 严格的一致性
  • 基础事务

NoSQL的优点/缺点

优点:
  • 高可扩展性
  • 分布式计算
  • 低成本
  • 架构的灵活性,半结构化数据
  • 没有复杂的关系
缺点:
  • 没有标准化
  • 有限的查询功能(到目前为止)
  • 最终一致是不直观的程序

CAP定理

在计算机科学中, CAP定理(CAP theorem), 又被称作布鲁尔定理(Brewer’stheorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点:

一致性(Consistency) (所有节点在同一时间具有相同的数据)

可用性(Availability) (保证每个请求不管成功或者失败都有响应)

分隔容忍(Partition tolerance) (系统中任意信息的丢失或失败不会影响系统的继续运作)

CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。

NoSQL数据库分类

_images/NoSQL.jpg

MongoDB简介

现在说说MongoDB的情况,MongoDB的名字由来,HumongousDatabase=MongoDB,中文意思就是巨大无比的数据库,顾名思义,MongoDB就是为处理大数据而生,以解决海量数据的存储和高效查询使用为使命。可以看出,它的使命不是为替代关系型数据库为目的,而是为对关系型数据库的补充。

MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。

集合存在于数据库中,集合没有固定的结构,这意味着你在对集合可以插入不同格式和类型的数据,但通常情况下我们插入集合的数据都会有一定的关联性。

mongodb文档存储一般用类似json的格式存储,存储的内容是文档型的。这样也就有有机会对某些字段建立索引,实现关系数据库的某些功能。MongoDB将数据存储为一个文档,数据结构由键值(key=>value)对组成。字段值可以包含其他文档,数组及文档数组。所有存储在集合中的数据都是BSON格式。BSON是一种类json的一种二进制形式的存储格式,简称BinaryJSON。文档是一组键值(key-value)对(即BSON)。MongoDB的文档不需要设置相同的字段,并且相同的字段不需要相同的数据类型,这与关系型数据库有很大的区别,也是 MongoDB 非常突出的特点.

与Mysql的结构对比

mongodb与mysql对比 传统的关系数据库一般由数据库(database)、表(table)、记录(record)三个层次概念组成。

MongoDB是由数据库(database)、集合(collection)、文档对象(document)三个层次组成。

MongoDB集合对于关系型数据库里的表,但是集合中没有列、行和关系概念,这体现了模式自由的特点。这也许是mongoDB介于关系数据库和非关系的之间的,又在非关系之间最像关系数据库的。

MongoDB主要特点

  1. MongoDB的提供了一个面向文档存储,操作起来比较简单和容易。
  2. 你可以在MongoDB记录中设置任何属性的索引 (如:FirstName=”Sameer”,Address=”8 Gandhi Road”)来实现更快的排序。
  3. 你可以通过本地或者网络创建数据镜像,这使得MongoDB有更强的扩展性。
  4. 如果负载的增加(需要更多的存储空间和更强的处理能力) ,它可以分布在计算机网络中的其他节点上这就是所谓的分片。
  5. Mongo支持丰富的查询表达式。查询指令使用JSON形式的标记,可轻易查询文档中内嵌的对象及数组。
  6. MongoDb 使用update()命令可以实现替换完成的文档(数据)或者一些指定的数据字段 。
  7. Mongodb中的Map/reduce主要是用来对数据进行批量处理和聚合操作。
  8. Map和Reduce。Map函数调用emit(key,value)遍历集合中所有的记录,将key与value传给Reduce函数进行处理。
  9. Map函数和Reduce函数是使用Javascript编写的,并可以通过db.runCommand或mapreduce命令来执行MapReduce操作。
  10. GridFS是MongoDB中的一个内置功能,可以用于存放大量小文件。
  11. MongoDB允许在服务端执行脚本,可以用Javascript编写某个函数,直接在服务端执行,也可以把函数的定义存储在服务端,下次直接调用即可。
  12. MongoDB支持各种编程语言:RUBY,PYTHON,JAVA,C++,PHP,C#等多种语言。
  13. MongoDB安装简单。

MongoDB

数据存储

MongoDB是面向集合(collection)的,集合中又包含多个文档(document),并支持对象型数据的存储。其中集合和文档的概念,在关系型数据库中类似于表(table)和元组(row:也就是所谓的一行数据)。

数据结构

MongoDB对数据结构的支持非常灵活,从横向到纵向的支持都很好,比如下面的数据都能存储在同一个集合中:

  • {“name”: “wangwu”, “age”: 25}
  • {“name”: “lisi”}
  • {“state”: “激活”, “remark”: “无”}
  • {“name”: 6, “age”: “你猜”}

从上面的例子我们能看出,横向方面,支持字段的动态增减(如①和②),从纵向方面,支持字段数据类型混合存储(如①和④)。而在关系型数据库中这种方式的存储是绝对达不到的。

数据及表关系、结构浏览

MongoDB因为没什么特别好的GUI客户端,所以在数据浏览和表关系、结构浏览上是非常困难和笨拙的,关系型数据库在这方面上优势明显。

查询

MongoDB的查询语句与关系型的sql语句有着很大的不同,或者说是两种风格,二者表现都很不错,MongoDB主要体现在灵活易用上,而sql则体现在功能全面强大上。

主外键关联

MongoDB不支持主外键关联,也没有“约束”的概念。 在插入数据的同时直接就会自动生成表! Id是自增的,自动生成的 mongodb数据库中没有主外键关系,有的只是ref引用

安装MongoDB

安装

你可以在mongodb官网下载该安装包,MongoDB 预编译二进制包下载地址:https://www.mongodb.com/download-center#community

根据你的系统下载 32 位或 64 位的 .msi 文件,下载后双击该文件,按操作提示安装即可。

安装过程中,你可以通过点击 “Custom(自定义)” 按钮来设置你的安装目录。

运行

创建数据目录

MongoDB将数据目录存储在 db 文件夹下。但是这个数据目录不会主动创建,我们在安装完成后需要创建它。请注意,数据目录应该放在根目录下((如: C:\ 或者 D:\ 等 )。创建一个data的文件夹,然后在data文件夹里创建db文件夹。

为了从命令提示符(cmd)下运行 MongoDB 服务器,你必须从 MongoDB 目录的 bin 目录中执行 mongod.exe 文件。

输入如下的命令启动mongodb服务:(我是安装到D盘了)

D:/mongodb/bin>mongod --dbpath D:\mongodb\data\db

如果执行成功,会输出如下信息:

2015-09-25T15:54:09.212+0800 I CONTROL  Hotfix KB2731284 or later update is not
installed, will zero-out data files
2015-09-25T15:54:09.229+0800 I JOURNAL  [initandlisten] journal dir=c:\data\db\j
ournal
2015-09-25T15:54:09.237+0800 I JOURNAL  [initandlisten] recover : no journal fil
es present, no recovery needed
2015-09-25T15:54:09.290+0800 I JOURNAL  [durability] Durability thread started
2015-09-25T15:54:09.294+0800 I CONTROL  [initandlisten] MongoDB starting : pid=2
488 port=27017 dbpath=c:\data\db 64-bit host=WIN-1VONBJOCE88
2015-09-25T15:54:09.296+0800 I CONTROL  [initandlisten] targetMinOS: Windows 7/W
indows Server 2008 R2
2015-09-25T15:54:09.298+0800 I CONTROL  [initandlisten] db version v3.0.6
……

要看下是否开启成功,mongodb采用27017端口,那么我们就在浏览器里面键入http://localhost:27017/。

It looks like you are trying to access MongoDB over HTTP on the native driver port

如果你需要进入MongoDB后台管理,你需要先打开mongodb装目录的下的bin目录,然后执行mongo.exe文件,MongoDBShell是MongoDB自带的交互式Javascript shell,用来对MongoDB进行操作和管理的交互式环境。

当你进入mongoDB后台后,它默认会链接到 test 文档(数据库)

将MongoDB设置成Windows服务

  1. 在d:\mongodb\data下新建文件夹log(存放日志文件)并且新建文件mongodb.log

  2. 在d:\mongodb新建文件mongo.config

    用记事本打开mongo.config输入:

    dbpath=D:\mongodb\data\db

    logpath=D:\mongodb\data\log\mongodb.log

3. 用管理员身份打开cmd命令行,进入D:\mongodb\bin目录,输入如下的命令:

mongod --config D:\mongodb\mongo.config --install --serviceName "MongoDB"

如图结果存放在日志文件中,查看日志发现已经成功。如果失败有可能没有使用管理员身份,遭到拒绝访问。

  1. 打开cmd输入services.msc查看服务可以看到MongoDB服务,点击可以启动。

GUI

  • 网页式,由Django和jQuery所构成。
  • 一个CouchDB Futon web的mongodb山寨版。
  • Ruby写成。
  • 适用于OSX的应用程序。
  • 一个基于浏览器的MongoDB控制台, 由PHP撰写而成。
  • Windows的mongodb管理工具
  • 最好的PHP语言的MongoDB管理工具,轻量级, 支持多国语言.

教程给的是Rockmongo 下载地址:http://rockmongo.com/downloads

而我用的是Robo 3T https://robomongo.org/

基本操作

通过cmd输入命令链接上mongoDB后,可以打开mongodb装目录的下的bin目录,然后执行mongo.exe文件。

或者再开一个cmd,进入bin目录,输入mongo命令打开shell,其实这个shell就是mongodb的客户端,同时也是一个js的编译器,默认连接的是“test”数据库。

1.创建/删除数据库

命令:

> use 数据库名

如果数据库不存在,则创建数据库,否则切换到指定数据库。

> db

查看当前数据库。

> show dbs

查看所有数据库。

我们刚创建的新数据库并不在查看出来的所有数据库的列表中, 要显示它,我们需要向新建的数据库插入一些数据。

> db.dropDatabase()

删除当前数据库

> db.集合名.drop()

删除集合

2.插入文档

文档的数据结构和JSON基本一样。所有存储在集合中的数据都是BSON格式。BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON。

MongoDB 没有像 SQL 一样有自动增长的功能, MongoDB 的 _id 是系统自动生成的12字节唯一标识。

MongoDB 使用 insert() 或 save() 方法向集合中插入文档,语法如下:

> db.集合名.insert(文档)

实例

> db.person.insert({name:”lishuang”,age:”你猜”})

person是集合名,如果该集数据库中没有person集合, MongoDB 会自动创建该集合并插入文档。

> db.person.find()

查询集合

插入文档你也可以使用 db. person.save(document) 命令。如果不指定_id字段save()方法类似于insert()方法。如果指定_id字段,save()则会更新该 _id 的数据。

db.collection.insertOne():向指定集合中插入一条文档数据

db.collection.insertMany():向指定集合中插入多条文档数据

3.更新文档

update() 方法用于更新已存在的文档。

>  db.collection.update(
  <query>,
  <update>,
  {
    upsert: <boolean>,
    multi: <boolean>,
    writeConcern: <document>
  }
)
参数说明:
  • query : update的查询条件,类似sql update查询内where后面的。
  • update : update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内set后面的
  • upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
  • multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
  • writeConcern :可选,抛出异常的级别。

实例

db.person.update({age:”你猜”},{$set:{age:23}},{multi:true})

设置 multi 参数为 true是要修改多条相同的文档。

db.person.find()

save() 方法通过传入的文档来替换已有文档

save() 方法的格式就是正常插入文档,与insert()方法相同,如果文档已存在则更新文档,如果不存在则插入文档。

4.删除文档

命令:

db.collection.remove(
  <query>,
  {
    justOne: <boolean>,
    writeConcern: <document>
  }
)
参数说明:
  • query :(可选)删除的文档的条件。
  • justOne : (可选)如果设为 true 或 1,则只删除一个文档。
  • writeConcern :(可选)抛出异常的级别。

实例

db.person.remove({age:23})

命令:

db.person.remove({})

删除所有

5.查询

普通查询
::
db.person.find(query,projection) * query :可选,使用查询操作符指定查询条件 * projection :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)_id是默认显示的,也可以手动设为0(不显示)。

实例

db.person.find({age:23},{age:1})

对应SQL:

select id,age from person where age = 23

命令:

db.person.find().pretty()

pretty() 方法以格式化的方式来显示所有文档。

MongoDB与RDBMS Where语句比较
_images/Where.jpg
And条件

查找年龄大于20小于30的人

db.person.find({$and:[age:{$gt:20},{age:{$lt:30}}]})

对应SQL:

select * from person where age between 20 and 30

还有许多查询运算符比如$or,$in,$type……等等。

db.person.find().limit(NUMBER)

limit(NUMBER)方法返回结果的记录条数

db.person.find().limit(NUMBER).skip(NUMBER)

skip(NUMBER)方法来跳过指定数量的数据

排序、索引、聚合

排序

命令:

db.person.find().sort({KEY:1})

在MongoDB中使用使用sort()方法对数据进行排序,sort()方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而-1是用于降序排列。

索引

命令

db.person.ensureIndex({KEY:1})

语法中 Key值为你要创建的索引字段,1为指定按升序创建索引,如果你想按降序来创建索引指定为-1即可。

ensureIndex() 方法中你也可以设置使用多个字段创建索引(关系型数据库中称作复合索引)

_images/ensureIndex.jpg

命令:

db.person.find({age:23}).explain()

explain 操作提供了查询信息,使用索引及查询统计等。有利于我们对索引的优化。

也可以使用 hint 来强制 MongoDB 使用一个指定的索引。(我已经创建了age索引)

db.person.find({age:23}).hint({age:1}).explain()
索引数组字段

需要对集合中的数组字段建立索引。在数组中创建索引,需要对数组中的每个字段依次建立索引。

索引子文档字段

字段是子文档的字段,所以我们需要对子文档建立索引。为子文档的三个字段创建索引

索引限制
  1. 每个索引占据一定的存储空间,在进行插入,更新和删除操作时也需要对索引进行操作。所以,如果你很少对集合进行读取操作,建议不使用索引。
  2. 由于索引是存储在内存(RAM)中,你应该确保该索引的大小不超过内存的限制。如果索引的大小大于内存的限制,MongoDB会删除一些索引,这将导致性能下降。
  3. 索引不能被以下的查询使用:

正则表达式及非操作符,如 $nin, $not, 等。

算术运算符,如 $mod, 等。

$where 子句。

所以,检测你的语句是否使用索引是一个好的习惯,可以用explain来查看。

  1. 如果现有的索引字段的值超过索引键的限制,MongoDB中不会创建索引。
  2. 如果文档的索引字段值超过了索引键的限制,MongoDB不会将任何文档转换成索引的集合。与mongorestore和mongoimport工具类似。
  3. 集合中索引不能超过64个

索引名的长度不能超过128个字符

一个复合索引最多可以有31个字段

聚合

MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。有点类似sql语句中的 count(*)。

db.person.aggregate(OPERATION)
_images/aggregate.jpg

MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。

表达式:处理输入文档并输出。表达式是无状态的,只能用于计算当前聚合管道的文档,不能处理其它的文档。

介绍一下聚合框架中常用的几个操作:
  • $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
  • $match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。
  • $limit:用来限制MongoDB聚合管道返回的文档数。
  • $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
  • $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
  • $group:将集合中的文档分组,可用于统计结果。
  • $sort:将输入文档排序后输出。
  • $geoNear:输出接近某一地理位置的有序文档。

命令:

db.person.aggregate(
   { $project : {
       age : 1 ,
       name : 1 ,
   }}
);

查询age和name

db.articles.aggregate( [
                       { $match : { age : { $gt : 20, $lte : 30 } } },
                       { $group: { _id: null, count: { $sum: 1 } } }
                      ] );

$match用于获取年龄大于20小于或等于30记录,然后将符合条件的记录送到下一阶段$group管道操作符进行处理。

引用和嵌入

_images/embed.jpg

图一

_images/ref.jpg

图二

图一为嵌入,将address集合嵌入一个集合中 图二为引用(ref),$ref为被引用的集合名,$id为id

_images/MongoDB.jpg _images/mysql.jpg

我们有两个Collection,student和coureses。第一张图就是MongoDB表设计,学生Collection中包含一个嵌入的address文档和coursesCollection有引用关系的score文档。第二张图为关系型数据库的建表模式。

关系型与mongoDB比较

student与score是一对多的关系,其scores字段就是一个BSON,该BSON可以只有一个for_course,也可以有两个、三个、任意个for_course,其固有的模式自由特性使得它可以将score包含在内而无需另建一个score集合。

对于与student为一对一关系的address表也可以直接合入student,无需担心address的扩展性,当以后需要给address新增一个province字段,直接在数据插入时加上这个值即可。

对于与student成多对多关系course表,为了减少数据冗余,可以将course建立为一个集合,同关系型的数据库设计中类似。

mongoDB嵌入与引用比较

在Mongo数据库设计中关键的一句话是“比起嵌入到其他Collection中做一个子对象,每个对象值得拥有自己的Collection吗?”。在关系数据库中。每个有兴趣的子项目通常都会分离出来单独设计一张表(除非为了性能的考虑)。而在Mongo中,是不建议使用这种设计的,嵌入式的对象更高效。数据是即时同步到硬盘上的,客户端与服务器不必要在数据库上做周转。所以通常来说问题就是“为什么不使用嵌入式对象呢?”

当查询address时比查询scores快

因为address是嵌入式对象,所以这个操作通常是很快速的,如果sdudent被放在内存中,那address也通常在内存中。而for_course是引用的,查询scores还需要再去查询for_cours的id。

那我们什么时候需要嵌入,什么时候引用?

  1. 顶级对象,一般都有自己的Collection
  2. 线性细节对象,一般作为嵌入式的
  3. 一个对象和另一个对象是包含关系时通常采用嵌入式设计
  4. 多对多的关系通常采取引用设计
  5. 只含有几个简单对象的可以单独作为一个Collection,因为整个Collection可以很快的被缓存在应用程序服务器内存中。
  6. 在Collection中嵌入式对象比顶级对象更难引用。你不能引用嵌入式对象(至少目前还没有)。
  7. 它得到一个嵌入式对象的系统级视图更难。例如,如果分数不嵌入它会更容易查询前100的成绩在所有学生。
  8. 如果将要嵌入的数据量很大(很多M),你可以限制单个对象的大小
  9. 如果性能存在问题,请使用嵌入式设计

实例

我们来用实例详解下嵌入式和引用(我写数量比较大的Demo时,建模没考虑好直接一对多嵌入,总是报文档过大错误,因为MongoDB聚合查询返回的文档大小限制在16M,大于它就会报错,所以看下面)

基于MongoDB丰富的表达力,我们不能说我们必须采用一个标准的方法来进行1 to n的建模。稍后我们从3个具体场景来展开讲解。

首先,我们将1 to n 中的n进行场景细化。这个n究竟代表多大的量级呢?是几个到几十个?还是几个到几千个?还是成千上万个?

1 to n(n代表好几个,或几十个,反正不太多)

比如每个Person会有多个Address。此种情况下,我们采用最简单的嵌入式文档来建模。

{
 name: 'Kate Monster',
 id: '123-456-7890',
 addresses : [
    { street: '123 Sesame St', city: 'Anytown', cc: 'USA' },
    { street: '123 Avenue Q', city: 'New York', cc: 'USA' }
 ]
}

这种建模的方式包含了显而易见的优点和缺点:

优点:你不需要执行单独的查询就可以获得某个Person的所有Address信息。

缺点:你无法像操作独立文档那样来操作Address信息。你必须首先操作(比如查询)Person文档后,才有可能继续操作Address。

在本实例中,我们不需要对Address进行独立的操作,且Address信息只有在关联到某一个具体Person后才有意义。所以结论是:采用这种embedded(嵌入式)建模是非常适合Person-Address场景的。

1 to n(n代表好些个,比如几十个,甚至几百个)

比如产品(Product)和零部件(part),每个产品会有很多个零部件。这种场景下,我们可以采用引用方式来建模,如下:

零部件(Part):

{
   _id : ObjectID('AAAA'),
   partno : '123-aff-456',
   name : '#4 grommet',
   qty: 94,
   cost: 0.94,
   price: 3.99
}

产品(Product):

{
   name : 'left-handed smoke shifter',
   manufacturer : 'Acme Corp',
   catalog_number: 1234,
   parts : [     // array of references to Part documents
       ObjectID('AAAA'),    // reference to the #4 grommet above
       ObjectID('F17C'),    // reference to a different Part
       ObjectID('D2AA'),
       // etc
   ]
}

首先每个part作为单独的文档存在。每个产品中包含一个数组类型字段(parts),这个数组中存放的是所有该产品包含的零部件的编号(_id主键)。当你需要根据某一个产品编号查询该产品包含的所有部件信息时,你可以执行以下操作:

> product = db.products.findOne({catalog_number: 1234});
  // Fetch all the Parts that are linked to this Product
> product_parts = db.parts.find({_id: { $in : product.parts } } ).toArray() ;

这种建模方式的优缺点也非常明显:

优点:部件是作为独立文档(document)存在的,你可以对某一部件进行独立的操作,比如查询或更新。

缺点:如上,你必须通过两次查询才能找到某一个产品所属的所有部件信息。

在本例中,这个缺点是可以接受的,本身实现起来也不难。而且,通过这种建模,你可以轻易的将1 to n扩展到n to n,即一个产品可以包含多个部件,同时一个部件也可以被多个产品所引用(即同一部件可以被多个产品使用)。

1 to n(这个n代表很大的数值,比如成千上万,甚至更大)

比如,每一个主机(host)会产生很大数量的日志信息(logmsg)。在这种情况下,如果你采用嵌入式建模,则一个host文档会非常庞大,从而轻易超过MongoDB的文档大小限制,所以不可行。如果你采用第二中方式建模,用数组来存放所有logmsg的_id值,这种方式同样不可行,因为当日志很多时,即使单单引用objectId也会轻易超过文档大小限制(16M)。所以此时,我们采用以下方式: 主机(hosts):

{
   _id : ObjectID('AAAB'),
   name : 'goofy.example.com',
   ipaddr : '127.66.66.66'
}

日志(logmsg):

{
   time : ISODate("2014-03-28T09:42:41.382Z"),
   message : 'cpu is on fire!',
   host: ObjectID('AAAB')       // Reference to the Host document
}

我们在logsmg中,存放对host的_id引用即可。

综上所述,在对1 to n关系建模时,我们需要考虑: #. n代表的数量级很小,且n代表的实体不需要单独操作时,可以采用嵌入式建模。 #. n代表的数量级比较大,或者n代表的实体需要单独进行操作时,采用在1中用Array存放引用的方式建模。 #. n代表的数量级非常大时,我们没有选择,只能在n端添加一个引用到1端。

文件存入

1.由于MongoDB的文档结构为BJSON格式(BJSON全称:Binary JSON),而BJSON格式本身就支持保存二进制格式的数据,因此可以把文件的二进制格式的数据直接保存到MongoDB的文档结构中。取的时候再以二进制格式取,这样文档就能实现无损保存。

2.数据库支持以BSON格式保存二进制对象。 但是MongoDB中BSON对象最大不能超过4MB。GridFS 规范提供了一种透明的机制,可以将一个大文件分割成为多个较小的文档。原理: GridFS在数据库中,默认使用fs.chunks和fs.files来存储文件。(GridFS不自动处理md5相同的文件)

  • fs.files集合存放文件的信息;
  • fs.chunks存放文件数据;

进入到MongoDB的安装目录的bin目录中,找到mongofiles.exe,并输入下面的代码:

mongofiles.exe -d gridfs put song.mp3

GridFS 是存储文件的数据名称。如果不存在该数据库,MongoDB会自动创建。Song.mp3 是音频文件名。

使用以下命令来查看数据库中文件的文档:

>db.fs.files.find()

以上命令执行后返回以下文档数据:

{
  _id: ObjectId('534a811bf8b4aa4d33fdf94d'),
  filename: "song.mp3",
  chunkSize: 261120,
  uploadDate: new Date(1397391643474), md5: "e4f53379c909f7bed2e9d631e15c1c41",
  length: 10401959
}

我们可以看到 fs.chunks 集合中所有的区块,以下我们得到了文件的 _id 值,我们可以根据这个 _id 获取区块(chunk)的数据:

>db.fs.chunks.find({files_id:ObjectId('534a811bf8b4aa4d33fdf94d')})

以上实例中,查询返回了 40 个文档的数据,意味着mp3文件被存储在40个区块中。

3. 使用mongoimport 在命令行执行如下:

mongoimport -d test -c students students.dat -d test

指的是导入test 数据库(database)

MongoDB JDBC

Jar包 下载地址:http://mongodb.github.io/mongo-java-driver/

国内jar 下载地址:http://central.maven.org/maven2/org/mongodb/mongo-java-driver/ (这里测试使用3.2.2版)。

连接数据库,你需要指定数据库名称,如果指定的数据库不存在,mongo会自动创建数据库。

连接数据库的Java代码如下:

import com.mongodb.MongoClient;
import com.mongodb.client.MongoDatabase;

public class MongoDBJDBC{
   public static void main( String args[] ){
          try{
           // 连接到 mongodb 服务
                 MongoClient mongoClient = new MongoClient( "localhost" , 27017 );

                 // 连接到数据库
                 MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
           System.out.println("Connect to database successfully");

          }catch(Exception e){
                System.err.println( e.getClass().getName() + ": " + e.getMessage() );
         }
   }
}

本实例中 Mongo 数据库无需用户名密码验证。如果你的 Mongo 需要验证用户名及密码,可以使用以下代码:

import java.util.ArrayList;
import java.util.List;
import com.mongodb.MongoClient;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.client.MongoDatabase;

public class MongoDBJDBC {
        public static void main(String[] args){
                try {
                        //连接到MongoDB服务 如果是远程连接可以替换“localhost”为服务器所在IP地址
                        //ServerAddress()两个参数分别为 服务器地址 和 端口
                        ServerAddress serverAddress = new ServerAddress("localhost",27017);
                        List<ServerAddress> addrs = new ArrayList<ServerAddress>();
                        addrs.add(serverAddress);

                        //MongoCredential.createScramSha1Credential()三个参数分别为 用户名 数据库名称 密码
                        MongoCredential credential = MongoCredential.createScramSha1Credential("username", "databaseName", "password".toCharArray());
                        List<MongoCredential> credentials = new ArrayList<MongoCredential>();
                        credentials.add(credential);

                        //通过连接认证获取MongoDB连接
                        MongoClient mongoClient = new MongoClient(addrs,credentials);

                        //连接到数据库
                        MongoDatabase mongoDatabase = mongoClient.getDatabase("databaseName");
                        System.out.println("Connect to database successfully");
                } catch (Exception e) {
                        System.err.println( e.getClass().getName() + ": " + e.getMessage() );
                }
        }
}

我们可以使用 com.mongodb.client.MongoDatabase 类中的createCollection()来创建集合

代码片段如下:

import com.mongodb.MongoClient;
import com.mongodb.client.MongoDatabase;

public class MongoDBJDBC{
   public static void main( String args[] ){
          try{
          // 连接到 mongodb 服务
          MongoClient mongoClient = new MongoClient( "localhost" , 27017 );


          // 连接到数据库
          MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
          System.out.println("Connect to database successfully");
          mongoDatabase.createCollection("test");
          System.out.println("集合创建成功");

          }catch(Exception e){
                System.err.println( e.getClass().getName() + ": " + e.getMessage() );
         }
   }
}

我们可以使用com.mongodb.client.MongoDatabase类的 getCollection() 方法来获取一个集合

import org.bson.Document;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;

public class MongoDBJDBC{
   public static void main( String args[] ){
          try{
           // 连接到 mongodb 服务
                 MongoClient mongoClient = new MongoClient( "localhost" , 27017 );

                 // 连接到数据库
                 MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
           System.out.println("Connect to database successfully");

           MongoCollection<Document> collection = mongoDatabase.getCollection("test");
           System.out.println("集合 test 选择成功");
          }catch(Exception e){
                System.err.println( e.getClass().getName() + ": " + e.getMessage() );
         }
   }
}

我们可以使用com.mongodb.client.MongoCollection类的 insertMany() 方法来插入一个文档

import org.bson.Document;

import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;

public class MongoDBJDBC{
   public static void main( String args[] ){
          try{
                 // 连接到 mongodb 服务
                 MongoClient mongoClient = new MongoClient( "localhost" , 27017 );

                 // 连接到数据库
                 MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
                 System.out.println("Connect to database successfully");

                 MongoCollection<Document> collection = mongoDatabase.getCollection("test");
                 System.out.println("集合 test 选择成功");
                 //插入文档
                 /**
                 * 1. 创建文档 org.bson.Document 参数为key-value的格式
                 * 2. 创建文档集合List<Document>
                 * 3. 将文档集合插入数据库集合中 mongoCollection.insertMany(List<Document>) 插入单个文档可以用 mongoCollection.insertOne(Document)
                 * */
                 Document document = new Document("title", "MongoDB").
                 append("description", "database").
                 append("likes", 100).
                 append("by", "Fly");
                 List<Document> documents = new ArrayList<Document>();
                 documents.add(document);
                 collection.insertMany(documents);
                 System.out.println("文档插入成功");
          }catch(Exception e){
                 System.err.println( e.getClass().getName() + ": " + e.getMessage() );
          }
   }
}

我们可以使用 com.mongodb.client.MongoCollection 类中的 find() 方法来获取集合中的所有文档。

此方法返回一个游标,所以你需要遍历这个游标。

import org.bson.Document;
import com.mongodb.MongoClient;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;

public class MongoDBJDBC{
   public static void main( String args[] ){
          try{
                 // 连接到 mongodb 服务
                 MongoClient mongoClient = new MongoClient( "localhost" , 27017 );

                 // 连接到数据库
                 MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
                 System.out.println("Connect to database successfully");

                 MongoCollection<Document> collection = mongoDatabase.getCollection("test");
                 System.out.println("集合 test 选择成功");

                 //检索所有文档
                 /**
                 * 1. 获取迭代器FindIterable<Document>
                 * 2. 获取游标MongoCursor<Document>
                 * 3. 通过游标遍历检索出的文档集合
                 * */
                 FindIterable<Document> findIterable = collection.find();
                 MongoCursor<Document> mongoCursor = findIterable.iterator();
                 while(mongoCursor.hasNext()){
                        System.out.println(mongoCursor.next());
                 }

          }catch(Exception e){
                 System.err.println( e.getClass().getName() + ": " + e.getMessage() );
          }
   }
}

编译运行以上程序,输出结果如下:

Connect to database successfully
集合 test 选择成功
Document{{_id=56e65fb1fd57a86304fe2692, title=MongoDB, description=database, likes=100, by=Fly}}

你可以使用 com.mongodb.client.MongoCollection 类中的 updateMany() 方法来更新集合中的文档。

public class MongoDBJDBC{
   public static void main( String args[] ){
          try{
                 // 连接到 mongodb 服务
                 MongoClient mongoClient = new MongoClient( "localhost" , 27017 );

                 // 连接到数据库
                 MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
                 System.out.println("Connect to database successfully");

                 MongoCollection<Document> collection = mongoDatabase.getCollection("test");
                 System.out.println("集合 test 选择成功");

                 //更新文档   将文档中likes=100的文档修改为likes=200
                 collection.updateMany(Filters.eq("likes", 100), new Document("$set",new Document("likes",200)));
                 //检索查看结果
                 FindIterable<Document> findIterable = collection.find();
                 MongoCursor<Document> mongoCursor = findIterable.iterator();
                 while(mongoCursor.hasNext()){
                        System.out.println(mongoCursor.next());
                 }

          }catch(Exception e){
                 System.err.println( e.getClass().getName() + ": " + e.getMessage() );
          }
   }
}

要删除集合中的第一个文档,首先你需要使用com.mongodb.DBCollection类中的 findOne()方法来获取第一个文档,然后使用remove 方法删除。

public class MongoDBJDBC{
   public static void main( String args[] ){
          try{
                 // 连接到 mongodb 服务
                 MongoClient mongoClient = new MongoClient( "localhost" , 27017 );

                 // 连接到数据库
                 MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
                 System.out.println("Connect to database successfully");

                 MongoCollection<Document> collection = mongoDatabase.getCollection("test");
                 System.out.println("集合 test 选择成功");

                 //删除符合条件的第一个文档
                 collection.deleteOne(Filters.eq("likes", 200));
                 //删除所有符合条件的文档
                 collection.deleteMany (Filters.eq("likes", 200));
                 //检索查看结果
                 FindIterable<Document> findIterable = collection.find();
                 MongoCursor<Document> mongoCursor = findIterable.iterator();
                 while(mongoCursor.hasNext()){
                   System.out.println(mongoCursor.next());
                 }

          }catch(Exception e){
                System.err.println( e.getClass().getName() + ": " + e.getMessage() );
         }
   }
}

更多操作可以参考:http://mongodb.github.io/mongo-java-driver/3.0/driver/getting-started/quick-tour/

SpringData MongoDB

整合MongoDB的框架还有Morphia,有兴趣的可以上网看看,网上也有别的乱七八糟框架的,这里简单地说下SpringData。

项目git地址:https://code.csdn.net/qq_16313365/demo-springdata-mongo/tree/master

官方文档:https://docs.spring.io/spring-data/mongodb/docs/1.7.0.RELEASE/reference/html/

spring Data MongoDB 项目提供与mongodb文档数据库的集成。Spring Data MongoDB POJO的关键功能区域为中心的模型与MongoDB的DBCollection轻松地编写一个存储库交互数据访问。

【pom.xml】

1.      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3.          <modelVersion>4.0.0</modelVersion>
4.
5.          <groupId>com.jastar</groupId>
6.          <artifactId>demo</artifactId>
7.          <version>0.0.1-SNAPSHOT</version>
8.          <packaging>jar</packaging>
9.
10.         <name>demo</name>
11.         <url>http://www.jastar-wang.tech</url>
12.
13.         <!-- 版本配置 -->
14.         <properties>
15.             <spring.version>4.1.4.RELEASE</spring.version>
16.             <spring.data.version>1.7.0.RELEASE</spring.data.version>
17.             <log4j.version>1.2.17</log4j.version>
18.         </properties>
19.
20.         <dependencies>
21.             <!-- 单元测试包 -->
22.             <dependency>
23.                 <groupId>junit</groupId>
24.                 <artifactId>junit</artifactId>
25.                 <version>4.10</version>
26.                 <scope>test</scope>
27.             </dependency>
28.
29.             <!-- spring核心包 -->
30.             <dependency>
31.                 <groupId>org.springframework</groupId>
32.                 <artifactId>spring-core</artifactId>
33.                 <version>${spring.version}</version>
34.             </dependency>
35.             <dependency>
36.                 <groupId>org.springframework</groupId>
37.                 <artifactId>spring-context</artifactId>
38.                 <version>${spring.version}</version>
39.             </dependency>
40.             <dependency>
41.                 <groupId>org.springframework</groupId>
42.                 <artifactId>spring-beans</artifactId>
43.                 <version>${spring.version}</version>
44.             </dependency>
45.             <dependency>
46.                 <groupId>org.springframework</groupId>
47.                 <artifactId>spring-tx</artifactId>
48.                 <version>${spring.version}</version>
49.             </dependency>
50.             <dependency>
51.                 <groupId>org.springframework</groupId>
52.                 <artifactId>spring-webmvc</artifactId>
53.                 <version>${spring.version}</version>
54.             </dependency>
55.             <dependency>
56.                 <groupId>org.springframework</groupId>
57.                 <artifactId>spring-aop</artifactId>
58.                 <version>${spring.version}</version>
59.             </dependency>
60.             <dependency>
61.                 <groupId>org.springframework</groupId>
62.                 <artifactId>spring-test</artifactId>
63.                 <version>${spring.version}</version>
64.             </dependency>
65.
66.             <!-- 关系型数据库整合时需配置 如hibernate jpa等 -->
67.             <dependency>
68.                 <groupId>org.springframework</groupId>
69.                 <artifactId>spring-orm</artifactId>
70.                 <version>${spring.version}</version>
71.             </dependency>
72.
73.             <!-- spring aop 关联 -->
74.             <dependency>
75.                 <groupId>org.aspectj</groupId>
76.                 <artifactId>aspectjweaver</artifactId>
77.                 <version>1.8.7</version>
78.             </dependency>
79.
80.             <!-- log4j -->
81.             <dependency>
82.                 <groupId>log4j</groupId>
83.                 <artifactId>log4j</artifactId>
84.                 <version>${log4j.version}</version>
85.             </dependency>
86.
87.             <!-- spring整合MongoDB -->
88.             <dependency>
89.                 <groupId>org.springframework.data</groupId>
90.                 <artifactId>spring-data-mongodb</artifactId>
91.                 <version>${spring.data.version}</version>
92.             </dependency>
93.
94.         </dependencies>
95.
96.         <repositories>
97.             <repository>
98.                 <id>spring-milestone</id>
99.                 <name>Spring Maven MILESTONE Repository</name>
100.                <url>http://repo.spring.io/libs-milestone</url>
101.            </repository>
102.        </repositories>
103.
104.        <build>
105.            <plugins>
106.                <plugin>
107.                    <groupId>org.apache.maven.plugins</groupId>
108.                    <artifactId>maven-compiler-plugin</artifactId>
109.                    <configuration>
110.                        <source>1.7</source>
111.                        <target>1.7</target>
112.                    </configuration>
113.                </plugin>
114.            </plugins>
115.        </build>
116.    </project>

实体类【UserInfo.java】

1.      package com.jastar.demo.entity;
2.
3.      import java.io.Serializable;
4.      import java.sql.Timestamp;
5.
6.      import org.springframework.data.annotation.Id;
7.      import org.springframework.data.mongodb.core.index.IndexDirection;
8.      import org.springframework.data.mongodb.core.index.Indexed;
9.      import org.springframework.data.mongodb.core.mapping.Document;
10.     import org.springframework.data.mongodb.core.mapping.Field;
11.
12.     /**
13.      * 用户实体类
14.      * <p>
15.      * ClassName: UserInfo
16.      * </p>
17.      * <p>
18.      * Description:本类用来展示MongoDB实体类映射的使用
19.      * </p>
20.      * <p>
21.      * Copyright: (c)2017 Jastar·Wang,All rights reserved.
22.      * </p>
23.      *
24.      * @author Jastar·Wang
25.      * @date 2017年4月12日
26.      */
27.     @Document(collection = "coll_user")
28.     public class UserInfo implements Serializable {
29.
30.         /** serialVersionUID */
31.         private static final long serialVersionUID = 1L;
32.
33.         // 主键使用此注解
34.         @Id
35.         private String id;
36.
37.         // 字段使用此注解
38.         @Field
39.         private String name;
40.
41.         // 字段还可以用自定义名称
42.         @Field("myage")
43.         private int age;
44.
45.         // 还可以生成索引
46.         @Indexed(name = "index_birth", direction = IndexDirection.DESCENDING)
47.         @Field
48.         private Timestamp birth;
49.
50.         public String getId() {
51.             return id;
52.         }
53.
54.         public void setId(String id) {
55.             this.id = id;
56.         }
57.
58.         public String getName() {
59.             return name;
60.         }
61.
62.         public void setName(String name) {
63.             this.name = name;
64.         }
65.
66.         public int getAge() {
67.             return age;
68.         }
69.
70.         public void setAge(int age) {
71.             this.age = age;
72.         }
73.
74.         public Timestamp getBirth() {
75.             return birth;
76.         }
77.
78.         public void setBirth(Timestamp birth) {
79.             this.birth = birth;
80.         }
81.
82.     }
附录:
  • @Id - 用于字段级别,标记这个字段是一个主键,默认生成的名称是“_id”
  • @Document - 用于类,以表示这个类需要映射到数据库,您也可以指定映射到数据库的集合名称
  • @DBRef - 用于字段,以表示它将使用com.mongodb.DBRef进行存储。
  • @Indexed - 用于字段,表示该字段需要如何创建索引
  • @CompoundIndex - 用于类,以声明复合索引
  • @GeoSpatialIndexed - 用于字段,进行地理位置索引
  • @TextIndexed - 用于字段,标记该字段要包含在文本索引中
  • @Language - 用于字段,以设置文本索引的语言覆盖属性。
  • @Transient - 默认情况下,所有私有字段都映射到文档,此注解将会去除此字段的映射
  • @PersistenceConstructor - 标记一个给定的构造函数,即使是一个protected修饰的,在从数据库实例化对象时使用。构造函数参数通过名称映射到检索的DBObject中的键值。
  • @Value 这个注解是Spring框架的一部分。在映射框架内,它可以应用于构造函数参数。这允许您使用Spring表达式语言语句来转换在数据库中检索的键值,然后再用它来构造一个域对象。为了引用给定文档的属性,必须使用以下表达式:@Value(“#root.myProperty”),root要指向给定文档的根。
  • @Field - 用于字段,并描述字段的名称,因为它将在MongoDB BSON文档中表示,允许名称与该类的字段名不同。
  • @Version - 用于字段锁定,保存操作时检查修改。初始值是0,每次更新时自动触发。

【db.properties】

1.      ###---The mongodb settings---
2.      mongo.dbname=demo
3.      mongo.host=localhost
4.      mongo.port=27017
5.      mongo.connectionsPerHost=8
6.      mongo.threadsAllowedToBlockForConnectionMultiplier=4
7.      mongo.connectTimeout=1000
8.      mongo.maxWaitTime=1500
9.      mongo.autoConnectRetry=true
10.     mongo.socketKeepAlive=true
11.     mongo.socketTimeout=1500
12.     mongo.slaveOk=true
13.     mongo.writeNumber=1
14.     mongo.writeTimeout=0
15.     mongo.writeFsync=true

【spring-mgo.xml】

1.      <?xml version="1.0" encoding="UTF-8"?>
2.      <beans xmlns="http://www.springframework.org/schema/beans"
3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4.          xmlns:context="http://www.springframework.org/schema/context"
5.          xmlns:mongo="http://www.springframework.org/schema/data/mongo"
6.          xsi:schemaLocation="http://www.springframework.org/schema/context
7.                http://www.springframework.org/schema/context/spring-context-3.0.xsd
8.                http://www.springframework.org/schema/data/mongo
9.                http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd
10.               http://www.springframework.org/schema/beans
11.               http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
12.
13.         <!-- 读取属性文件 -->
14.         <context:property-placeholder location="classpath:db.properties" />
15.
16.         <!-- 启用注解支持 -->
17.         <context:annotation-config />
18.
19.         <!-- 扫描组件包 -->
20.         <context:component-scan base-package="com.jastar.demo" />
21.
22.         <!-- SpringData类型转换器 -->
23.         <mongo:mapping-converter id="mongoConverter">
24.             <mongo:custom-converters>
25.                 <mongo:converter>
26.                     <bean class="com.jastar.demo.converter.TimestampConverter" />
27.                 </mongo:converter>
28.             </mongo:custom-converters>
29.         </mongo:mapping-converter>
30.
31.         <!--
32.             MongoDB配置部分
33.             1.mongo:连接配置
34.             2.db-factory:相当于sessionFactory
35.             3.mongoTemplate:与数据库接口交互的主要实现类
36.         -->
37.         <mongo:mongo host="${mongo.host}" port="${mongo.port}">
38.             <mongo:options
39.                 connections-per-host="${mongo.connectionsPerHost}"
40.                 threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}"
41.                 connect-timeout="${mongo.connectTimeout}"
42.                 max-wait-time="${mongo.maxWaitTime}"
43.                 auto-connect-retry="${mongo.autoConnectRetry}"
44.                 socket-keep-alive="${mongo.socketKeepAlive}"
45.                 socket-timeout="${mongo.socketTimeout}"
46.                 slave-ok="${mongo.slaveOk}"
47.                 write-number="${mongo.writeNumber}"
48.                 write-timeout="${mongo.writeTimeout}"
49.                 write-fsync="${mongo.writeFsync}" />
50.         </mongo:mongo>
51.
52.         <mongo:db-factory id="mongoDbFactory" dbname="${mongo.dbname}" mongo-ref="mongo" />
53.
54.         <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
55.             <constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
56.             <constructor-arg name="mongoConverter" ref="mongoConverter" />
57.         </bean>
58.
59.     </beans>
说明:
  • mongo:options - 用于配置一些数据库连接设置信息
  • mongo:db-factory - 相当于Hibernate中的SessionFactory
  • mongoTemplate - 非常重要,整个与数据库的交互操作全是靠他,相当于Hibernate的HibernateTemplate

另外,以上配置中有一个类型转换器,因为Spring Data MongoDB本身默认时间类型是java.util.Date,如果实体字段含有java.sql.Timestamp类型,需要自定义转换器进行转换,否则后续操作会报错

【BaseDaoImpl.Java】

1.      package com.jastar.demo.dao.impl;
2.
3.      import static org.springframework.data.mongodb.core.query.Criteria.where;
4.
5.      import java.io.Serializable;
6.      import java.lang.reflect.Field;
7.      import java.lang.reflect.Method;
8.      import java.lang.reflect.Modifier;
9.      import java.util.ArrayList;
10.     import java.util.HashMap;
11.     import java.util.List;
12.     import java.util.Map;
13.
14.     import org.springframework.beans.factory.annotation.Autowired;
15.     import org.springframework.data.annotation.Id;
16.     import org.springframework.data.domain.Sort;
17.     import org.springframework.data.domain.Sort.Direction;
18.     import org.springframework.data.domain.Sort.Order;
19.     import org.springframework.data.mongodb.core.MongoTemplate;
20.     import org.springframework.data.mongodb.core.query.Query;
21.     import org.springframework.data.mongodb.core.query.Update;
22.
23.     import com.jastar.demo.dao.IBaseDao;
24.     import com.jastar.demo.util.EmptyUtil;
25.     import com.jastar.demo.util.PageModel;
26.
27.     /**
28.      * 基本操作接口MongoDB数据库实现类
29.      * <p>
30.      * ClassName: BaseDaoImpl
31.      * </p>
32.      * <p>
33.      * Description:本实现类适用于MongoDB数据库,以下代码仅供参考,本人水平有限,可能会存在些许问题(如有更好方案可告知我,一定虚心学习),
34.      * 再次提醒,仅供参考!!
35.      * </p>
36.      * <p>
37.      * Copyright: (c)2017 Jastar·Wang,All rights reserved.
38.      * </p>
39.      *
40.      * @author Jastar·Wang
41.      * @date 2017年4月12日
42.      */
43.     public abstract class BaseDaoImpl<T> implements IBaseDao<T> {
44.
45.         protected abstract Class<T> getEntityClass();
46.
47.         @Autowired
48.         protected MongoTemplate mgt;
49.
50.         @Override
51.         public void save(T entity) {
52.             mgt.save(entity);
53.         }
54.
55.         @Override
56.         public void update(T entity) {
57.
58.             // 反向解析对象
59.             Map<String, Object> map = null;
60.             try {
61.                 map = parseEntity(entity);
62.             } catch (Exception e) {
63.                 e.printStackTrace();
64.             }
65.
66.             // ID字段
67.             String idName = null;
68.             Object idValue = null;
69.
70.             // 生成参数
71.             Update update = new Update();
72.             if (EmptyUtil.isNotEmpty(map)) {
73.                 for (String key : map.keySet()) {
74.                     if (key.indexOf("{") != -1) {
75.                         // 设置ID
76.                         idName = key.substring(key.indexOf("{") + 1, key.indexOf("}"));
77.                         idValue = map.get(key);
78.                     } else {
79.                         update.set(key, map.get(key));
80.                     }
81.                 }
82.             }
83.             mgt.updateFirst(new Query().addCriteria(where(idName).is(idValue)), update, getEntityClass());
84.         }
85.
86.         @Override
87.         public void delete(Serializable... ids) {
88.             if (EmptyUtil.isNotEmpty(ids)) {
89.                 for (Serializable id : ids) {
90.                     mgt.remove(mgt.findById(id, getEntityClass()));
91.                 }
92.             }
93.
94.         }
95.
96.         @Override
97.         public T find(Serializable id) {
98.             return mgt.findById(id, getEntityClass());
99.         }
100.
101.        @Override
102.        public List<T> findAll() {
103.            return mgt.findAll(getEntityClass());
104.        }
105.
106.        @Override
107.        public List<T> findAll(String order) {
108.            List<Order> orderList = parseOrder(order);
109.            if (EmptyUtil.isEmpty(orderList)) {
110.                return findAll();
111.            }
112.            return mgt.find(new Query().with(new Sort(orderList)), getEntityClass());
113.        }
114.
115.        @Override
116.        public List<T> findByProp(String propName, Object propValue) {
117.            return findByProp(propName, propValue, null);
118.        }
119.
120.        @Override
121.        public List<T> findByProp(String propName, Object propValue, String order) {
122.            Query query = new Query();
123.            // 参数
124.            query.addCriteria(where(propName).is(propValue));
125.            // 排序
126.            List<Order> orderList = parseOrder(order);
127.            if (EmptyUtil.isNotEmpty(orderList)) {
128.                query.with(new Sort(orderList));
129.            }
130.            return mgt.find(query, getEntityClass());
131.        }

update方法不像关系型数据库那样,给个实体类就能更新,需要自己去想办法搞定。(所以MongoDB都是用来读写的,存储一些信息的)

【TestUseService.java】

1.      package com.jastar.test;
2.
3.      import java.sql.Timestamp;
4.      import java.util.List;
5.
6.      import org.junit.Test;
7.      import org.junit.runner.RunWith;
8.      import org.springframework.beans.factory.annotation.Autowired;
9.      import org.springframework.test.context.ContextConfiguration;
10.     import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
11.
12.     import com.jastar.demo.entity.UserInfo;
13.     import com.jastar.demo.service.UserService;
14.     import com.jastar.demo.util.PageModel;
15.
16.     @RunWith(SpringJUnit4ClassRunner.class)
17.     @ContextConfiguration(locations = "classpath:spring-mgo.xml")
18.     public class TestUserService {
19.
20.         @Autowired
21.         private UserService service;
22.
23.         @Test
24.         public void save() {
25.             UserInfo user = new UserInfo();
26.             user.setName("张三");
27.             user.setAge(25);
28.             user.setBirth(Timestamp.valueOf("2017-4-12 16:52:00"));
29.             service.save(user);
30.             System.out.println("已生成ID:" + user.getId());
31.         }
32.
33.         @Test
34.         public void find() {
35.             UserInfo user = service.find("58edf1b26f033406394a8a61");
36.             System.out.println(user.getName());
37.         }
38.
39.         @Test
40.         public void update() {
41.             UserInfo user = service.find("58edf1b26f033406394a8a61");
42.             user.setAge(18);
43.             service.update(user);
44.         }
45.
46.         @Test
47.         public void delete() {
48.             service.delete("58edef886f03c7b0fdba51b9");
49.         }
50.
51.         @Test
52.         public void findAll() {
53.             List<UserInfo> list = service.findAll("age desc");
54.             for (UserInfo u : list) {
55.                 System.out.println(u.getName());
56.             }
57.         }
58.
59.         @Test
60.         public void findByProp() {
61.             List<UserInfo> list = service.findByProp("name", "张三");
62.             for (UserInfo u : list) {
63.                 System.out.println(u.getName());
64.             }
65.         }
66.
67.         @Test
68.         public void findByProps() {
69.             List<UserInfo> list = service.findByProps(new String[] { "name", "age" }, new Object[] { "张三", 18 });
70.             for (UserInfo u : list) {
71.                 System.out.println(u.getName());
72.             }
73.         }

就例子总结与mysql 的对比

实例

用上面的框架做个demo,有2000个项目(集合为Project),每个项目里分别有500个案例(Case)。我建立的一个案例对应一个项目结构,每条案例引用一个项目,(类似于每个学生有班级的id,之前一直用嵌入的结构,tm返回结果大于16M,弄了好几天,还是不能解决,所以决定换成这样的存储)

_images/project.jpg _images/case.jpg

集合结构 Project和Case

_images/projectPojo.jpg

实体类 Project

_images/casePojo.jpg
实体类Case
  • @Indexed(name = “time”, direction = IndexDirection.DESCENDING)为time字段建立索引
  • @DBRef引用Project集合
_images/save1.jpg _images/save2.jpg

先保存入MongoDB的数据,案例里有时间(time)的字段,意思是每个案例有不同的时间,我是利用随机数建立的随机时间。

红框的部分:MongoDB不支持级联保存,MongoDB不支持事务,所以分别保存,先保存被引用的,再保存引用的。

_images/test.jpg

输出结果(我在Case实体类里重写了toString)

_images/print.jpg

与Mysql对比

我分别测试了MongoDB和Mysql:

在某个时间段内的所有案例,结果集大约为38万条数据,MongoDB比Mysql查询速率快了约40秒

而在一个项目里的某段时间的案例,结果集大约190条数据,Mysql比MongoDB快了约1000毫秒

相比较MySQL,MongoDB数据库更适合那些读作业较重的任务模型。MongoDB能充分利用机器的内存资源。如果机器的内存资源丰富的话,MongoDB的查询效率会快很多。

在插入数据时候,2000个项目,也就是说有1000000个案例,我用Mysql插了一上午啊(而且经常出现报错,提示连接数不够,我用spring+Mybatis整合的,也许我哪参数配的少了吧),而MongoDB花了近五分钟,就插入完成了。

MongoDB优势

  1. MongoDB适合那些对数据库具体数据格式不明确或者数据库数据格式经常变化的需求模型,而且对开发者十分友好。
  2. MongoDB官方就自带一个分布式文件系统,可以很方便地部署到服务器机群上。MongoDB里有一个Shard的概念,就是方便为了服务器分片使用的。每增加一台Shard,MongoDB的插入性能也会以接近倍数的方式增长,磁盘容量也很可以很方便地扩充。
  3. MongoDB还自带了对map-reduce运算框架的支持,这也很方便进行数据的统计。

MongoDB缺点

  1. 事务关系支持薄弱。这也是所有NoSQL数据库共同的缺陷,不过NoSQL并不是为了事务关系而设计的,具体应用还是很需求。(所以他不能完全代替关系型数据库,可以说是辅佐型,对关系型的补充)
  2. 稳定性有些欠缺。
  3. MongoDB一方面在方便开发者的同时,另一方面对运维人员却提出了相当多的要求。

应用场景

  • 网站数据:Mongo 非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。
  • 缓存:由于性能很高,Mongo 也适合作为信息基础设施的缓存层。在系统重启之后,由Mongo 搭建的持久化缓存层可以避免下层的数据源过载。
  • 大尺寸、低价值的数据:使用传统的关系型数据库存储一些数据时可能会比较昂贵,在此之前,很多时候程序员往往会选择传统的文件进行存储。
  • 高伸缩性的场景:Mongo 非常适合由数十或数百台服务器组成的数据库,Mongo 的路线图中已经包含对MapReduce 引擎的内置支持。
  • 用于对象及JSON 数据的存储:Mongo 的BSON 数据格式非常适合文档化格式的存储及查询。
  1. 用在应用服务器的日志记录,查找起来比文本灵活,导出也很方便。也是给应用练手,从外围系统开始使用MongoDB。用在一些第三方信息的获取或者抓取,因为MongoDB的无模式,所有格式灵活,不用为了各种格式不一样的信息专门设计统一的格式,极大的减少开发的工作。
  2. 主要用来存储一些监控数据,No schema 对开发人员来说,真的很方便,增加字段不用改表结构,而且学习成本极低。
  3. 使用MongoDB做了O2O快递应用,·将送快递骑手、快递商家的信息(包含位置信息)存储在 MongoDB,然后通过 MongoDB 的地理位置查询,这样很方便的实现了查找附近的商家、骑手等功能,使得快递骑手能就近接单,目前在使用MongoDB 上没遇到啥大的问题,官网的文档比较详细,很给力
  4. 游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、更新
  5. 物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来。
  6. 社交场景,使用 MongoDB 存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能
  7. 物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析
  8. 视频直播,使用 MongoDB 存储用户信息、礼物信息等