参考原文:
https://blog.csdn.net/bestallen/article/details/52601457
https://blog.csdn.net/jiulixiang_88/article/details/80587071
relationship 的 lazy
属性指定 sqlalchemy 数据库什么时候加载数据:
- select:就是访问到属性的时候,就会全部加载该属性的数据
- joined:对关联的两个表使用联接
- subquery:与joined类似,但使用子子查询
- dynamic:不加载记录,但提供加载记录的查询,也就是生成query对象
我们先建立一个测试模型,一门课程(class)可以对应多个学生(student)。
class Class(db.Model):
__tablename__ = 'classes'
id = db.Column(db.Integer, primary_key=True)
students = db.relationship('Student', backref='_class', lazy="select")
name = db.Column(db.String(64))
def __repr__(self):
return '<Class: %r>' %self.name
class Student(db.Model):
__tablename__ = 'students'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
class_id = db.Column(db.Integer, db.ForeignKey('classes.id'))
def __repr__(self):
return '<Student: %r>' %self.name
注意 Class 里面的 students 是 relationship,里面的 lazy
的值是 select
,也就是默认值。
手动插入几条数据:
c1 = Class(name='语文')
c2 = Class(name='数学')
c3 = Class(name='英语')
db.session.add(c1)
db.session.add(c2)
db.session.add(c3)
s1 = Student(name='小明', class_id=1)
s2 = Student(name='小张', class_id=1)
s3 = Student(name='小李', class_id=2)
db.session.add(s1)
db.session.add(s2)
db.session.add(s3)
db.session.commit()
我们作以下查询操作:
>>> c1 = Class.query.first()
>>> c1.students
[<Student: '小明'>, <Student: '小张'>]
可以看到,当 lazy
属性是 select
的时候,是直接返回了关联的对象,并由对象为元素组成了一个 list
。
再试试另一组查询操作:
>>> s1 = Student.query.first()
>>> s1._class
<Class: '语文'>
用 s1 这个实例对象,通过 Class 里面的 backref=_class
属性,也能访问 Class 内容,因为 backref=_class
默认也是 select
属性,所以,也是直接导出结果。
下面我把 lazy
属性改成 dynamic
。
students = db.relationship('Student', backref='_class', lazy="dynamic")
再执行上面的查询:
>>> c1 = Class.query.first()
>>> c1.students
<sqlalchemy.orm.dynamic.AppenderBaseQuery at 0x16770c51f60>
>>> c1.students.all()
[<Student: '小明'>, <Student: '小张'>]
可以看到,c1.students
变成了一个查询对象,而不是直接生成列表。我们可以对生成的查询对象再执行 one()
,all()
,filter()
等方法。
lazy="dynamic"
只可以用在一对多和多对多关系中,不可以用在一对一和多对一中。
前面所用到的 backref
属性,由于是一对多的关系,所以用的是默认的
select
,如果想要为 backref
改成 dynamic
属性呢?首先我们要把模型改成多对多关系,来进行测试。
registrations = db.Table(
'registrations',
db.Column('student_id', db.Integer, db.ForeignKey('students.id')),
db.Column('class_id', db.Integer, db.ForeignKey('classes.id'))
)
class Student(db.Model):
__tablename__ = 'students'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
def __repr__(self):
return '<Student: %r>' %self.name
class Class(db.Model):
__tablename__ = 'classes'
id = db.Column(db.Integer, primary_key=True)
students = db.relationship(
'Student',
secondary = registrations,
backref = db.backref('_class',lazy="dynamic"),
lazy = "dynamic"
)
name = db.Column(db.String(64))
def __repr__(self):
return '<Class: %r>' %self.name
我们进行一下查询操作:
>>> s1 = Student.query.first()
>>> s1._class
<sqlalchemy.orm.dynamic.AppenderBaseQuery at 0x28e2fce9b70>
>>> s1._class.all()
>>> [<Class: '语文'>, <Class: '英语'>]
我们把 SQL 语句打印出来看看:
>>> print(s1._class)
SELECT classes.id AS classes_id, classes.name AS classes_name
FROM classes, registrations
WHERE ? = registrations.student_id AND classes.id = registrations.class_id
执行一下 SQL 语句。

