在前面的Mongo In Action实战中,我们介绍了如何进行数据库建模。本篇我们讨论一个更为详细的内容,文档与插入。MongoDB是一个文档数据库,因此文档时MongoDB的核心概念。对于文档的各类操作也是MongoDB中最为基础的操作。
我们从下面几个方面来分别介绍文档及相应的文档插入操作:
1. 文档序列化、类型和限制
在实际操作中,所有的文档都会在发送到MongoDB之前被序列化为BSON。然后再由驱动将文档从BSON序列化到语言自己的文档表述。大多数的驱动都提供了一个简单的接口,用于进行BSON的序列化和反序列化。比如我们可以通过BSON的序列化来了解文档传递给MongoDB时的字节大小:
doc={
:_id=>BSON::ObjectId.new,
:username=>'kbanker',
:action_code=>rand(5),
:time=>Time.now.utc,
:n=>1
}
bson=doc.to_bson
puts "Document #{doc.inspect} take up #{bson.length} bytes as BSON"
发现的产生的文档数据的大小为82个字节。
反序列化文档也很简单,将序列化对象调用from_bson就可以实现反序列化。
请注意,不是所有的Ruby散列都能被序列化。要正确序列化,键名必须合法,每个值都必须能转换为BSON类型。合法的键名由null结尾的字符串组成,最大长度为255字节。字符串可以包含任意ASCII字符的组合,但又三种情况除外:不能以$开头,不能包含.字符,除了结尾处外不能包含null字节。在Ruby里,可以用符号充当散列的键,在序列化时它们被转换为等效的字符串。
应当慎重选择键名的长度,因为这是存储在文档里面的。而在RDBMS中,列名总是与数据分开保存的。除了合法的键名,文档还必须包含可以序列化为BSON的值,这边列出了一些经常容易遇到的陷阱。
(1)字符串:所有字符串都必须编码为UTF-8,虽然UTF-8就快成为字符编码的行业标准了,但是还是有很多地方仍然在使用旧的编码。在将数据从遗留系统导入到MongoDB时,用户通常会遇到一些问题。解决方案一般是在插入前将内容转换为UTF-8,或者将文本保存为BSON二进制类型。
(2)数字:BSON规定了三种数字类型:double,int和long。也就是说BSON可以编码各种IEEE浮点数值,以及各种八字节以内的带符号整数。在动态语言里序列化整数时,驱动会自己决定将其转化为int还是double。实际上,只有一种常见情况需要显式地决定数字类型,那就是通过JavaScript Shell插入数字数据时,很遗憾,JavaScript天生就支持一种数字类型,即Number,它等价于IEEE的双精度浮点数。因此,如果希望在shell里将一个数字保存为整数,需要使用NumberLong()或NumberInt().具体操作如下图所示,使用find查找n等于5的会返回两个文档。:
若使用$type操作符来查询BSON类型。每种BSON类型都是由一个从1开始的整数来标识的,双精度浮点数的类型是1,而64位整数的类型是18。使用$type的查询结果如下图:
附件中会给出BSON类型对应的标识整数信息。
另一个与BSON数字类型有关的问题就是缺乏对小数的支持,这意味着在MongoDB中保存货币值需要使用整数类型,并且以美分为单位来保存货币值。
(3) 日期时间
首先,如果在JavaScript中创建日期,请牢记JavaScript日期里的月份是从0开始的。也就是说new Date(2017,6,17)创建出的是2017年7月17日。其次,如果使用Ruby确定存储时间数据,BSON序列化器会期待传入一个UTC格式的Ruby Time对象。其结果就是,不能使用带时区信息的日期类,因为BSON日期时间函数无法对它进行编码。
(4) 自定义类型
如果希望连同时区一起存入时间改怎么办,虽然无法创建自定义BSON类型,但可以结合几个不同的原生BSON值,由此创建一个自己的虚拟类型。比如创建一个带有时区的时间信息,Ruby代码如下:
{
:time_with_zone=>{
:time=>Time.now.utc,
:zone=>'EST'
}
}
控制文档的大小,一是为了防止开发者创建难看的数据类型,避免深层次的嵌套;二是考虑查询性能,在服务器端查询大文档,在将结果发送各客户端之前需要将文档复制到缓冲区,复制动作代价很大。
如果有很大的对象,应该将他们拆开,修改其数据类型。使用一到两个额外的集合。如果仅仅存储大的二进制对象,比如图像或视频,这又是另外一种情况。
2. 批量插入
有了正确的文档后,就应该执行插入操作了。本部分主要探讨批量插入。所有的驱动都可以一次插入多个文档。下面的例子中,一次构造了40个文档数组,然后将整个文档数组传递给insert方法。
require 'mongo'
VIEW_PRODUCT =0
ADD_TO_CART =1024
CHECKOUT =2
PURCHASE =3
con=Mongo::Client.new(['127.0.0.1:27017'],:database=>'test')
logInfo=con['logInfo']
docs=(0..40).map do |n|
{
:username=>'kbanker',
:action_code=>rand(5),
:time=>Time.now.utc,
:n=>n
}
end
logInfo.insert_many(docs)