Как сохранить несколько связанных экземпляров Django ORM за один раз без отдельных обращений к базе данных по сети?
Вот пример:
article1 = Article(title="Global Warming", content="...")
article2 = Article(title="Visiting Mars", content="...")
comment1 = Comment(content="Some comment", article=article1)
user1 = User(username="user1")
some_updated_article.title = "updated title"
article_to_delete.delete()
Я знаю, что в SQLAlchemy вы можете сохранить несколько экземпляров в базе данных за один вызов следующим образом:
db.session.add_all([article1, article2, comment1, user1])
db.session.commit()
Этот подход отправляет все инструкции в базу данных за один раз (пожалуйста, поправьте меня, если я ошибаюсь). db.session.add_all()
, за которым следует db.session.commit()
, будет работать, и не будет трех отдельных обращений к серверу базы данных.
Я знаю, что в Django я могу использовать bulk_create
, bulk_update
, для каждой модели:
Article.objects.bulk_create([article1, article2])
Comment.objects.bulk_create([comment1])
User.objects.bulk_create([user1])
Article.objects.bulk_update([some_updated_article], fields=["title"])
Но это отправляет отдельные вызовы на сервер базы данных для каждой модели. Есть ли способ добиться чего-то подобного add_all()
от SQLAlchemy, где я могу отправлять все объекты за один раз, независимо от модели?
Я подумывал о том, чтобы использовать transaction.atomic для этого:
with transaction.atomic():
Article.objects.bulk_create([article1, article2])
Comment.objects.bulk_create([comment1])
User.objects.bulk_create([user1])
Article.objects.bulk_update([some_updated_article], fields=["title"])
Использование transaction.atomic()
гарантирует, что все операции будут выполнены успешно или завершатся неудачей как одна атомарная транзакция. Однако в моем случае использования я не хочу, чтобы выполнялся полный откат. Например, если при создании комментариев возникает ошибка, я все равно хочу успешно сохранить статьи и пользователей. Я знаю, что Django предоставляет точек сохранения с помощью transaction.savepoint()
и transaction.savepoint_rollback()
, но это кажется немного громоздким.
Есть ли лучший способ добиться этого в Django? В идеале я хотел бы:
Избегайте отката всей транзакции в случае сбоя одной операции.
Получать уведомления о том, какой шаг завершился неудачей, сохраняя при этом успешные данные.
Сделайте все за один вызов базы данных (так что, если, например, мой внутренний сервер находится в Австралии, а моя база данных - в Северной Америке, не будет 4 отдельных сетевых запросов)
Каков наилучший подход для обработки таких сценариев? Возможно, есть какая-то причина, по которой вы не можете сделать это легко, о которой я не знаю, и лучше использовать поведение по умолчанию?
Обычные табличные выражения на самом деле не поддерживаются ORM изначально, так что, возможно, вы столкнулись с ситуацией cursor
, в которой требуется выполнить простой старый sql (https://docs.djangoproject.com/en/5.1/topics/db/sql/#executing-custom-sql-directly).
Не уверен, используете ли вы postgres или другую реляционную базу данных, но CTE, вероятно, должны быть одинаковыми между ними https://www.postgresql.org/docs/current/queries-with.html#QUERIES-WITH-MODIFYING.
Возможно, в итоге у вас получится что-то вроде:
with connection.cursor() as cursor:
cursor.execute(
"""
WITH insertedArticles AS (
INSERT INTO articles
(name, subtitle)
VALUES
('name_value', 'subtitle_value')
RETURNING article_id
)
INSERT INTO comments
(article_id, comment_text)
SELECT
insertedArticles.article_id, $1
FROM insertedArticles;
"""
Как всегда при использовании необработанных методов sql, а не ORM, убедитесь в параметризации входных данных.