该语句通过学生的 id 找到该学生对应的课程,返回的对象信息只有 class 表中包含的字段。
同样地,我们试试从课程查询学生。
>>> c1 = Class.query.first()
>>> print(c1.students)
SELECT students.id AS students_id, students.name AS students_name
FROM students, registrations
WHERE ? = registrations.class_id AND students.id = registrations.student_id
得到的也是类似的 SQL 语句。
接下来,我们把 lazy
属性改为 joined
。
backref = db.backref('_class',lazy="joined"),
这个 joined
属性非常之奇妙,因为他不是直接改变 _class
这个 backref
生成的结果,他反而是改变了这个 relationship
的属性的结果(也就是 students
属性)
我们尝试以下查询操作:
>>> c1 = Class.query.first()
>>> print(c1.students)
SELECT students.id AS students_id,
students.name AS students_name,
classes_1.id AS classes_1_id,
classes_1.name AS classes_1_name
FROM registrations, students LEFT OUTER JOIN (
registrations AS registrations_1 JOIN
classes AS classes_1 ON
classes_1.id = registrations_1.class_id
) ON students.id = registrations_1.student_id
WHERE ? = registrations.class_id AND
students.id = registrations.student_id
执行一下该 SQL 语句:

可以看到程序是进行了 Join 操作,把三个表都查询了。
在下面的多对多例子中,我们可以看到上述的 lazy
方式的优势,
我们把关联表改为实体 model
,并且额外增加一个时间信息字段。模型代码如下:
class Registration(db.Model):
__tablename__ = 'registrations'
student_id = db.Column(
db.Integer,
db.ForeignKey('students.id'),
primary_key=True
)
class_id = db.Column(
db.Integer,
db.ForeignKey('classes.id'),
primary_key=True
)
create_at = db.Column(
db.DateTime,
default=datetime.utcnow
)
class Student(db.Model):
__tablename__ = 'students'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
_classes = db.relationship(
'Registration',
foreign_keys = [Registration.student_id],
backref = db.backref('_student', lazy='joined'),
lazy = 'dynamic'
)
def __repr__(self):
return '<Student: %r>' %self.name
class Class(db.Model):
__tablename__ = 'classes'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
_students = db.relationship(
'Registration',
foreign_keys = [Registration.class_id],
backref = db.backref('_class', lazy='joined'
),
lazy='dynamic')
def __repr__(self):
return '<Class: %r>' %self.name
手动插入数据:
c1 = Class(id=1, name='语文')
c2 = Class(id=2, name='数学')
s1 = Student(id=1, name='小明')
s2 = Student(id=2, name='小李')
s3 = Student(id=3, name='小红')
r1 = Registration(student_id=3, class_id=1)
r2 = Registration(student_id=2, class_id=2)
r3 = Registration(student_id=1, class_id=1)
r4 = Registration(student_id=1, class_id=2)
db.session.add_all([s1, s2, s3, c1, c2, r1, r2, r3, r4])
db.session.commit()
现在三张表的结构是这样的:



我们执行查询操作:
>>> s1 = Student.query.first()
>>> c1 = Class.query.first()
>>> s1._classes
<sqlalchemy.orm.dynamic.AppenderBaseQuery at 0x1d572c33710>
>>> s1._classes.all()
[<Registration 1, 1>, <Registration 1, 2>]
可见现在 s1._classes
的调用只返回 Registration 对象,并不返回 Student 和 Class 对象。
我们在 Student 类的声明里面定义了 backref = db.backref('_student', lazy='joined')
,所以可以用 registration._student
来调用对应的 Student 对象;同理要查询 Class 对象可使用 registration._class
。
>>> for i in s1._classes.all():
....: print(i._class)
....:
<Class: '语文'>
<Class: '数学'>
用列表生成式表达:
>>> list(i._class for i in s1._classes.all())
[<Class: '语文'>, <Class: '数学'>]
那么问题就来了,这里在调用 Registration
的 _class
和 _student
时候, 还需不需要再查询一遍数据库呢?
下面通过查看执行的 sql 语句来看看。
>>> print(s1._classes)
SELECT registrations.student_id AS registrations_student_id,
registrations.class_id AS registrations_class_id,
registrations.create_at AS registrations_create_at,
students_1.id AS students_1_id,
students_1.name AS students_1_name,
classes_1.id AS classes_1_id,
classes_1.name AS classes_1_name
FROM registrations LEFT OUTER JOIN students AS students_1 ON
students_1.id = registrations.student_id
LEFT OUTER JOIN classes AS classes_1 ON
classes_1.id = registrations.class_id
WHERE ? = registrations.student_id

我们可以发现: 跟上一个例子一样,s1._class
不仅查询了对应的 class
信息,而且通过 join 操作,获取到了相应的 Student 和 Class对象。
换句话说,把 Registration 的 _student
和 _class
两个回引属性均指向了对应的对象。
也就是说,s1._class
这一条查询语句就可以把上述操作都完成。这个就是 backref=db.backref('_class', lazy='joined')
的作用。
网友评论