【前往Cassandra Day的路上】使用对象映射进行数据库编程(Python)
首先
东京的卡桑德拉日
今年的6月1日,Cassandra Day将在日本举办。去年,Cassandra Day已经在柏林、伦敦、阿姆斯特丹、河内、雅加达、休斯顿、圣克拉拉、西雅图和新加坡成功举办过。
这次在东京举办活动,我们将发布与Apache Cassandra相关的文章。

关于Apache Cassandra
Apache Cassandra是一个开源的分布式数据库管理系统。
与其他分布式数据库管理系统相同,我们将使用多个通用服务器来构建一个数据库(为了开发等目的,也可以只使用一个服务器来构建)。
在这里,我们将省略详细的说明,将介绍感兴趣的人的角色委托给官方网站或维基百科。
通过对象映射来进行数据库编程
数据库和对象映射
在数据库中管理的数据(记录)和面向对象编程中使用的数据(对象)之间的转换通常需要掌握专用库,尤其是在关系型数据库中。这样的功能被称为对象(关系)映射。
Cassandra与对象映射
在Cassandra中,对象映射功能由cassandra.cqlengine包提供。
本文将介绍如何利用该程序包提供的对象映射功能进行编程的概要。
关于处理Cassandra数据模型的详细信息,请参考上述文档。然而,Cassandra使用CQL(Cassandra Query Language)协议进行本地访问,如果你有关系型数据库的经验,你应该可以几乎完全理解下面的代码(如果出现不熟悉的术语如”聚类键”,请适时查阅相关文档。不过,应该没有问题来掌握整体的概念)。
示例程序
首先,我们将展示示例程序的整体结构。
import uuid
from cassandra.cqlengine import columns
from cassandra.cqlengine import connection
from datetime import datetime
from cassandra.cqlengine.management import sync_table
from cassandra.cqlengine.models import Model
# まず、モデルを定義します。
class ExampleModel(Model):
example_id = columns.UUID(primary_key=True, default=uuid.uuid4)
example_type = columns.Integer(index=True)
created_at = columns.DateTime()
description = columns.Text(required=False)
# 次にCassandraデータベースとの接続を行います。
# 利用できるオプションについては、次を参照: http://datastax.github.io/python-driver/api/cassandra/cluster.html
# 配列として、接続先ホストのリストをClusterインスタンス作成時に渡します(ここでは開発環境を想定し、ローカルホスト一つを利用)。
connection.setup(['127.0.0.1'], "cqlengine", protocol_version=3)
以下是在Python命令行界面上执行并输出结果的示例图像。
# 以下の操作により、定義したモデルのテーブルがCassandraに作成されます。
>>> sync_table(ExampleModel)
# これでテーブルにレコード(列)を追加することができます。
>>> em1 = ExampleModel.create(example_type=0, description="example1", created_at=datetime.now())
>>> em2 = ExampleModel.create(example_type=0, description="example2", created_at=datetime.now())
>>> em3 = ExampleModel.create(example_type=0, description="example3", created_at=datetime.now())
>>> em4 = ExampleModel.create(example_type=0, description="example4", created_at=datetime.now())
>>> em5 = ExampleModel.create(example_type=1, description="example5", created_at=datetime.now())
>>> em6 = ExampleModel.create(example_type=1, description="example6", created_at=datetime.now())
>>> em7 = ExampleModel.create(example_type=1, description="example7", created_at=datetime.now())
>>> em8 = ExampleModel.create(example_type=1, description="example8", created_at=datetime.now())
# テーブルに対してクエリを実行することもできます。
>>> ExampleModel.objects.count()
8
>>> q = ExampleModel.objects(example_type=1)
>>> q.count()
4
>>> for instance in q:
>>> print instance.description
example5
example6
example7
example8
# ここでは、追加のフィルターを適用します。
# queryオブジェクトはイミュータブルです。つまり、ここで戻されるオブジェクト(q2)は、新しいオブジェクトです。
>>> q2 = q.filter(example_id=em5.example_id)
>>> q2.count()
1
>>> for instance in q2:
>>> print instance.description
example5
模型概要
模型是表示CQL表格的Python类。模型是从Model派生出来的,并定义了基本表格属性和表格列。
模型的列会映射到CQL表的列上。通过在模型类中定义列的属性,可以定义CQL列。为了使模型有效,至少需要一个主键列和一个非主键列。与CQL类似,定义列的顺序也很重要。这与在对应的表中定义列的顺序相同。
以下是定义模型的基本示例。有关详细信息,请参阅文档。
桥本是模特的典范。
在这个示例中,我们定义了一个继承自Model类并具有列first_name和last_name的Person类。
from cassandra.cqlengine import columns
from cassandra.cqlengine.models import Model
class Person(Model):
id = columns.UUID(primary_key=True)
first_name = columns.Text()
last_name = columns.Text()
此”Person”类对应以下CQL表。可以使用”create table”语句来表示。
CREATE TABLE cqlengine.person (
id uuid,
first_name text,
last_name text,
PRIMARY KEY (id)
);
接下来,我们将介绍另一种模型定义方法的例子,即Comment类。
在这里,作为一个新的要素,我们可以利用clustering_order=”DESC”这个降序(DESC)的聚类键。
from cassandra.cqlengine import columns
from cassandra.cqlengine.models import Model
class Comment(Model):
photo_id = columns.UUID(primary_key=True)
comment_id = columns.TimeUUID(primary_key=True, clustering_order="DESC")
comment = columns.Text()
上述的 Comment 模型对应的表格如下所示。通过 create table 语句创建。
CREATE TABLE comment (
photo_id uuid,
comment_id timeuuid,
comment text,
PRIMARY KEY (photo_id, comment_id)
) WITH CLUSTERING ORDER BY (comment_id DESC);
同步这些模型到数据库,按照以下步骤进行操作。(注意,在此处,Person和Comment不是对象,而是类)
from cassandra.cqlengine.management import sync_table
sync_table(Person)
sync_table(Comment)
模型同步需要进行模式更改。必须考虑对数据库的影响并进行操作。有关注意事项,请参阅文档中的模式管理说明。
模型操作
模型实例可以通过类似字典操作的方式进行访问,具体如下。
class Person(Model):
first_name = columns.Text()
last_name = columns.Text()
kevin = Person.create(first_name="Kevin", last_name="Deldycke")
dict(kevin) # returns {'first_name': 'Kevin', 'last_name': 'Deldycke'}
kevin['first_name'] # returns 'Kevin'
kevin.keys() # returns ['first_name', 'last_name']
kevin.values() # returns ['Kevin', 'Deldycke']
kevin.items() # returns [('first_name', 'Kevin'), ('last_name', 'Deldycke')]
kevin['first_name'] = 'KEVIN5000' # changes the models first name
模型验证的扩展
每次将模型实例保存到cqlengine中时,模型内的数据将根据为模型定义的架构进行验证。这种验证通常是显而易见的。例如,它会确保不会混淆不同的数据类型,也就是说,不会将文本保存为整数列。此外,它还会执行必要的转换以正确保存数据。
然而,除了确保在尝试插入数据时不会在Cassandra数据库中出现错误之外,还可能希望添加额外的约束和转换。要定义额外的验证逻辑以验证模型,需要扩展该模型的validate方法。
class Member(Model):
person_id = UUID(primary_key=True)
name = Text(required=True)
def validate(self):
super(Member, self).validate()
if self.name == 'jon':
raise ValidationError('no jon\'s allowed')
如果验证失败,可以引发(cassandra.cqlengine.) ValidationError。然而,这并非必需。也可以考虑将其转换为失败验证的默认值等其他处理方法。
模型的继承
使用单个CQL表格,您可以保存和加载不同的模型类。这对于希望将不同对象类型存储在单个Cassandra行中的情况非常有用。
假设有一个存储猫和狗相关信息的宠物表格。
class Pet(Model):
__table_name__ = 'pet'
owner_id = UUID(primary_key=True)
pet_id = UUID(primary_key=True)
pet_type = Text(discriminator_column=True)
name = Text()
def eat(self, food):
pass
def sleep(self, time):
pass
class Cat(Pet):
__discriminator_value__ = 'cat'
cuteness = Float()
def tear_up_couch(self):
pass
class Dog(Pet):
__discriminator_value__ = 'dog'
fierceness = Float()
def bark_all_night(self):
pass
在调用sync_table之后,每个模型定义的列将被添加到pet表中。此外,保存模型的Cat和Dog行所需的元数据将被保存,以便识别它们是猫还是狗。
要使用继承来设置模型结构,请按照以下步骤进行操作。
-
- 識別子(ここではpet_type)として設定された列を持つ基本モデルを作成する(distriminator_column=Trueにより指定)。
-
- サブクラス モデルを(複数)定義し、__discriminator_value__フィールドに(それぞれ)一意の値を定義する
sync_tableを各サブテーブルで実行する
自定义类型
cqlengine可以对用户定义类型(UDT:User Defined Type)进行建模。
UDT可以作为模型(表)的字段被使用,而不是单独使用。
UDT实例通过对应于表的模型进行创建、保存和查询。
from cassandra.cqlengine.columns import *
from cassandra.cqlengine.models import Model
from cassandra.cqlengine.usertype import UserType
class address(UserType):
street = Text()
zipcode = Integer()
class users(Model):
__keyspace__ = 'account'
name = Text(primary_key=True)
addr = UserDefinedType(address)
users.create(name="Joe", addr=address(street="Easy St.", zipcode=99999))
user = users.objects(name="Joe")[0]
print user.name, user.addr
# Joe address(street=u'Easy St.', zipcode=99999)
UDT被定义为继承UserType的类,并通过为其属性字段设置相应数据类型的实例来进行建模。
然后,通过将UDT类声明为UserDefinedType类的参数,可以在模型的(字段/行)定义中使用UDT。
“Sync_table将隐式同步包含在表中的所有UDT。或者,您可以使用sync_type()来显式地创建/修改UDT。”
声明后,类型将自动注册给驱动程序,因此查询结果将返回UserType类的实例。
最终
本次介绍了在使用Python进行Cassandra编程时,对象映射的工作原理。当然,也可以不使用对象映射,而是使用CQL查询来进行编程。在项目中使用Cassandra时,介绍了对象映射作为一种可选的选择。