Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

SQLAlchemy

Глава 20 из книги "Архитектура приложений с открытым исходным кодом", том 2.

Оригинал: SQLAlchemy
Автор: Michael Bayer
Перевод: А. Панин

20.8. Сессия и индивидуальное отображение

В SQLAlchemy объект Session представляет публичный интерфейс для непосредственного использования объектно-реляционного отображения, позволяющий загружать и хранить на постоянной основе данные. Он является отправной точкой для выполнения запросов и операций сохранения данных для заданного соединения с базой данных.

В дополнение к поддержке соединения с базой данных, объект Session поддерживает активный список ссылок для набора, состоящего из всех отображенных элементов, которые присутствуют в памяти и относятся к сессии, представленной данным объектом Session. Таким образом, класс Session является фасадным классом, использующим шаблоны проектирования индивидуального отображения (identity map) и рабочей единицы (unit of work), которые также описаны Martin Flower. Индивидуальное отображение позволяет поддерживать уникальное в рамках базы данных отображение всех объектов определенной представленной объектом Session сессии, исключая проблемы, связанные с наличием дубликатов элементов. Рабочая единица является надстройкой над индивидуальным отображением, реализующей систему автоматизации процесса сохранения на постоянной основе всех изменений состояния базы данных наиболее эффективным способом из возможных. Сам этап сохранения данных известен как "этап записи данных", причем в современных версиях SQLAlchemy он обычно выполняется автоматически.

История разработки

Класс Session начал свое существование как наиболее закрытая система, ответственная за выполнение единственной задачи, заключающейся в сохранении данных. Процесс сохранения данных подразумевает выполнение SQL-запросов с помощью базы данных в соответствии с изменениями состояния объектов, отслеживаемых системой рабочей единицы и, в связи с этим, синхронизации текущего состояния базы данных с содержимым памяти. Сохранение данных всегда было одной из наиболее сложных операций, выполняемых SQLAlchemy.

Выполнение операции сохранения данных (flush) в ранних версиях начинается после вызова метода с именем commit, который присутствовал у явно созданного в рамках потока объекта с именем objectstore. Во время использования нами версии 0.1 SQLAlchemy не было особой надобности в вызове метода Session.add, как, впрочем, и не было какой-либо четкой концепции класса сессии Session вообще. Единственными доступными пользователю операциями были операции создания функций для формирования отображений, операции создания новых объектов, операции модификации существующих объектов, загруженных в ходе выполнения запросов (в этом случае сами запросы выполнялись непосредственно через объект Mapper), после чего все данные должны были сохраниться с помощью команды objectstore.commit. Пул объектов для набора операций был безусловно глобальным в рамках модуля и безусловно локальным в рамках потока.

Модель objectstore.commit была популярна среди группы пользователей ранних версий, но отсутствие гибкости этой модели быстро привело к потере актуальности. Пользователи, начавшие работать с новыми версиями SQLAlchemy, иногда жаловались на необходимость создания фабрики и, возможно, реестра для объектов Session, а также на необходимость сохранения своих объектов, организованных в рамках одного объекта Session в каждый момент времени, но этот подход гораздо предпочтительнее существовавшего ранее, при котором принцип работы всех элементов системы был четко задан. Присущие использованному в версии 0.1 шаблону проектирования пользовательские качества все еще в большей степени присутствуют в современных версиях SQLAlchemy, в которых реализован реестр сессий, обычно настроенный для использования локального пространства потока.

Сам объект Session был представлен только в версии 0.2 SQLAlchemy и был смоделирован в общих чертах по аналогии с объектом Session из состава Hibernate. Эта версия содержала интегрированный механизм контроля транзакций, в рамках которого объект Session мог помещаться в транзакцию с помощью метода begin, а завершение транзакции осуществлялось с помощью метода commit. Метод objectstore.commit был переименован в objectstore.flush и новые объекты Session могли создаваться в любой момент. Сам объект Session был отделен от другого объекта с именем UnitOfWork, который остался приватным объектом, ответственным за выполнение операции сохранения данных.

