lazy = True(Flask-)SQLAlchemy

时间:2017-07-17 08:51:06

标签: python sqlalchemy

我正在学习SQLAlchemy,我想确保我已正确理解backref中的relationship参数。

例如

from app import db

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True)

    posts = db.relationship('Post', backref='author', lazy=True)


class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    body = db.Column(db.String(140))

    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

假设我有一个用户对象j = models.User.query.get(1)。我的问题是,以下事情之间有什么区别吗?

  • j.posts
  • Post.query.filter_by(author=j).all()
  • Post.query.with_parent(j).all()
  • Post.query.with_parent(j, property='posts').all()
  • Post.query.with_parent(j, property=User.posts).all()

返回的结果是一样的,但我不知道执行的SQL语句是否相同。

我尝试了什么

SQLAlchemy docs说:

  

with_parent(instance, property=None, from_entity=None)

     

...给定的property可以是None,在这种情况下,会针对此Query对象的目标映射器执行搜索。

所以最后三个语句看起来是一样的,但我并不真正理解这个Query对象的目标映射器所引用的内容。在这种情况下是Post,因为此查询是在Post上执行的吗?

2 个答案:

答案 0 :(得分:4)

即使生成的SQL语句相同,您登记的命令也可能对您的应用程序产生不同的影响,例如: j.posts将缓存( memoize ,不要与Werkzeug缓存混淆)您获得的结果,而其他人将每次都会获取它们。

如果您从查询中删除.all(),则只需打印它们即可:

query = Post.query.filter_by(author=j)
print(query)

哪会导致:

SELECT post.id AS post_id, post.body AS post_body, post.user_id AS post_user_id 
FROM post 
WHERE ? = post.user_id

使用.all()就像获取[m for m in query])。

查询打印技巧不适用于j.posts,它将返回如下内容:

> print(j.posts)
> [Post(...), Post(..)]

但是,您仍然可以使用内置的sqlalchemy记录器查看所有静默发出的查询。请参阅以下代码:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.engine import Engine
from sqlalchemy import event
import logging


app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/tests.db'
db = SQLAlchemy(app)


logging.basicConfig()
logger = logging.getLogger('sqlalchemy.engine')


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True)

    posts = db.relationship('Post', backref='author', lazy=True)


class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    body = db.Column(db.String(140))

    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

db.drop_all()
db.create_all()
user = User(username='test', posts=[Post(body='some body')])
db.session.add(user)
db.session.commit()

# start logging
logger.setLevel(logging.DEBUG)
j = User.query.get(1)

queries = {
    "j.posts",
    "Post.query.filter_by(author=j)",
    "Post.query.with_parent(j)",
    "Post.query.with_parent(j, property='posts')",
    "Post.query.with_parent(j, property=User.posts)",
}

def test_queries():
    for name in queries:
        print('\n=======')
        print('Executing %s:' % name)
        query = eval(name)
        print(query)

test_queries()  # you should see j.posts query here
print('Second test')
test_queries()  # but not here

回到你的问题:是的,发出的SQL查询是相同的。

查询对象的目标映射器中,查询对象的目标在您的示例中引用Post。当你声明Post类,继承自db.Model时,对于SQLAlchemy来解耦这就像创建一个对象帖子和映射这个属性一样对象到专门创建的表的列。

下面有一个Mapper类的实例,它负责您创建的每个模型的映射(了解有关此处映射的更多信息:Types of Mappings)。您只需让此映射器在模型上调用class_mapper,或在模型实例上调用object_mapper

from sqlalchemy.orm import object_mapper, class_mapper, 
from sqlalchemy.orm.mapper import Mapper
assert object_mapper(j) is class_mapper(User)
assert type(class_mapper(User)) is Mapper

Mapper包含有关模型中列和关系的所有必要信息。在调用Post.query.with_parent(j)时,此信息用于查找与帖子和用户对象相关的属性(即关系),因此在您的情况下使用User.posts填充“属性”。

答案 1 :(得分:1)

要查看查询,可以使用-i运行python脚本,然后单独运行每个查询,它将打印出运行的SQL代码。 例如:

main.py

import sqlalchemy
from sqlalchemy import create_engine, Column, Integer, String, Sequence
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os

engine = create_engine('sqlite:///:memory:', echo=True)

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, Sequence('user_id_seq'), primary_key=True)
    name = Column(String(50))
    fullname = Column(String(50))
    password = Column(String(12))

    def __repr__(self):
        return "< User(name={}, fullname={}, password={} )>".format(self.name, self.fullname, self.password)

Base.metadata.create_all(engine)

ed_user= User(name='ed', fullname='Ed Jones', password='edpassword')
Session = sessionmaker(bind=engine, autoflush=False)
session = Session()
session.add(ed_user)
session.add_all([
    User(name='wendy', fullname='Wendy Williams', password='foobar'),
    User(name='mary', fullname='Mary Contraty', password='xxg527'),
    User(name='fred', fullname='Fred Flinstone', password='blah')
])
session.commit()
os.system('clear')

现在您使用python -i main.py运行它,键入:session.query(User).filter_by(name='ed').first(),您将看到生成的SQL。在running所有测试后,我得出结论,他们都相同。使用此方法,您可以测试任何查询并查看是否存在任何差异。

P.S。我添加了os.system('clear')来删除创建数据库和其他一些东西的所有不必要的输出。