Библиотека сайта rus-linux.net
SQLAlchemy
Глава 20 из книги "Архитектура приложений с открытым исходным кодом", том 2.Оригинал: SQLAlchemy
Автор: Michael Bayer
Перевод: А. Панин
20.6. Отображение классов при использовании объектно-реляционного отображения
Переключим наше внимание на объектно-реляционное отображение. Первой целью является использование описанной нами системы таблиц метаданных для предоставления возможности переноса функций заданного пользователем класса на коллекцию столбцов в таблице базы данных. Второй целью является предоставление возможности описания отношений между заданными пользователем классами, которые будут основываться на отношениях между таблицами в базе данных.
В SQLAlchemy такая связь называется "отображением", что соответствует широко известному шаблону проектирования с названием "DataMapper", описанному в книге Martin Flower с названием "Patterns of Enterprise Architecture". В целом, система объектно-реляционного отображения SQLAlchemy была разработана с применением большого количества приемов, которые описал в своей книге Martin Flower. Она также подверглась значительному влиянию со стороны известной системы реляционного отображения Hibernate для языка программирования Java и продукта SQLObject для языка программирования Python от Ian Bicking.
Классическое отображение против декларативного отображения
Table
и заданный пользователем класс для формирования двух связанных с помощью функции с именем mapper
, но при этом отдельных, примитивов. Как только функция mapper
применяется по отношению к заданному пользователем классу, класс приобретает новые атрибуты, соответствующие столбцам таблицы:
class User(object): pass mapper(User, user_table) # теперь у объекта User есть атрибут ".id" User.id
Функция mapper
также может добавлять другие атрибуты классу, включая атрибуты, соответствующие как ссылкам на другие типы объектов, так и на произвольные SQL-запросы. Процесс добавления произвольных атрибутов классу в мире Python известен под названием "monkeypatching"; однако, так как мы выполняем его на основе данных и не привольным образом, суть процесса гораздо лучше может быть обозначена термином "доработка класса" ("class instrumentation").
Table
в большинстве случаев не упоминается ни явно, ни при использовании функции mapper
; явно упоминается только пользовательский класс, объекты Column
и другие относящиеся к отображению атрибуты:
class User(Base): __tablename__ = 'user' id = Column(Integer, primary_key=True)
При рассмотрении приведенного выше фрагмента кода может показаться, что добавление атрибутов в класс осуществляется непосредственно в строке id = Column()
, но на самом деле это не так. Декларативное расширение использует метакласс Python, с помощью которого очень удобно выполнять серии операций каждый раз, когда новый класс впервые декларируется с целью генерации нового объекта Table
на основе декларации и передачи его функции mapper
вместе с классом. После этого функция mapper
выполняет работу точно таким же образом, добавляя набор атрибутов в класс, причем в данном случае используется атрибут id
, а также заменяя атрибуты, которые были добавлены ранее. Ко времени окончания процесса инициализации метакласса (т.е., к моменту, когда поток выполнения покидает блок описания класса User
), объект Column
с атрибутом id
перемещается в новый объект Table
и User.id
заменяется на новый атрибут, специфичный для отображения.
Всегда считалось, что система SQLAlchemy должна иметь четкую декларативную форму конфигурации. Однако, создание декларативного расширения задерживалось из-за продолжающейся работы по стабилизации механизмов классического отображения. Ранее существовало временное расширение под названием ActiveMapper, которое впоследствии было преобразовано в проект Elixir. Оно позволяло повторно объявлять конструкции отражения в рамках декларативной системы более высокого уровня. Задача декларативного расширения была противоположной задаче проекта Elixir и заключалась в создании мощной абстракции путем создания системы, которая практически полностью сохраняет классические концепции SQLAlchemy в области отображения данных, допуская реорганизацию метода использования для сокращения объема отладочной информации и повышения совместимости с расширениями уровня классов по сравнению с возможностями классических систем отображения.
.c
, как показано в этом примере:
result = session.query(User).filter(User.c.username == 'ed').all()
result = session.query(User).filter(User.username == 'ed').all()
Это изменение метода доступа к атрибутам оказалось значительным усовершенствованием, так как оно позволило аналогичным объектам столбцов объектам находиться в классе для достижения дополнительных, специфичных для класса возможностей, которые не доступны для классов, наследуемых напрямую от находящегося уровнем ниже класса Table
. Оно также позволило интегрировать различные типы атрибутов классов, такие, как атрибуты, ссылающиеся непосредственно на столбцы таблицы, атрибуты, ссылающиеся на SQL-запросы, созданные на основе этих столбцов, а также атрибуты, ссылающиеся на соответствующий класс. Наконец, оно позволило достичь соответствия между классом для отображения и экземпляром этого класса для отображения, в котором тот же атрибут может быть предназначен для работы с другой информацией в зависимости от родительского класса. Атрибуты классов возвращают SQL-запросы, в то время, как атрибуты экземпляров классов возвращают реальные данные.
Анатомия отображения
id
, который был привязан к нашему классу User
, является объектом, тип которого известен в рамках языка программирования Python как дескриптор (descriptor), причем этот объект поддерживает методы __get__
, __set__
и __del__
, которые интерпретатор Python позволяет использовать во всех операциях, связанных с этим классом или его экземпляром. Реализация в рамках SQLAlchemy известна под именем InstrumentedAttribute
и мы продемонстрируем механизмы, скрытые за этим фасадным классом, в ходе рассмотрения следующего примера. Начиная работу с описания класса Table
, а также пользовательского класса, мы начинаем формирование отображения только для одного столбца, а также используем функцию relationship
, которая задает ссылку на связанный класс:
user_table = Table("user", metadata, Column('id', Integer, primary_key=True), ) class User(object): pass mapper(User, user_table, properties={ 'related':relationship(Address) })
После создания отображения структура относящихся к классу объектов будет соответствовать представленной на Рисунке 20.11.
Рисунок 20.11: Анатомия отображения
На рисунке проиллюстрировано созданное с помощью SQLAlchemy отображение, использующее два отдельных уровня для осуществления взаимодействия заданного пользователем класса и метаданных таблицы, с которой он связан. Инструментарий, предназначенный для реализации функций класса представлен в левой части рисунка, в то время, как функции для работы с базой данных и SQL-запросами - в правой части. Основной используемый шаблон проектирования подразумевает то, что композиция объектов используется для разделения моделей повеления объектов, а наследование объектов используется для разделения вариаций поведения объектов в рамках определенной роли.
В области инструментария класса следует выделить класс ClassManager
, который связан с используемым для отображения классом, причем каждый объект InstrumentedAttribute
из его коллекции связан с каждым отображаемым атрибутом класса. InstrumentedAttribute
также является упомянутым ранее общедоступным дескриптором языка Python и позволяет формировать SQL-запросы при использовании совместно с запросами на основе классов (т.е., User.id==5
). При разговоре об экземпляре класса User
, следует упомянуть о том, что объект InstrumentedAttribute
делегирует поддержку атрибута объекту AttributeImpl
, который является одним из нескольких аналогичных объектов, выбранным в соответствии с типом представляемых данных.
Со стороны отображения объект Mapper
представляет связь заданного пользователем класса и выбираемого элемента базы данных, чаще всего объекта таблицы Table
. Объект Mapper
поддерживает коллекцию объектов, известных как MapperProperty
и соответствующих каждому из атрибутов, которые отвечают за представление определенного атрибута в SQL-запросе. Наиболее часто встречающимися вариантами объектов MapperProperty
являются объекты ColumnProperty
, представляющие столбцы в SQL-запросе, а также объекты RelationshipProperty
, представляющие связь с другим классом отображения.
Объект MapperProperty
делегирует функции загрузки атрибутов, включая функции преобразования атрибутов в фрагменты SQL-запроса и функции их извлечения из строк результатов запросов, объекту LoaderStrategy
, который также может быть представлен несколькими вариантами. Различные объекты LoaderStrategies
устанавливают методы загрузки атрибута в соответствии с вариантами: deferred (отложенная загрузка), eager (быстрая загрузка) или immediate (немедленная загрузка). Стандартная версия поведения выбирается во время конфигурации отображения, при этом дополнительным вариантом является использование альтернативной стратегии в момент выполнения запроса. Объект RelationshipProperty
также ссылается на объект DependencyProcessor
, который устанавливает метод обработки зависимостей между отображениями и метод синхронизации атрибутов для применения во время сохранения данных. Выбор объекта DependencyProcessor
основывается на параметрах отношения родительских и целевых элементов, связанных друг с другом.
Структура Mapper
/RelationshipProperty
формирует граф, в котором объекты Mapper
являются узлами, а объекты RelationshipProperty
- ориентированными ребрами. После того, как полный набор функций отображения декларируется приложением, наступает этап отложенной "инициализации", известный как процесс конфигурации (configuration). Данные, собираемые в ходе выполнения этого процесса, главным образом используются каждым объектом RelationshipProperty
для уточнения параметров, используемых функциями отображения родительских (parent) и целевых (target) объектов, причем также производится выбор объекта AttributeImpl
наряду с объектом DependencyProcessor
. Этот граф является ключевой структурой данных, используемой при выполнении операции объектно-реляционного отображения. Он участвует в операциях, использующих так называемые "каскады", которые устанавливают последовательность выполнения функций на основе последовательностей объектов в операциях запросов, в которых связанные объекты и коллекции объектов "быстро" одновременно загружаются, а также в процессах сохранения данных объектов, когда граф зависимостей для всех объектов создается до того, как будут завершены этапы освобождения выделенных для хранения данных ресурсов.
Продолжение статьи: Методы выполнения запросов и загрузки данных