Voyage系列3: 技巧与提示

本章汇集了多年来大家积累的一些实用技巧与心得,由萨宾娜·马纳撰写。

如何通过id查询对象

若已知_id值,可将其初始化为OID并进行查询。

Person selectOne: {('_id' -> (OID value: 16r55CDD2B6E9A87A520F000001))} asDictionary.

请注意以下两种方式是等效的:

OID value: 26555050698940995562836590593. "dec"
OID value: 16r55CDD2B6E9A87A520F000001. "hex"

或者,如果您拥有一个根集合中的实例(本例中为Person实例),可以直接获取其voyageId并用于查询。以下示例假设存在Trips和Persons两个根集合:行程数据中包含嵌入的收据集合,而收据具有description属性。该查询将检索指定人员所有至少包含一张描述为aString的收据的行程记录。

 Trip
		selectMany:
			{('receipts.description' -> aString).
			('person._id' -> aPerson voyageId)} asDictionary

尚未支持的Mongo命令

indexes

目前尚无法通过Voyage直接创建和删除索引,但您可以使用OSProcess来实现。

假设您有一个名为myDB的数据库,其中包含名为Trips的集合。行程数据中嵌有收据集合,而收据具有名为description的属性。此时您可以通过以下方式在description字段上创建索引:

OSProcess command:
'/{pathToMongoDB}/MongoDB/bin/mongo --eval "db.getSiblingDB(''myDB'').Trips.createIndex({''receipts.description'':1})"'

通过以下命令删除Trips集合中的所有索引:

OSProcess command:
'/{pathToMongoDB}/MongoDB/bin/mongo --eval "db.getSiblingDB(''myDB'').Trips.dropIndexes()"'

Backup

目前尚无法通过Voyage直接创建备份,请使用

OSProcess command:
 '/{pathToMongoDB}/MongoDB/bin/mongodump  --out {BackupPath}'

请参阅MongoDB文档了解相关命令,特别是--eval命令的使用方法。

有用的mongo命令

在mongo控制台中使用“.explain()”来确认查询是否真正使用了索引。

例如:

在嵌入式属性(description)上创建索引:

> db.Trips.createIndex({"receipts.description":1})

执行查询并调用explain方法。可见仅扫描了2个文档:

db.Trips.find({"receipts.description":"a"}).explain("executionStats")
{
"cursor" : "BtreeCursor receipts.receiptDescription_1",
"isMultiKey" : true,
"n" : 2,
"nscannedObjects" : 2,
"nscanned" : 2,
"nscannedObjectsAllPlans" : 2,
"nscannedAllPlans" : 2,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"indexBounds" : {
"receipts.receiptDescription" : [
["a", "a"]
]
},
"allPlans" : [
{
"cursor" : "BtreeCursor receipts.receiptDescription_1",
"n" : 2,
"nscannedObjects" : 2,
"nscanned" : 2,
"indexBounds" : {
"receipts.receiptDescription" : [
["a", "a"]
]
}
}
],
"server" : "MacBook-Pro-Sabine.local:27017"
}

现在,删除索引

> db.Trips.dropIndexes()
{
	"nIndexesWas" : 2,
	"msg" : "non-_id indexes dropped for collection",
	"ok" : 1
}

再次执行查询,此时会扫描所有文档。

> db.Trips.find({"receipts.receiptDescription":"a"}).explain("executionStats")
{
	"cursor" : "BasicCursor",
	"isMultiKey" : false,
	"n" : 2,
	"nscannedObjects" : 246,
	"nscanned" : 246,
	"nscannedObjectsAllPlans" : 246,
	"nscannedAllPlans" : 246,
	"scanAndOrder" : false,
	"indexOnly" : false,
	"nYields" : 0,
	"nChunkSkips" : 0,
	"millis" : 1,
	"indexBounds" : {

	},
	"allPlans" : [
		{
			"cursor" : "BasicCursor",
			"n" : 2,
			"nscannedObjects" : 246,
			"nscanned" : 246,
			"indexBounds" : {

			}
		}
	],
	"server" : "MacBook-Pro-Sabine.local:27017"
}

