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

然后我们定义两个子类WeaponArmor

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
}

因为我们没有将WeaponArmor定义成单独的根,所以数据库中只有一个名为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 进行更传统的关系数据库映射。

posted @ 2024-02-18 12:22  fmcdr  阅读(21)  评论(0编辑  收藏  举报