美文网首页
Flask - SQLalchemy 之 lazy 属性

Flask - SQLalchemy 之 lazy 属性

作者: SingleDiego | 来源:发表于2018-08-11 00:04 被阅读743次

参考原文:

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()

现在三张表的结构是这样的:


class 表 student 表 registrations 表

我们执行查询操作:

>>> 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') 的作用。

相关文章

网友评论

      本文标题:Flask - SQLalchemy 之 lazy 属性

      本文链接:https://www.haomeiwen.com/subject/znqrbftx.html