在MongoDB中存储Date类型的实例

MongoDB存在一个已知问题:无法区分Date和DateAndTime类型。因此即使您存储的是Date类型,查询返回的将是DateAndTime类型。在具体化对象时,您需要手动将其转换回Date类型。

数据库设计

对象结构通常不会形成简单的树形关系,而是包含循环引用的图结构。例如,人员对象可能指向其行程记录,而每个行程记录又关联到对应的人员(形成Person<->>Trip双向关系)。通过分别为Persons和Trips创建根集合,可以避免生成无限循环(详见第1.2章说明)。

这是一个行程指向另一个根集合中人员对象的示例,同时还涉及另一个根集合paymentMethod。需要注意的是,收据也反向指向行程,但这并不会形成循环引用。

Trip
{
 "_id" : ObjectId("55cf2bc73c9b0fe702000008"),
"#version" : 876079653,
"person" : {
	"#collection" : "Persons",
	"_id" : ObjectId("55cf2bbb3c9b0fe702000007") },
"receipts" : [
	{ "currency" : "EUR",
	"date" : { "#instanceOf" : "ZTimestamp", "jdn" : 2457249, "secs" : 0 },
	"exchangeRate" : 1,
	"paymentMethod" : {
		"#collection" : "PaymentMethods",
		"_id" : ObjectId("55cf2bbb3c9b0fe702000003") },
	"receiptDescription" : "Taxi zum Hotel",
	"receiptNumber" : 1,
	"trip" : {
		"#collection" : "Trips",
		"_id" : ObjectId("55cf2bc73c9b0fe702000008") } } ],
	"startPlace" : "Österreich",
	"tripName" : "asdf",
	"tripNumber" : 1 }

对应的人员对象会指向其所有行程记录以及所属公司

{ "#version" : 714221829,
"_id" : ObjectId("55cf2bbb3c9b0fe702000007"),
"bankName" : "",
 "company" : {
"#collection" : "Companies",
"_id" : ObjectId("55cf2bbb3c9b0fe702000002") },
"email" : "bb@spesenfuchs.de",
"firstName" : "Berta",
"lastName" : "Block",
"roles" : [  "user" ],
"tableOfAccounts" : "SKR03",
"translator" : "German",
"trips" : [
{
"#collection" : "Trips",
"_id" : ObjectId("55cf2bc73c9b0fe702000008") } ] }

如果您的业务领域存在严格划分的区域(例如客户管理),可以考虑为每个区域(客户)创建独立的存储库。

数据检索

问题是:能否从Mongo集合中检索即使该数据库并非通过Voyage创建的数据?答案是肯定的。以下是解决方案。

首先我们创建一个包含两个类方法的MyClass:

MyClass class >> isVoyageRoot
	^ true
MyClass class >> descriptionContainer
    <voyageContainer>
    ^ VOContainer new
        collectionName: 'myCollection';
        yourself

此外,为了正确读取数据,需要根据数据库中的内容添加相应的实例变量。

例如,若数据库中存在以下存储信息:

{ "_id" : ObjectId("5900a0175bc65a2b7973b48a"), "item" : "canvas", "qty" : 100, "tags" : [ "cotton" ] }

此时MyClass应包含实例变量:item、qty、tags及相应的访问方法。随后我们在类端定义以下描述:

MyClass class >> mongoItem
	<mongoDescription>
	^ VOToOneDescription new
		attributeName: 'item';
		kind: String;
		yourself
MyClass class >> mongoQty
	<mongoDescription>
	^ VOToOneDescription new
		attributeName: 'qty';
		kind: Integer;
		yourself
MyClass class >> mongoTags
	<mongoDescription>
	^ VOToOneDescription new
		attributeName: 'tags';
		kind: OrderedCollection;
		yourself

此后便可连接数据库并获取信息。

| repository |
repository := VOMongoRepository database: 'databaseName'.
repository selectAll: MyClass
posted @ 2025-10-27 09:23  fmcdr  阅读(5)  评论(0)    收藏  举报