• SQLAlchemy中Model.query和session.query(Model)的区别


    我们使用Flask 0.11.1,Flask-SQLAlchemy 2.1使用PostgreSQL作为DBMS.

    示例使用以下代码更新数据库中的数据:

    entry = Entry.query.get(1)
    entry.name = 'New name'
    db.session.commit()

    从Flask shell执行时,这完全正常,因此数据库已正确配置.现在,我们的控制器用于更新条目,略微简化(没有验证和其他样板),如下所示:

    def details(id):
        entry = Entry.query.get(id)
    
        if entry:
            if request.method == 'POST':
                form = request.form
                entry.name = form['name']
                db.session.commit()
                flash('Updated successfully.')
            return render_template('/entry/details.html', entry=entry)
        else:
            flash('Entry not found.')
            return redirect(url_for('entry_list'))
    
    # In the application the URLs are built dynamically, hence why this instead of @app.route
    app.add_url_rule('/entry/details/<int:id>', 'entry_details', details, methods=['GET', 'POST'])

    当我在details.html中提交表单时,我可以完全看到更改,这意味着表单已正确提交,有效并且模型对象已更新.但是,当我重新加载页面时,更改已经消失,就好像它已被DBMS回滚一样.

    我启用了app.config [‘SQLALCHEMY_ECHO’] = True,我可以在自己的手动提交之前看到“ROLLBACK”.

    如果我换行:

    entry = Entry.query.get(id)

    至:

    entry = db.session.query(Entry).get(id)

    正如https://stackoverflow.com/a/21806294/4454028中所解释的那样,它确实按预期工作,因此我猜测Flask-SQLAlchemy的Model.query实现中存在某种错误.

    但是,由于我更喜欢​​第一个构造,我对Flask-SQLAlchemy进行了快速修改,并从原始版本重新定义了查询@property:

    class _QueryProperty(object):
    
        def __init__(self, sa):
            self.sa = sa
    
        def __get__(self, obj, type):
            try:
                mapper = orm.class_mapper(type)
                if mapper:
                    return type.query_class(mapper, session=self.sa.session())
            except UnmappedClassError:
                return None

    至:

    class _QueryProperty(object):
    
        def __init__(self, sa):
            self.sa = sa
    
        def __get__(self, obj, type):
            return self.sa.session.query(type)

    其中sa是Flask-SQLAlchemy对象(即控制器中的db).

    现在,这就是事情变得奇怪的地方:它仍然没有保存变化.代码完全相同,但DBMS仍在回滚我的更改.

    我读到Flask-SQLAlchemy可以在拆卸时执行提交,并尝试添加以下内容:

    app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True

    突然间,一切正常.问题是:为什么?

    是否应该只在视图完成渲染时才会发生拆解?为什么修改后的Entry.query与db.session.query(Entry)的行为不同,即使代码相同?

    最佳答案
    以下是更改模型实例并将其提交到数据库的正确方法:
    # get an instance of the 'Entry' model
    entry = Entry.query.get(1)
    
    # change the attribute of the instance; here the 'name' attribute is changed
    entry.name = 'New name'
    
    # now, commit your changes to the database; this will flush all changes 
    # in the current session to the database
    db.session.commit()

    注意:不要使用SQLALCHEMY_COMMIT_ON_TEARDOWN,因为它被认为是有害的,也从文档中删除.见the changelog for version 2.0.

    编辑:如果你有两个普通会话对象(使用sessionmaker()创建)而不是作用域会话,那么在上面调用db.session.add(entry)代码会引发错误sqlalchemy.exc.InvalidRequestError:对象”已经附加会话’2′(这是’3′).有关sqlalchemy会话的更多信息,请阅读以下部分

    范围会话与正常会话的主要区别

    我们主要从sessionmaker()调用构造并用于与我们的数据库通信的会话对象是正常会话.如果再次调用sessionmaker(),您将获得一个新的会话对象,其状态独立于上一个会话.例如,假设我们有两个以下列方式构造的会话对象:

    from sqlalchemy import Column, String, Integer, ForeignKey
    from sqlalchemy.ext.declarative import declarative_base
    
    Base = declarative_base()
    
    class User(Base):
        __tablename__ = 'user'
        id = Column(Integer, primary_key=True)
        name = Column(String)
    
    
    from sqlalchemy import create_engine
    engine = create_engine('sqlite:///')
    
    from sqlalchemy.orm import sessionmaker
    session = sessionmaker()
    session.configure(bind=engine)
    Base.metadata.create_all(engine)
    
    # Construct the first session object
    s1 = session()
    # Construct the second session object
    s2 = session()

    然后,我们将无法同时向s1和s2添加相同的User对象.换句话说,一个对象最多只能附加一个唯一的会话对象.

    >>> jessica = User(name='Jessica')
    >>> s1.add(jessica)
    >>> s2.add(jessica)
    Traceback (most recent call last):
    ......
    sqlalchemy.exc.InvalidRequestError: Object '' is already attached to session '2' (this is '3')

    但是,如果从scoped_session对象检索会话对象,那么我们就没有这样的问题,因为scoped_session对象维护了同一会话对象的注册表.

    >>> session_factory = sessionmaker(bind=engine)
    >>> session = scoped_session(session_factory)
    >>> s1 = session()
    >>> s2 = session()
    >>> jessica = User(name='Jessica')
    >>> s1.add(jessica)
    >>> s2.add(jessica)
    >>> s1 is s2
    True
    >>> s1.commit()
    >>> s2.query(User).filter(User.name == 'Jessica').one()

    请注意,s1和s2是相同的会话对象,因为它们都是从保持对同一会话对象的引用的scoped_session对象中检索的.

    提示

    因此,尽量避免创建多个普通会话对象.创建会话的一个对象,并在从声明模型到查询的任何地方使用它.

    补充:

    也存在使用db.session.commit()提交数据后,使用model.query()查不到新增加的数据

    原因:db.session.commit()是提交了数据到数据库,但是没有刷新模型映射中的数据,也就是model.query()中的数据。
    而使用db.session.query()则是 从整个服务会话中进行查询,而db.session.commit()提交的数据在这里是有刷新的,根源还是上面提到的范围会话与正常会话的主要区别。

  • 相关阅读:
    线程间的通信
    高速排序算法
    LightOJ 1205 Palindromic Numbers
    java异常处理
    Android Bundle类
    Linux守护进程的编程实现
    深入浅出JMS(一)——JMS简单介绍
    Maven安装与配置
    matlab学习------------普通dialog对话框,错误对话框errordlg,警告对话框warndlg
    读书笔记:计算机网络4章:网络层
  • 原文地址:https://www.cnblogs.com/miaoweiye/p/12016385.html
Copyright © 2020-2023  润新知