Когда мы уже настроили объект машины соединений, мы готовы идти дальше. Однако нам может потребоваться углубиться в основы его операций, и в том числе соединения (Connection) и результата обработки запроса (Result). Нам также необходимо понять как устроена фасада (паттерн объединяющего класса) в ORM, соединяющая эти объекты (соединение и результат) и известная как сессия.
<aside> 🌐 При использовании ORM, машина соединений полностью контролируется более высокой абстракцией - сессией. В современной алхимии сессия является менеджером транзакции и выполнения SQL скриптов, подобно тому, как это происходит с соединением (Connection) в Core. Поэтому хотя эта глава предназначена больше для Core, все ее концепции применимы и для ORM. Разница между соединением (Connection) и сессией (Session) будет показана в конце этой главы.
</aside>
Чтобы познакомиться с основным инструментом алхимии - языком выражений SQL, мы начнем его использование с наиболее простой его конструкции, называемой text(), и которая позволяет нам передавать выражения SQL напрямую в виде текста. Остается сказать, что такая практика на сегодняшний день в алхимии используются скорее как исключение, нежели чем правило, хотя эта возможность и остается доступной.
Единственная цель машины соединений это предоставлять множество соединений к базе данных. При работе с Core напрямую, объект соединения (Connection) представляет полностью всё взаимодействие с ней. При этом нам бы хотелось всегда ограничивать область использования этого объекта до конкретного контекста и лучший способ это сделать - использовать контекстные менеджеры Python, контролируемые ключевым словом with. Ниже мы покажем некий Hello World скрипт с использованием текстового SQL:
from sqlalchemy import text
with engine.connect() as conn:
result = conn.execute(text("select 'hello world'"))
print(result.all())
BEGIN (implicit)
select 'hello world'
[...] ()
[('hello world',)]
ROLLBACK
Как мы видим в приведенных примерах, контекстный менеджер предоставляет объект соединения, а также обрамляет все операции в рамки одной транзакции. Стандартное поведение Python DBAPI подразумевает, что транзакция всегда активна, когда же контекст с соединением заканчивается отправляется команда ROLLBACK, регистрирующая конец транзакции. Обратите внимание, что по умолчанию транзакции не завершаются автоматически, если вы хотите закрыть ее и выполнить соответствующий SQL скрипт, то вам необходимо вызвать Connection.commit(), либо использовать autocommit, который будет обсужден позже.
Результат выполнения SELECT был также возвращен в качестве объекта, называемого Result (будет также раскрыт подробнее позднее), однако имейте ввиду, что не стоит вытягивать этот объект за пределы контекста соединения.
Вы только что узнали, что DBAPI соединения не сохраняют изменения автоматически. Но что, если мы хотим передать определенную информацию и сохранить ее? Мы можем расширить предыдущий пример и добавить в него создание таблицы и вставки определенных данных:
with engine.connect() as conn:
conn.execute(text("CREATE TABLE some_table (x int, y int)"))
conn.execute(
text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
[{"x": 1, "y": 1}, {"x": 2, "y": 4}],
)
conn.commit()
BEGIN (implicit)
CREATE TABLE some_table (x int, y int)
[...] ()
<sqlalchemy.engine.cursor.CursorResult object at 0x...>
INSERT INTO some_table (x, y) VALUES (?, ?)
[...] ((1, 1), (2, 4))
<sqlalchemy.engine.cursor.CursorResult object at 0x...>
COMMIT
Итак, мы выполнили две SQL команды, которые вообще говоря транзакциональны: “CREATE TABLE” и “INSERT”, которые также были параметризованы с помощью соответствующего синтаксиса (о параметризации подробнее поговорим позже). Так как мы хотим, чтобы результат нашей работы не потерялся, то мы должны сохранить (за-commit-тить) наши результаты. В данном примере мы вызываем соответствующий метод .commit(), который соответственно закрывает транзакцию, но в отличие от ROLLBACK, не теряет данные, а записывает их в БД. Иногда такой стиль работы с транзакциями называется “сохранение на ходу”.
Однако существует и другой стиль, который как мы увидим будет сильнее связан с контекстом в Python. Для этого стиля сохранения мы будем использовать Engine.begin(), который не только будет открывать соединение, подобно Engine.connect(), но и сохранять изменения по выходу из контекста:
with engine.begin() as conn:
conn.execute(
text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
[{"x": 6, "y": 8}, {"x": 9, "y": 10}],
)
BEGIN (implicit)
INSERT INTO some_table (x, y) VALUES (?, ?)
[...] ((6, 8), (9, 10))
<sqlalchemy.engine.cursor.CursorResult object at 0x...>
COMMIT
Как мы видим в данном случае мы не вызываем .commit() самостоятельно, но тем не менее по закрытии блока with это происходит автоматически.
Данный способ сохранения удобен, но в ходе этого туториала мы будем использовать “сохранение на ходу”, так как оно более подходит для демонстрационных целей.
<aside> ❓ Что значит BEGIN (implicit)? Вы могли заметить в логах линию с такой фразой в начале каждого SQL скрипта. “implicit” (с англ. подразумеваемый) в данном случае означает, что SQLAlchemy не будет отправлять никаких команд в начале транзакции, а будет это делать только тогда, когда это действительно потребуется.
</aside>