Хотя процесс сохранения данных и был изначально реализован в рамках явно вызываемого пользователем метода, в версии 0.4 SQLAlchemy была представлена концепция автоматического сохранения изменений (autoflush), которая предполагала, что сохранение данных будет осуществляться непосредственно перед каждым запросом. Преимущество автоматического сохранения данных заключается в том, что SQL-запрос, выполняемый в результате запроса всегда имеет доступ со стороны реляционной базы данных к данным, присутствующим в памяти, так как все изменения были переданы. Ранние версии SQLAlchemy не могли включать эту возможность, так как стандартным шаблоном проектирования оговаривалось, что сохранение данных должно сопровождаться полным сохранением изменений. Но в момент представления концепции автоматического сохранения данных была реализована сопутствующая ей возможность под названием "транзакционная сессия" ("transactional session") в рамках объекта Session, заключающаяся в предоставлении объекта Session, который должен был автоматически начинать транзакцию, продолжавшуюся до того момента, когда пользователь явно вызывал метод commit. После реализации этой возможности метод flush больше не записывал данные для сохранения и мог вызываться автоматически. Сейчас объект Session позволяет осуществлять пошаговую синхронизацию между данными состояния в памяти и данными состояния SQL-запроса путем сохранения данных при необходимости без постоянного сохранения данных до момента явного вызова метода commit. Такое поведение фактически точно повторяет поведение системы Hibernate для языка программирования Java. Однако, этот стиль работы был реализован в SQLAlchemy благодаря использованию в качестве примера системы Storm ORM для языка программирования Python, представленной в момент существования версии 0.3 системы SQLAlchemy.

В версии 0.5 была улучшена работа с транзакциями и представлена схема истечения срока действия транзакции (post-transaction expiration); после каждого использования методов commit и rollback по умолчанию истекал срок действия всех данных состояния в рамках объекта Session (они удалялись) и они должны были снова извлекаться в ходе выполнения последующих SQL-запросов или тогда, когда доступ к атрибутам оставшегося набора объектов с истекшим сроком действия осуществляется из контекста новой транзакции. Изначально система SQLAlchemy была спроектирована в соответствии с предположением о том, что запросы SELECT должны безусловно выполняться так мало раз, как это возможно. Стратегия истечения срока действия данных при их записи внедрялась медленно именно по этой причине; однако, она полностью решала проблему хранения в рамках объекта Session устаревшей копии полученных после транзакции данных, для обновления которой не было предложено простого, не требующего полномасштабного повторного создания набора уже загруженных объектов способа. Сначала казалось, что эта проблема не имеет разумного решения, так как момент, после которого объект Session должен считать текущие данные состояния устаревшими и, следовательно, использовать набор ресурсоемких запросов SELECT при следующей попытке доступа к данным, не был очевиден. Однако, как только объект Session начал постоянно использоваться в рамках транзакций, момент завершения транзакции стал естественным четким моментом истечения срока действия данных, так как природа транзакции с высокой степенью изоляции состоит в том, что она не может получить доступ к новым данным до того, как ее данные будут записаны, либо она будет отменена. Различные базы данных и конфигурации, конечно же, характеризуются различными степенями изоляции транзакций, включая отсутствие транзакций как таковых. Эти режимы работы полностью допустимы при использовании модели истечения срока действия данных SQLAlchemy; разработчик должен заботиться только о том, чтобы низкая степень изоляции не привела к раскрытию неизолированных изменений в рамках сессии в том случае, когда множество сессий использует одни и те же строки. Эта ситуация ни коим образом не отличается от ситуации, которая может произойти при непосредственном использовании двух соединений с базой данных.

Обзор сессии

Рисунок 20.13 иллюстрирует объект Session и основные структуры, взаимодействующие с ним.

Обзор объекта Session
Рисунок 20.13: Обзор объекта Session

