A Simple Tutorial with Super Heroes
2 A Simple Tutorial with Super Heroes
本章将循序渐进地介绍 Voyage(对象到文档映射器)提供的各种可能性。 我们将使用一个简单但并不简约的领域:超级英雄、技能及其装备。您将学习如何保存和检索对象。
2.1 创建连接
安装好 MongoBD 后,我们就可以开始连接数据库了,如下所示:
| repository |
repository := VOMongoRepository
host: 'localhost'
database: 'superHeroes'.
repository enableSingleton.
如果没有连接到数据库,可以使用内存存储库(用于应用程序原型开发)。
| repository |
repository := VOMemoryRepository new.
repository enableSingleton
通过这个方法,你可以像连接真实的数据库一样工作,以后在开发过程中可以透明地切换模式。
通常,我们定义一个方法来建立资源库。例如,我们可以在稍后的 Hero
类中添加一个类方法。
Hero class >> setUpConnection
| repository |
repository := VOMongoRepository
host: 'localhost'
database: 'superHeroes'.
repository enableSingleton.
2.2 SuperHeroes
现在,我们可以定义域的第一个版本。下图显示了我们将在本教程中使用的模型。
2.3 英雄
我们来定义Hero
类
Object subclass: #Hero
instanceVariableNames: 'name level powers'
classVariableNames: ''
package: 'SuperHeroes'
Hero >> name
^ name
Hero >> name: aString
name := aString
Hero >> level
^ level
Hero >> level: anObject
level := anObject
Hero >> powers
^ powers ifNil: [ powers := Set new ]
Hero >> addPower: aPower
self powers add: aPower
2.4 技能
我们来定义Power
类
Object subclass: #Power
instanceVariableNames: 'name'
classVairableNames: ''
package: 'SuperHeroes'
Power >> name
^ name
Power >> name: aString
name := aString
添加printOn:
,以改进超级英雄的导航和调试的方法。
2.5 根类
现在,我们必须决定要保存和查询的对象是什么。为此,我们应声明要保存的对象图的根。根可以是系统中的任何类。声明根的方法是在我们所要保存的对象的类侧实现类方法 isVoyageRoot
。我们稍后会看到定义根的意义。现在,我们只需将 Hero
定义为根。
Hero class >> isVoyageRoot
^ true
我们就可以创建一些超级英雄,然后把他们保存在数据库中。
Hero new
name: 'Spiderman';
level: #epic;
addPower: (Power new name: 'Super-strength');
addPower: (Power new name: 'Wall-climbing');
addPower: (Power new name: 'Spider instinct');
save.
Hero new
name: 'Wolverine';
level: #epic;
addPower: (Power new name: 'Regeneration');
addPower: (Power new name: 'Adamantium claws');
save.
2.6 检查MongoDB
我们可以直接在数据库中查看对象的保存情况。
> show dbs
local 0.078GB
superHeroes 0.078GB
> use superHeroes
switched to db superHeroes
> show collections
Hero
现在我们可以看到超级英雄实际上是如何存储的。db.Hero.find()[0]
获取集合中的第一个对象。
> db.Hero.find()[0]
{
"_id" : ObjectId("d847065c56d0ad09b4000001"),
"#version" : 688076276,
"#instanceOf" : "Hero",
"level" : "epic",
"name" : "Spiderman",
"powers" : [
{
"#instanceOf" : "Power",
"name" : "Spider instinct"
},
{
"#instanceOf" : "Power",
"name" : "Super-strength"
},
{
"#instanceOf" : "Power",
"name" : "Wall-climbing"
}
]
}
请注意技能的保存方式:它们被嵌入到代表超级英雄的文档中。
2.7 查询
回到Pharo中,我们可以执行一些查询,以获取存储在数据库中的对象。
Hero selectAll.
> an OrderedCollection(a Hero( Spiderman ) a Hero( Wolverine ))
Hero selectOne: [ :each | each name = 'Spiderman' ].
> a Hero( Spiderman )
Hero selectMany: [ :each | each level = #epic ].
> an OrderedCollection(a Hero( Spiderman ) a Hero( Wolverine ))
由于 MongoDB 内部存储的是 JSON,因此查询的参数可以是一个字典,如下所示:
Hero selectOne: { #name -> 'Spiderman' } asDictionary.
> a Hero( Spiderman )
Hero selectMany: { #level -> #epic } asDictionary.
> an OrderedCollection(a Hero( Spiderman ) a Hero( Wolverine )
下面是一个更复杂的查询:
Hero
selectMany: { #level -> #epic } asDictionary
sortBy: { #name -> VOOrder ascending } asDictionary
limit: 10
offset: 0
2.8 其他基本操作
计数
Hero count.
> 2
Hero count: [ :each | each name = 'Spiderman' ]
> 1
删除
hero := Hero selectAll anyOne.
hero remove.
> a Hero
从类中删除所有对象。
Hero removeAll.
> Hero class
2.9 添加一个新的根
现在,我们将改变需求,表明我们希望能够查询另一类对象:技能。请注意,在添加根对象时,必须刷新数据库或执行迁移,例如加载旧对象并重新发布它们。
每次更改数据库 "模式 "时,都应使用以下表达式重置数据库:
VORepository current reset.
什么时候需要添加新的根类
在面对是否有必要添加一个类作为根的问题时,有两个要点需要考虑。
-
首先,一个显而易见的考虑因素是,我们是否需要将查询对象与引用对象分开。
-
其次,如果需要确保共享子部分而不重复,则应将子部分声明为根。例如,如果您需要在两个超级英雄之间共享一种能力,并希望确保在加载两个超级英雄时不会获得相同技能的两个副本。
2.10 Power作为根
Power class >> isVoyageRoot
^ true
现在,我们可以单独保存技能对象,如下所示:
Power new name: 'Fly'; save.
Power new name: 'Super-strength'; save.
如果使用 show collections 功能在数据库中看不到新的对象,可能是遇到了 Voyage 的 BUG,需要在 Pharo 图像中重置内存数据库缓存:
VORepository current reset.
现在保存对象并再次检查 mongo 数据库,应该会显示
> show collections
Hero
Power
现在我们可以保存英雄及其技能。为了进行全面的测试,我们将执行 Hero removeAll
来清除数据库中的英雄,并执行以下操作:
| fly superStrength |
fly := Power selectOne: [ :each | each name = 'Fly'].
superStrength := Power selectOne: [ :each | each name = 'Super-strength'].
Hero new
name: 'Superman'; level: #epic;
addPower: fly;
addPower: superStrength;
save.
请注意,虽然我们保存的技能与英雄无关,但这并不是必须的,因为保存英雄会自动保存其技能。
现在,当我们查询数据库时,我们可以看到一个英雄引用了另一个技能集合,而这些技能并没有嵌套在英雄对象中。
2.11 关系
Voyage支持根对象之间的循环引用,但不支持对嵌入对象的循环引用。我们将在下一节中看到这一点。
2.12 扩展Hero
类
现在,我们将用装备来扩展 Hero
类。这个示例表明,根集合声明是静态的:当一个超类被定义为根集合时,mongo db 中的集合将包含该类及其子类的实例。如果我们想让每个子类都有一个集合,就必须将每个子类都定义为根集合,并在每个类中复制 isVoyageRoot
方法。
我们为 Hero
类添加一个名为 equipment
的新实例变量。
Object subclass: #Hero
instanceVariableNames: 'name level powers equipment'
classVariableNames: ''
package: 'SuperHeroes'
Hero >> equipment
^ equipment ifNil: [ equipment := Set new ]
Hero >> addEquipment: anEquipment
self equipment add: anEquipment
由于我们更改了类的结构,因此我们应该执行VORepository current reset
来重置数据库的本地缓存。
现在,我们把Equipment
定义为新的根类。
Object subclass: #Equipment
instanceVariableNames: ''
classVariableNames: ''
package: 'SuperHeroes'
Equipment class >> isVoyageRoot
^ true
然后我们定义两个子类Weapon
和Armor
Equipment subclass: #Weapon
instanceVariableNames: ''
classVariableNames: ''
category: 'SuperHeroes'
Equipment subclass: #Armor
instanceVariableNames: ''
classVariableNames: ''
category: 'SuperHeroes'
现在,保存一个携带装备的英雄时,也会把装备作为单独的对象进行保存
Hero new
name: 'Iron-Man';
level: #epic;
addEquipment: Armor new;
save.
我们可以看到对象是如何保存在数据库中的:
> db.Hero.find()[1]
{
"_id" : ObjectId("d8475734421aa909b4000001"),
"#instanceOf" : "Hero",
"#version" : NumberLong("2898020230"),
"equipment" : [
{
"#instanceOf" : "Armor"
}
],
"level" : "epic",
"name" : "Iron-Man",
"powers" : null
}
因为我们没有将Weapon
和Armor
定义成单独的根,所以数据库中只有一个名为Equipment
的集合,其中既包含武器又包含盔甲。
2.13 装备也可以拥有技能
事实上,装备也可以有技能(就像雷神之锤)。因此,我们为设备增加了以下技能:
Object subclass: #Equipment
instanceVariableNames: 'powers'
classVariableNames: ''
package: 'SuperPowers'
Equipment >> powers
^ powers ifNil: [ powers := Set new ]
Equipment >> addPower: aPower
self powers add: aPower
由于我们改变了类的结构,我们应该重置数据库的本地缓存:
VORepository current reset
现在,我们可以为铁人添加一个具有技能的装备,如下所示:
| hero fly superStrength |
hero := Hero selectOne: [ :each | each name = 'Iron-Man' ].
fly := Power selectOne: [ :each | each name = 'Fly' ].
superStrength := Power selectOne: [ :each | each name = 'Super-strength' ].
hero addEquipment: (Armor new
addPower: fly;
addPower: superStrength;
yourself);
save.
我们在数据库中看到,"装备 "集合包含 "盔甲 "对象。
> db.Equipment.find()[0]
{
"_id" : ObjectId("d8475777421aa909b4000003"),
"#instanceOf" : "Armor",
"#version" : NumberLong("4204064627")
}
请注意,一个装备可以包含另一个装备。由于 Equipment
类是一个集合根,因此我们没有任何东西来处理循环引用。
2.14 总结
这个小教程展示了在 Mongo 数据库中存储对象是多么容易。它补充了各种可能的解决方案,如使用 Fuel 序列化对象、使用内存 SandStone 方法或使用 Garage 进行更传统的关系数据库映射。