(Flask-)SQLAlchemy 中的惰性 = 真

nal*_*zok 2 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'))
Run Code Online (Sandbox Code Playgroud)

假设我有一个 User 对象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的文档说:

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

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

所以最后三个语句似乎相同,但我真的不明白这个 Query 对象的目标映射器指的是什么。它是Post在这种情况下,执行这个查询Post

kra*_*ski 5

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

如果您.all()从查询中删除,您可以简单地打印它们:

query = Post.query.filter_by(author=j)
print(query)
Run Code Online (Sandbox Code Playgroud)

这将导致:

SELECT post.id AS post_id, post.body AS post_body, post.user_id AS post_user_id 
FROM post 
WHERE ? = post.user_id
Run Code Online (Sandbox Code Playgroud)

使用.all()本质上就像得到[m for m in query])。

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

> print(j.posts)
> [Post(...), Post(..)]
Run Code Online (Sandbox Code Playgroud)

尽管如此,您仍然可以使用内置的 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
Run Code Online (Sandbox Code Playgroud)

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

Query object's target mapper 中Query object's targetPost在您的示例中引用。解耦这个,当你声明Post类,继承自 db.Model,对于 SQLAlchemy 就像创建一个对象Post 并将这个对象的属性映射到专门创建的表的列。

下面有一个Mapper类的实例,它负责您创建的每个模型的映射(在此处了解有关映射的更多信息:映射类型)。您可以简单地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
Run Code Online (Sandbox Code Playgroud)

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