Быстрый старт ORM¶
Для новых пользователей, которые хотят быстро увидеть, как выглядит базовое использование ORM, здесь представлена сокращенная форма отображений и примеров, используемых в Самоучитель SQLAlchemy 1.4 / 2.0. Приведенный здесь код полностью запускается из чистой командной строки.
Поскольку описания в этом разделе намеренно очень краткие, пожалуйста, перейдите к полному Самоучитель SQLAlchemy 1.4 / 2.0 для более подробного описания каждой из иллюстрируемых здесь концепций.
Объявить модели¶
Здесь мы определяем конструкции на уровне модуля, которые формируют структуры, которые мы будем запрашивать из базы данных. Эта структура, известная как Declarative Mapping, определяет одновременно и объектную модель Python, и database metadata, которая описывает реальные таблицы SQL, которые существуют или будут существовать в конкретной базе данных:
>>> from sqlalchemy import Column
>>> from sqlalchemy import ForeignKey
>>> from sqlalchemy import Integer
>>> from sqlalchemy import String
>>> from sqlalchemy.orm import declarative_base
>>> from sqlalchemy.orm import relationship
>>> Base = declarative_base()
>>> class User(Base):
... __tablename__ = "user_account"
...
... id = Column(Integer, primary_key=True)
... name = Column(String(30))
... fullname = Column(String)
...
... addresses = relationship(
... "Address", back_populates="user", cascade="all, delete-orphan"
... )
...
... def __repr__(self):
... return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"
>>> class Address(Base):
... __tablename__ = "address"
...
... id = Column(Integer, primary_key=True)
... email_address = Column(String, nullable=False)
... user_id = Column(Integer, ForeignKey("user_account.id"), nullable=False)
...
... user = relationship("User", back_populates="addresses")
...
... def __repr__(self):
... return f"Address(id={self.id!r}, email_address={self.email_address!r})"
Отображение начинается с базового класса, который выше называется Base
, и создается путем вызова функции declarative_base()
, которая производит новый базовый класс.
Затем создаются отдельные сопоставленные классы путем создания подклассов Base
. Сопоставленный класс обычно ссылается на одну конкретную таблицу базы данных, имя которой указывается с помощью атрибута уровня класса __tablename__
.
Далее объявляются столбцы, которые являются частью таблицы, путем добавления атрибутов, связанных с конструкцией Column
. Column
описывает все аспекты столбца базы данных, включая информацию о типе с помощью таких объектов типа, как Integer
и String
, а также значения по умолчанию сервера и информацию об ограничениях, например, принадлежность к первичному ключу и внешним ключам.
Все сопоставленные классы ORM требуют объявления хотя бы одного столбца как части первичного ключа, обычно с помощью параметра Column.primary_key
на тех объектах Column
, которые должны быть частью ключа. В приведенном выше примере столбцы User.id
и Address.id
обозначены как первичный ключ.
Вместе взятые, комбинация строкового имени таблицы и списка объявлений столбцов называется в SQLAlchemy table metadata. Настройка метаданных таблицы с использованием как Core, так и ORM подходов представлена в Самоучитель SQLAlchemy 1.4 / 2.0 в Работа с метаданными базы данных. Приведенное выше отображение является примером того, что называется конфигурацией Declarative Table.
Существуют и другие декларативные директивы, чаще всего это конструкция relationship()
, указанная выше. В отличие от атрибутов, основанных на столбцах, relationship()
обозначает связь между двумя классами ORM. В приведенном выше примере User.addresses
связывает User
с Address
, а Address.user
связывает Address
с User
. Конструкция relationship()
вводится в Самоучитель SQLAlchemy 1.4 / 2.0 в Работа со связанными объектами.
Наконец, приведенные выше примеры классов включают метод __repr__()
, который не является обязательным, но полезен для отладки.
Создать двигатель¶
Engine
- это фабрика, которая может создавать для нас новые соединения с базой данных, а также удерживать соединения внутри Connection Pool для быстрого повторного использования. Для целей обучения мы обычно используем базу данных SQLite только с памятью для удобства:
>>> from sqlalchemy import create_engine
>>> engine = create_engine("sqlite://", echo=True, future=True)
Совет
Параметр echo=True
указывает на то, что SQL, испускаемый соединениями, будет записываться в стандартный журнал. future=True
это необходимо для того, чтобы убедиться, что мы используем последние версии API SQLAlchemy 2.0-style.
Полное введение в Engine
начинается с Установление связи - двигатель.
Emit CREATE TABLE DDL¶
Используя метаданные нашей таблицы и наш движок, мы можем сгенерировать нашу схему сразу в нашей целевой базе данных SQLite, используя метод под названием MetaData.create_all()
:
>>> Base.metadata.create_all(engine)
BEGIN (implicit)
PRAGMA main.table_...info("user_account")
...
PRAGMA main.table_...info("address")
...
CREATE TABLE user_account (
id INTEGER NOT NULL,
name VARCHAR(30),
fullname VARCHAR,
PRIMARY KEY (id)
)
...
CREATE TABLE address (
id INTEGER NOT NULL,
email_address VARCHAR NOT NULL,
user_id INTEGER NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(user_id) REFERENCES user_account (id)
)
...
COMMIT
Из того кусочка кода на Python, который мы написали, произошло очень многое. Для полного обзора того, что происходит с метаданными таблицы, перейдите в учебник по ссылке Работа с метаданными базы данных.
Создание объектов и постоянство¶
Теперь мы готовы к вставке данных в базу данных. Для этого мы создаем экземпляры классов User
и Address
, у которых уже есть метод __init__()
, автоматически созданный в процессе декларативного отображения. Затем мы передаем их в базу данных с помощью объекта Session, который использует метод Engine
для взаимодействия с базой данных. Метод Session.add_all()
используется здесь для добавления нескольких объектов одновременно, а метод Session.commit()
будет использоваться для flush любых ожидающих изменений в базе данных и затем commit текущей транзакции базы данных, которая всегда находится в процессе, когда используется Session
:
>>> from sqlalchemy.orm import Session
>>> with Session(engine) as session:
...
... spongebob = User(
... name="spongebob",
... fullname="Spongebob Squarepants",
... addresses=[Address(email_address="spongebob@sqlalchemy.org")],
... )
... sandy = User(
... name="sandy",
... fullname="Sandy Cheeks",
... addresses=[
... Address(email_address="sandy@sqlalchemy.org"),
... Address(email_address="sandy@squirrelpower.org"),
... ],
... )
... patrick = User(name="patrick", fullname="Patrick Star")
...
... session.add_all([spongebob, sandy, patrick])
...
... session.commit()
BEGIN (implicit)
INSERT INTO user_account (name, fullname) VALUES (?, ?)
[...] ('spongebob', 'Spongebob Squarepants')
INSERT INTO user_account (name, fullname) VALUES (?, ?)
[...] ('sandy', 'Sandy Cheeks')
INSERT INTO user_account (name, fullname) VALUES (?, ?)
[...] ('patrick', 'Patrick Star')
INSERT INTO address (email_address, user_id) VALUES (?, ?)
[...] ('spongebob@sqlalchemy.org', 1)
INSERT INTO address (email_address, user_id) VALUES (?, ?)
[...] ('sandy@sqlalchemy.org', 2)
INSERT INTO address (email_address, user_id) VALUES (?, ?)
[...] ('sandy@squirrelpower.org', 2)
COMMIT
Совет
Рекомендуется использовать Session
в стиле контекстного менеджера, как описано выше, то есть с помощью оператора Python with:
. Объект Session
представляет активные ресурсы базы данных, поэтому полезно убедиться, что он закрывается после завершения серии операций. В следующем разделе мы будем держать объект Session
открытым просто для наглядности.
Основы создания Session
приведены в Выполнение с помощью сессии ORM, а более подробная информация - в Основы использования сеанса.
Затем в Вставка строк с помощью ORM представлены некоторые разновидности базовых операций персистентности.
Простой ВЫБОР¶
Имея несколько строк в базе данных, вот простейшая форма создания оператора SELECT для загрузки некоторых объектов. Для создания операторов SELECT мы используем функцию select()
для создания нового объекта Select
, который затем вызываем с помощью Session
. Метод, который часто бывает полезен при запросе объектов ORM, - это метод Session.scalars()
, который возвращает объект ScalarResult
, перебирающий выбранные нами объекты ORM:
>>> from sqlalchemy import select
>>> session = Session(engine)
>>> stmt = select(User).where(User.name.in_(["spongebob", "sandy"]))
>>> for user in session.scalars(stmt):
... print(user)
BEGIN (implicit)
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name IN (?, ?)
[...] ('spongebob', 'sandy')
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
В приведенном выше запросе также использовался метод Select.where()
для добавления критериев WHERE, а также метод ColumnOperators.in_()
, который является частью всех конструкций SQLAlchemy, подобных столбцам, для использования оператора SQL IN.
Более подробно о том, как выбирать объекты и отдельные столбцы, рассказано в Выбор сущностей и столбцов ORM.
SELECT с JOIN¶
Очень часто приходится делать запросы сразу к нескольким таблицам, и в SQL ключевое слово JOIN является основным способом, с помощью которого это происходит. Конструкция Select
создает соединения, используя метод Select.join()
:
>>> stmt = (
... select(Address)
... .join(Address.user)
... .where(User.name == "sandy")
... .where(Address.email_address == "sandy@sqlalchemy.org")
... )
>>> sandy_address = session.scalars(stmt).one()
SELECT address.id, address.email_address, address.user_id
FROM address JOIN user_account ON user_account.id = address.user_id
WHERE user_account.name = ? AND address.email_address = ?
[...] ('sandy', 'sandy@sqlalchemy.org')
>>> sandy_address
Address(id=2, email_address='sandy@sqlalchemy.org')
Приведенный выше запрос иллюстрирует несколько критериев WHERE, которые автоматически связываются вместе с помощью AND, а также то, как использовать столбцеподобные объекты SQLAlchemy для создания сравнений «равенства», для чего используется переопределенный метод Python ColumnOperators.__eq__()
для создания объекта SQL-критерия.
Некоторые дополнительные сведения о вышеупомянутых концепциях приведены в Предложение ГДЕ и Явные предложения FROM и JOIN.
Внести изменения¶
Объект Session
в сочетании с нашими ORM-сопоставленными классами User
и Address
автоматически отслеживает изменения в объектах по мере их внесения, что приводит к SQL-запросам, которые будут выданы при следующей очистке Session
. Ниже мы изменим один адрес электронной почты, связанный с «sandy», а также добавим новый адрес электронной почты к «patrick», после того как выполним SELECT для получения строки для «patrick»:
>>> stmt = select(User).where(User.name == "patrick")
>>> patrick = session.scalars(stmt).one()
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = ?
[...] ('patrick',)
>>> patrick.addresses.append(Address(email_address="patrickstar@sqlalchemy.org"))
SELECT address.id AS address_id, address.email_address AS address_email_address, address.user_id AS address_user_id
FROM address
WHERE ? = address.user_id
[...] (3,)
>>> sandy_address.email_address = "sandy_cheeks@sqlalchemy.org"
>>> session.commit()
UPDATE address SET email_address=? WHERE address.id = ?
[...] ('sandy_cheeks@sqlalchemy.org', 2)
INSERT INTO address (email_address, user_id) VALUES (?, ?)
[...] ('patrickstar@sqlalchemy.org', 3)
COMMIT
Обратите внимание, когда мы обратились к patrick.addresses
, был выдан SELECT. Это называется lazy load. В Стратегии работы погрузчика приведена информация о различных способах доступа к связанным элементам с использованием более или менее SQL.
Подробное описание манипулирования данными в ORM начинается с Манипулирование данными с помощью ORM.
Некоторые удаления¶
Все должно заканчиваться, как и некоторые строки нашей базы данных - вот краткая демонстрация двух различных форм удаления, обе из которых важны в зависимости от конкретного случая использования.
Сначала мы удалим один из объектов Address
у пользователя «sandy». Когда Session
в следующий раз будет промыт, это приведет к удалению строки. Такое поведение мы настроили в нашей связке под названием delete cascade. Мы можем получить обращение к объекту sandy
по первичному ключу с помощью Session.get()
, а затем работать с объектом:
>>> sandy = session.get(User, 2)
BEGIN (implicit)
SELECT user_account.id AS user_account_id, user_account.name AS user_account_name, user_account.fullname AS user_account_fullname
FROM user_account
WHERE user_account.id = ?
[...] (2,)
>>> sandy.addresses.remove(sandy_address)
SELECT address.id AS address_id, address.email_address AS address_email_address, address.user_id AS address_user_id
FROM address
WHERE ? = address.user_id
[...] (2,)
Последний SELECT выше был операцией lazy load, выполняемой для того, чтобы коллекция sandy.addresses
могла быть загружена, чтобы мы могли удалить член sandy_address
. Есть и другие способы выполнить эту серию операций, которые не будут содержать так много SQL.
Мы можем выдать SQL DELETE для того, что было изменено на данный момент, без фиксации транзакции, используя метод Session.flush()
:
>>> session.flush()
DELETE FROM address WHERE address.id = ?
[...] (2,)
Далее мы полностью удалим пользователя «patrick». Для удаления верхнего уровня объекта самого по себе мы используем метод Session.delete()
; этот метод фактически не выполняет удаление, но устанавливает объект для удаления при следующем удалении. Операция также будет cascade на связанные объекты на основе настроенных нами опций каскада, в данном случае на связанные объекты Address
:
>>> session.delete(patrick)
SELECT user_account.id AS user_account_id, user_account.name AS user_account_name, user_account.fullname AS user_account_fullname
FROM user_account
WHERE user_account.id = ?
[...] (3,)
SELECT address.id AS address_id, address.email_address AS address_email_address, address.user_id AS address_user_id
FROM address
WHERE ? = address.user_id
[...] (3,)
Метод Session.delete()
в данном конкретном случае выдал два оператора SELECT, хотя не выдал DELETE, что может показаться удивительным. Это потому, что когда метод перешел к осмотру объекта, оказалось, что объект patrick
был expired, что произошло, когда мы в последний раз вызывали Session.commit()
, и SQL, который был испущен, был для повторной загрузки строк из новой транзакции. Это истечение является необязательным, и при обычном использовании мы часто будем отключать его в ситуациях, где оно не очень применимо.
Чтобы проиллюстрировать удаляемые строки, вот фиксация:
>>> session.commit()
DELETE FROM address WHERE address.id = ?
[...] (4,)
DELETE FROM user_account WHERE user_account.id = ?
[...] (3,)
COMMIT
В учебнике удаление ORM рассматривается в Удаление объектов ORM. Общие сведения об истечении срока действия объектов приведены в Истечение срока действия / Обновление; каскады подробно обсуждаются в Каскады.
Глубоко изучите вышеупомянутые концепции¶
Для нового пользователя вышеприведенные разделы, скорее всего, были вихревым туром. В каждом из вышеперечисленных шагов есть много важных понятий, которые не были освещены. После краткого обзора того, как все выглядит, рекомендуется проработать Самоучитель SQLAlchemy 1.4 / 2.0, чтобы получить твердые рабочие знания о том, что на самом деле происходит выше. Удачи!