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 进行更传统的关系数据库映射。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2022-02-18 把 Common Lisp 当作脚本语言(2015版)
2022-02-18 ASDF 3 - 为什么说 Lisp 现在是一种可以接受的脚本语言