135 votes

SQLAlchemy : Création et réutilisation d'une session

Juste une petite question : SQLAlchemy parle de en appelant sessionmaker() une fois, mais en appelant le résultat Session() chaque fois que vous avez besoin de parler à votre DB. Pour moi, cela signifie que la seconde où je fais mon premier session.add(x) ou quelque chose de similaire, je ferais d'abord

from project import Session
session = Session()

Ce que j'ai fait jusqu'à présent, c'est de passer l'appel session = Session() dans mon modèle une fois et ensuite importer toujours la même session n'importe où dans mon application. Puisqu'il s'agit d'une application web, cela permettrait de généralement signifient la même chose (car une vue est exécutée).

Mais où est la différence ? Quel est l'inconvénient d'utiliser une seule session tout le temps par rapport à l'utilisation de celle-ci pour mes affaires de base de données jusqu'à ce que ma fonction soit terminée, puis d'en créer une nouvelle la prochaine fois que je veux parler à ma base de données ?

Je comprends que si j'utilise plusieurs fils, chacun devrait avoir sa propre session. Mais en utilisant scoped_session() je m'assure déjà que ce problème n'existe pas, n'est-ce pas ?

Veuillez préciser si l'une de mes hypothèses est erronée.

305voto

zzzeek Points 22617

sessionmaker() est une usine, elle est là pour encourager la mise en place d'options de configuration pour créer de nouvelles Session des objets à un seul endroit. Il est facultatif, dans la mesure où vous pourriez tout aussi bien appeler Session(bind=engine, expire_on_commit=False) chaque fois que vous avez besoin d'un nouveau Session sauf que c'est verbeux et redondant, et je voulais arrêter la prolifération de petites "aides" qui abordaient chacune le problème de cette redondance d'une manière nouvelle et plus confuse.

Alors sessionmaker() est juste un outil pour vous aider à créer Session des objets quand vous en avez besoin.

Partie suivante. Je pense que la question est, quelle est la différence entre faire une nouvelle Session() à différents moments, plutôt que d'en utiliser une seule tout au long du processus. La réponse est : pas beaucoup. Session est un conteneur pour tous les objets que vous y mettez, et il garde aussi la trace d'une transaction ouverte. Au moment où vous appelez rollback() o commit() la transaction est terminée, et le Session n'a aucune connexion avec la base de données jusqu'à ce qu'il soit appelé à émettre à nouveau du SQL. Les liens qu'il entretient avec vos objets mappés sont des références faibles, à condition que les objets soient exempts de modifications en attente. Session se videra pour revenir à un tout nouvel état lorsque votre application perdra toutes les références aux objets mappés. Si vous le laissez avec sa valeur par défaut "expire_on_commit" alors tous les objets sont expirés après un commit. Si cette Session qui traîne pendant cinq ou vingt minutes, et que toutes sortes de choses ont changé dans la base de données, la prochaine fois que vous l'utiliserez, elle chargera un tout nouvel état la prochaine fois que vous accéderez à ces objets, même s'ils sont restés en mémoire pendant vingt minutes.

Dans les applications web, on a l'habitude de dire, hey pourquoi ne pas faire un tout nouveau Session à chaque demande, plutôt que d'utiliser le même à chaque fois. Cette pratique garantit que la nouvelle requête commence "proprement". Si certains objets de la requête précédente n'ont pas encore été collectés, et si vous avez peut-être désactivé la fonction "expire_on_commit" Si vous ne faites pas attention, il se peut qu'un état de la requête précédente soit toujours présent, et cet état peut même être assez ancien. Si vous faites attention à laisser expire_on_commit allumé et pour appeler définitivement commit() o rollback() à la fin de la demande, alors c'est bien, mais si vous commencez avec une toute nouvelle Session alors il n'y a aucun doute sur le fait que vous partez de zéro. Donc l'idée de commencer chaque demande avec une nouvelle Session est vraiment le moyen le plus simple de s'assurer que vous repartez à zéro, et de faire l'utilisation de expire_on_commit plutôt facultatif, car ce drapeau peut entraîner beaucoup de SQL supplémentaire pour une opération qui fait appel à commit() au milieu d'une série d'opérations. Je ne sais pas si cela répond à votre question.

La prochaine étape est ce que vous mentionnez au sujet de l'enfilage. Si votre application est multithreadée, nous vous recommandons de vous assurer que l'option Session utilisé est local à... quelque chose. scoped_session() par défaut le rend local au thread actuel. Dans une application web, il est encore mieux de le faire localement, au niveau de la requête. Flask-SQLAlchemy envoie en fait une "fonction de portée" personnalisée à scoped_session() afin d'obtenir une session à l'échelle de la demande. L'application Pyramid moyenne place la session dans le registre "request". Lorsque l'on utilise des schémas de ce type, l'idée de "créer une nouvelle session au début de la demande" continue de sembler être le moyen le plus simple de garder les choses en ordre.

36voto

Berislav Lopac Points 3251

En plus de l'excellente réponse de zzzeek, voici une recette simple pour créer rapidement des sessions jetables et auto-fermées :

from contextlib import contextmanager

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker

@contextmanager
def db_session(db_url):
    """ Creates a context with an open SQLAlchemy session.
    """
    engine = create_engine(db_url, convert_unicode=True)
    connection = engine.connect()
    db_session = scoped_session(sessionmaker(autocommit=False, autoflush=True, bind=engine))
    yield db_session
    db_session.close()
    connection.close()

Utilisation :

from mymodels import Foo

with db_session("sqlite://") as db:
    foos = db.query(Foo).all()

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X