Одна из проблем, с которой сталкиваются разработчики GAE, привыкшие работать с реляционными СУБД и ORM, — это ссылки и отношения в App Engine. В этом руководстве рассматриваются два вопроса: во-первых, что такое отношения в СУБД?; во-вторых, как их использовать в GAE?
Типы отношений
СУБД работают с несколькими типами отношений: «один-к-одному», «один-ко-многим» и «многие-ко-многим».Несмотря на различия в терминологии, отношения работают по тем же принципам, что и ссылки.
Ссылка — это поле сущности, содержащее ключ другой сущности — например, если «питомец» относится к «владельцу», то это означает, что у сущности «питомец» есть поле, содержащее ключ сущности «владелец».
Все типы отношений можно представить в виде связей.
В своей простейшей форме связь «один ко многим» — это ссылка: у каждого питомца свой владелец, поэтому у владельца может быть несколько питомцев, ссылающихся на него.
При этом сам «хозяин» не меняется – отдельные «питомцы» полагаются на него, называя своим хозяином.
Отношения «один-к-одному» — это отношения «один-ко-многим» с дополнительным ограничением, согласно которому на «владельца» ссылается только одно «домашнее животное».
Это ограничение можно усилить, сохраняя перекрестные ссылки (поля, которые ссылаются друг на друга в каждой сущности).
Отношения «многие ко многим» немного сложнее.
Их можно реализовать несколькими способами, но все они сводятся к списку пар ссылок.
В качестве примера возьмем веб-страницу.
Каждая страница имеет множество входящих и исходящих ссылок.
Их можно представить в виде списка пар вида (from_url, to_url).
В реляционных СУБД такие совпадения хранятся в отдельных таблицах, которые объединяются в запросы для поиска связанных записей.
Теперь давайте посмотрим, как вышеуказанные типы отношений работают в App Engine. В общем, часто бывает полезно избавиться от терминологии «один-ко-многим» и т. д. и рассматривать сущности с объектно-ориентированной точки зрения.
Давайте поставим вопрос по-другому: как одна сущность должна ссылаться на другую, чтобы она соответствовала вашей структуре данных?
Отношения в App Engine
Один ко многим
Этот тип отношений легко реализуется в любой системе.Платформа App Engine гарантирует, что ключ «одной» стороны хранится в объекте «многих» сторон.
В Python для этого используется поле ReferenceProperty:
Чтобы найти «владельца» «питомца», мы обращаемся к атрибуту pet.owner, и App Engine автоматически загружает объект, на который мы ссылаемся.class Owner(db.Model): name = db.StringProperty() class Pet(db.Model): name = db.StringProperty() owner = db.ReferenceProperty(Owner)
Чтобы найти всех «домашних животных», которые относятся к конкретному «владельцу», просто выполните следующий запрос: pets = Pet.all().
filter('owner =', owner).
fetch(100)
Аналогичного результата можно добиться и проще: ReferenceProperty автоматически создает свойство в классе Owner для быстрого и легкого доступа к связанным данным, поэтому вы можете получить список «домашних животных» следующим образом: pets = Owner.owner_set.fetch(100)
По умолчанию App Engine называет это свойство как имя поля + «_set», но вы можете задать свое собственное: class Pet(db.Model):
name = db.StringProperty()
owner = db.ReferenceProperty(Owner, collection_name='pets')
pets = owner.pets.fetch(100)
Другой способ смоделировать связь «один-ко-многим» — привязать сущность к родительскому элементу.
Когда сущность создается, ей можно назначить родителя.
В этом случае ключ родительской сущности становится частью дочернего ключа и не может быть изменен в будущем.
Вот как это выглядит в нашем примере: class Owner(db.Model):
name = db.StringProperty()
class Pet(db.Model):
name = db.StringProperty()
bob = Owner(name='Bob')
felix = Pet(name='Felix', parent=bob)
owner_of_felix = felix.parent
Далее мы нигде явно не указываем связь между сущностями — она следует из указания родителя в момент создания.
Когда лучше использовать родительскую привязку вместо ссылочного свойства? Это влияет на работу транзакций: в App Engine в каждой отдельной транзакции можно оперировать сущностями только одной группы, т.е.
набором сущностей с родителем из той же группы.
Если вы хотите, чтобы транзакция исключала связанные сущности, используйте поле ссылки.
Также помните, что у сущности может быть только один непосредственный родитель, и ее ключ нельзя изменить после создания.
Один к одному
Отношения «один-к-одному» являются частным случаем отношений «один-ко-многим».Они осуществляются путем хранения на «одной» стороне поля, которое является ссылкой на другую сущность.
Многие ко многим
Схема «многие ко многим» наиболее сложна в реализации.Существует несколько решений для их создания для App Engine. Наиболее очевидным подходом является таблица отношений, подобная реляционной базе данных, которая содержит пары ключей для обеих сторон отношений.
В нашем примере с «домашними животными/владельцами» это будет выглядеть так: class Owner(db.Model):
name = db.StringProperty()
class Pet(db.Model):
name = db.StringProperty()
class PetOwner(db.Model):
pet = db.ReferenceProperty(Pet, collection_name='owners')
owner = db.ReferenceProperty(Owner, collection_name='pets')
Преимущество этого метода в том, что к связи можно добавить дополнительные свойства — например, при моделировании связей ссылок страниц в связь можно добавить поле с текстом ссылки.
Доступ к данным осуществляется поэтапно: находятся связанные пары, из которых затем извлекаются нужные сущности.
В примере используется шаблон пакетного извлечения сущностей из ссылок, описанный в этом разделе.
статья * : petowners = felix.owners.fetch(100)
prefetch_refprops(owners, 'owner')
owners = [x.owner for x in petowners]
Получение сущностей в обратном направлении (от «хозяина» к «питомцам») осуществляется аналогично.
Другой подход заключается в том, чтобы одна сторона отношения хранила список ключей сущностей на другой стороне.
Это полезно, когда количество сохраняемых элементов ограничено (скажем, несколько сотен или меньше).
С таким списком удобно выполнять пакетные операции.
Например: class Pet(db.Model):
name = db.StringProperty()
class Owner(db.Model):
name = db.StringProperty()
pets = db.ListProperty(db.Key)
От каждого «хозяина» можно извлечь список его «питомцев»: pets = db.get(bob.pets)
А чтобы найти всех «владельцев» данного «питомца», выполните следующий запрос: owners = Owner.all().
filter('pets =', felix).
fetch(100)
Наконец, гибридный подход может оказаться наиболее продуктивным и гибким.
Советую посмотреть замечательный репортаж Бретта Слаткинса на эту тему.
Разработка сложных масштабируемых приложений на App Engine .
* — Имеется в виду разработанный автором статьи паттерн для извлечения сущностей из ссылок без выполнения лишних запросов к хранилищу.
Короче говоря, ссылочное поле не загружает объект сразу, и при доступе к ссылочному атрибуту или методу будет выдан запрос.
Чтобы минимизировать количество запросов, паттерн загружает сущности по ссылкам за раз (примечание переводчика).
Теги: #gae #ссылка #ссылки #отношения #Google App Engine
-
Цифровые Вывески – Новый Путь К Медиа!
19 Oct, 24 -
Как Восстановить Забытый Пароль Windows?
19 Oct, 24 -
Графический Дизайн Как Все О Творчестве
19 Oct, 24 -
Сумасшедший Colormatrixfilter.
19 Oct, 24 -
30 Лет Звездным Войнам!
19 Oct, 24