Общедоступными объектами на рисунке выше является сам объект Session, а также коллекция пользовательских объектов, каждый из которых является экземпляром класса, используемого для создания отображения. Здесь мы можем увидеть, что используемые для отображения объекты ссылаются на конструкцию из состава SQLAlchemy с именем InstanceState, которая отслеживает состояние отдельного объектно-реляционного отображения, включая ожидающие операции изменения атрибутов, а также факт истечения срока действия атрибутов. Объект InstanceState является инструментарием для работы с атрибутами на уровне экземпляра класса, описанным в предыдущем разделе под названием "Анатомия отображения" и соответствующим объекту ClassManager на уровне класса, который позволяет поддерживать состояние словаря используемого для создания отображения объекта (т.е., атрибута __dict__, описанного в рамках языка программирования Python) на стороне ассоциированных с классом объектов AttributeImpl.

Отслеживание состояния

Объект IdentityMap позволяет создавать отображение индивидуальных данных базы данных для объектов InstanceState, которые в свою очередь используются теми объектами, которым требуются эти индивидуальные данные, называемые также постоянными (persistent). Стандартная реализация объекта IdentityMap взаимодействует с объектом InstanceState для самостоятельного управления объемом занятой памяти путем удаления созданных пользователем отображений в тех случаях, когда удаляются все жесткие ссылки на эти отображения - таким образом, этот объект функционирует аналогично объекту WeakValueDictionary из состава Python. Объект Session защищает набор всех объектов с пометкой "устаревший" ("dirty") или "удаленный" ("deleted"), а также охраняет объекты с пометкой "новый" ("new") от механизма сборки мусора путем создания жестких ссылок на эти объекты в случае ожидания их изменений. Все жесткие ссылки удаляются после выполнения операции сохранения данных.

Объект InstanceState также выполняет критичную задачу, заключающуюся в поддержании "списка изменений" для атрибутов определенного объекта с использованием системы перемещения данных при изменении, которая сохраняет "данные предыдущего состояния" определенного атрибута в словаре с именем commited_state перед использованием переданного значения для изменения значения в словаре атрибутов объекта. Во время выполнения операции сохранения изменений содержимое словаря commited_state, а также ассоциированного с объектом словаря __dict__ сравниваются с целью формирования набора измененных данных для каждого из объектов.

В случае коллекций отдельный пакет с именем collections осуществляет координацию работы системы объектов InstrumentedAttribute/InstanceState для поддержания функционирования коллекции изменений для определенной коллекции объектов, используемых для отображения. Такие стандартные классы языка Python, как set, list и dict перед использованием объявляются подклассами и принимают в качестве аргументов методы, предназначенные для отслеживания истории изменений. Система коллекций была переработана в версии 0.4 с целью расширения ее возможностей в плане использования любых аналогичных коллекциям объектов.

Контроль транзакций

Объект Session при обычном сценарии использования поддерживает открытую транзакцию для выполнения всех операций, которая завершается в момент вызова метода commit или rollback. Объект SessionTransaction поддерживает набор объектов Connection, который может быть как пустым, так и заполненным, причем каждый объект в нем представляет открытую транзакцию для определенной базы данных. Объект SessionTransaction является объектом с отложенной инициализацией, которая начинается при отсутствии данных состояния базы данных. Так как определенная база данных должна участвовать в процессе выполнения запроса, соответствующий этой базе данных объект соединения Connection добавляется в список соединений объекта SessionTransaction. Хотя обычно в каждый момент времени используется одно соединение с базой данных, поддерживается сценарий использования множества соединений, в котором определенное соединение используется для выполнения определенной операции, в соответствии с ассоциированными с объектами Table, Mapper данными конфигурации, либо в соответствии с конструкциями языка SQL, применяемыми в рамках операции. При использовании множества соединений также может координироваться процесс выполнения транзакции при применении двухфазной схемы в тех случаях, когда реализация DBAPI предоставляет ее.


Продолжение статьи: Рабочая единица